source: trunk/libtransmission/platform.c @ 7113

Last change on this file since 7113 was 7113, checked in by charles, 12 years ago

#1406: need tr_getDefaultDownloadDir() for consistency between apps

  • Property svn:keywords set to Date Rev Author Id
File size: 18.1 KB
Line 
1/*
2 * This file Copyright (C) 2008 Charles Kerr <charles@rebelbase.com>
3 *
4 * This file is licensed by the GPL version 2.  Works owned by the
5 * Transmission project are granted a special exemption to clause 2(b)
6 * so that the bulk of its code can remain under the MIT license.
7 * This exemption does not extend to derived works not owned by
8 * the Transmission project.
9 *
10 * $Id: platform.c 7113 2008-11-15 17:39:54Z charles $
11 */
12
13#ifdef __BEOS__
14 #include <signal.h>
15 #include <fs_info.h>
16 #include <FindDirectory.h>
17 #include <kernel/OS.h>
18 #define BEOS_MAX_THREADS 256
19#elif defined( WIN32 )
20 #include <windows.h>
21 #include <shlobj.h> /* for CSIDL_APPDATA, CSIDL_MYDOCUMENTS */
22#else
23 #ifdef SYS_DARWIN
24  #include <CoreFoundation/CoreFoundation.h>
25 #endif
26
27 #define _XOPEN_SOURCE 500  /* needed for recursive locks. */
28 #ifndef __USE_UNIX98
29  #define __USE_UNIX98 /* some older Linuxes need it spelt out for them */
30 #endif
31 #include <pthread.h>
32#endif
33
34#include <assert.h>
35#include <stdio.h>
36#include <stdlib.h>
37#include <string.h>
38
39#include <sys/stat.h>
40#include <sys/types.h>
41#ifdef WIN32
42#include <libgen.h>
43#endif
44#include <dirent.h>
45#include <fcntl.h>
46#include <unistd.h> /* getuid getpid close */
47
48#include "transmission.h"
49#include "list.h"
50#include "platform.h"
51#include "utils.h"
52
53/***
54****  THREADS
55***/
56
57#ifdef __BEOS__
58typedef thread_id tr_thread_id;
59#elif defined( WIN32 )
60typedef DWORD tr_thread_id;
61#else
62typedef pthread_t tr_thread_id;
63#endif
64
65static tr_thread_id
66tr_getCurrentThread( void )
67{
68#ifdef __BEOS__
69    return find_thread( NULL );
70#elif defined( WIN32 )
71    return GetCurrentThreadId( );
72#else
73    return pthread_self( );
74#endif
75}
76
77static int
78tr_areThreadsEqual( tr_thread_id a,
79                    tr_thread_id b )
80{
81#ifdef __BEOS__
82    return a == b;
83#elif defined( WIN32 )
84    return a == b;
85#else
86    return pthread_equal( a, b );
87#endif
88}
89
90struct tr_thread
91{
92    void            ( * func )( void * );
93    void *          arg;
94    tr_thread_id    thread;
95#ifdef WIN32
96    HANDLE          thread_handle;
97#endif
98};
99
100int
101tr_amInThread( const tr_thread * t )
102{
103    return tr_areThreadsEqual( tr_getCurrentThread( ), t->thread );
104}
105
106#ifdef WIN32
107 #define ThreadFuncReturnType unsigned WINAPI
108#else
109 #define ThreadFuncReturnType void
110#endif
111
112static ThreadFuncReturnType
113ThreadFunc( void * _t )
114{
115    tr_thread * t = _t;
116
117#ifdef __BEOS__
118    /* This is required because on BeOS, SIGINT is sent to each thread,
119       which kills them not nicely */
120    signal( SIGINT, SIG_IGN );
121#endif
122
123    t->func( t->arg );
124
125#ifdef WIN32
126    _endthreadex( 0 );
127    return 0;
128#endif
129}
130
131tr_thread *
132tr_threadNew( void   ( *func )(void *),
133              void * arg )
134{
135    tr_thread * t = tr_new0( tr_thread, 1 );
136
137    t->func = func;
138    t->arg  = arg;
139
140#ifdef __BEOS__
141    t->thread =
142        spawn_thread( (void*)ThreadFunc, "beos thread", B_NORMAL_PRIORITY,
143                     t );
144    resume_thread( t->thread );
145#elif defined( WIN32 )
146    {
147        unsigned int id;
148        t->thread_handle =
149            (HANDLE) _beginthreadex( NULL, 0, &ThreadFunc, t, 0,
150                                     &id );
151        t->thread = (DWORD) id;
152    }
153#else
154    pthread_create( &t->thread, NULL, ( void * ( * )(
155                                           void * ) )ThreadFunc, t );
156#endif
157
158    return t;
159}
160
161/***
162****  LOCKS
163***/
164
165struct tr_lock
166{
167    int                 depth;
168#ifdef __BEOS__
169    sem_id              lock;
170    thread_id           lockThread;
171#elif defined( WIN32 )
172    CRITICAL_SECTION    lock;
173    DWORD               lockThread;
174#else
175    pthread_mutex_t     lock;
176    pthread_t           lockThread;
177#endif
178};
179
180tr_lock*
181tr_lockNew( void )
182{
183    tr_lock *           l = tr_new0( tr_lock, 1 );
184
185#ifdef __BEOS__
186    l->lock = create_sem( 1, "" );
187#elif defined( WIN32 )
188    InitializeCriticalSection( &l->lock ); /* supports recursion */
189#else
190    pthread_mutexattr_t attr;
191    pthread_mutexattr_init( &attr );
192    pthread_mutexattr_settype( &attr, PTHREAD_MUTEX_RECURSIVE );
193    pthread_mutex_init( &l->lock, &attr );
194#endif
195
196    return l;
197}
198
199void
200tr_lockFree( tr_lock * l )
201{
202#ifdef __BEOS__
203    delete_sem( l->lock );
204#elif defined( WIN32 )
205    DeleteCriticalSection( &l->lock );
206#else
207    pthread_mutex_destroy( &l->lock );
208#endif
209    tr_free( l );
210}
211
212void
213tr_lockLock( tr_lock * l )
214{
215#ifdef __BEOS__
216    acquire_sem( l->lock );
217#elif defined( WIN32 )
218    EnterCriticalSection( &l->lock );
219#else
220    pthread_mutex_lock( &l->lock );
221#endif
222    assert( l->depth >= 0 );
223    if( l->depth )
224        assert( tr_areThreadsEqual( l->lockThread, tr_getCurrentThread( ) ) );
225    l->lockThread = tr_getCurrentThread( );
226    ++l->depth;
227}
228
229int
230tr_lockHave( const tr_lock * l )
231{
232    return ( l->depth > 0 )
233           && ( tr_areThreadsEqual( l->lockThread, tr_getCurrentThread( ) ) );
234}
235
236void
237tr_lockUnlock( tr_lock * l )
238{
239    assert( l->depth > 0 );
240    assert( tr_areThreadsEqual( l->lockThread, tr_getCurrentThread( ) ) );
241
242    --l->depth;
243    assert( l->depth >= 0 );
244#ifdef __BEOS__
245    release_sem( l->lock );
246#elif defined( WIN32 )
247    LeaveCriticalSection( &l->lock );
248#else
249    pthread_mutex_unlock( &l->lock );
250#endif
251}
252
253/***
254****  PATHS
255***/
256
257#if !defined( WIN32 ) && !defined( __BEOS__ ) && !defined( __AMIGAOS4__ )
258 #include <pwd.h>
259#endif
260
261static const char *
262getHomeDir( void )
263{
264    static char * home = NULL;
265
266    if( !home )
267    {
268        home = tr_strdup( getenv( "HOME" ) );
269
270        if( !home )
271        {
272#ifdef WIN32
273            char appdata[MAX_PATH]; /* SHGetFolderPath() requires MAX_PATH */
274            *appdata = '\0';
275            SHGetFolderPath( NULL, CSIDL_MYDOCUMENTS, NULL, 0, appdata );
276            home = tr_strdup( appdata );
277#elif defined( __BEOS__ ) || defined( __AMIGAOS4__ )
278            home = tr_strdup( "" );
279#else
280            struct passwd * pw = getpwuid( getuid( ) );
281            if( pw )
282                home = tr_strdup( pw->pw_dir );
283            endpwent( );
284#endif
285        }
286
287        if( !home )
288            home = tr_strdup( "" );
289    }
290
291    return home;
292}
293
294static const char *
295getOldConfigDir( void )
296{
297    static char * path = NULL;
298
299    if( !path )
300    {
301#ifdef __BEOS__
302        char buf[MAX_PATH_LENGTH];
303        find_directory( B_USER_SETTINGS_DIRECTORY,
304                       dev_for_path( "/boot" ), true,
305                       buf, sizeof( buf ) );
306        path = tr_buildPath( buf, "Transmission", NULL );
307#elif defined( SYS_DARWIN )
308        path = tr_buildPath( getHomeDir( ), "Library",
309                              "Application Support",
310                              "Transmission", NULL );
311#elif defined( __AMIGAOS4__ )
312        path = tr_strdup( "PROGDIR:.transmission" );
313#elif defined( WIN32 )
314        char appdata[MAX_PATH]; /* SHGetFolderPath() requires MAX_PATH */
315        SHGetFolderPath( NULL, CSIDL_APPDATA, NULL, 0, appdata );
316        path = tr_buildPath( appdata, "Transmission", NULL );
317#else
318        path = tr_buildPath( getHomeDir( ), ".transmission", NULL );
319#endif
320    }
321
322    return path;
323}
324
325#if defined(SYS_DARWIN) || defined(WIN32)
326 #define RESUME_SUBDIR  "Resume"
327 #define TORRENT_SUBDIR "Torrents"
328#else
329 #define RESUME_SUBDIR  "resume"
330 #define TORRENT_SUBDIR "torrents"
331#endif
332
333static const char *
334getOldTorrentsDir( void )
335{
336    static char * path = NULL;
337
338    if( !path )
339        path = tr_buildPath( getOldConfigDir( ), TORRENT_SUBDIR, NULL );
340
341    return path;
342}
343
344static const char *
345getOldCacheDir( void )
346{
347    static char * path = NULL;
348
349    if( !path )
350    {
351#if defined( __BEOS__ ) || defined( WIN32 )
352        path = tr_buildPath( getOldConfigDir( ), "Cache", NULL );
353#elif defined( SYS_DARWIN )
354        path = tr_buildPath( getHomeDir( ), "Library", "Caches", "Transmission", NULL );
355#else
356        path = tr_buildPath( getOldConfigDir( ), "cache", NULL );
357#endif
358    }
359
360    return path;
361}
362
363static void
364moveFiles( const char * oldDir,
365           const char * newDir )
366{
367    if( oldDir && newDir && strcmp( oldDir, newDir ) )
368    {
369        DIR * dirh = opendir( oldDir );
370        if( dirh )
371        {
372            int             count = 0;
373            struct dirent * dirp;
374            while( ( dirp = readdir( dirh ) ) )
375            {
376                if( strcmp( dirp->d_name,
377                            "." ) && strcmp( dirp->d_name, ".." ) )
378                {
379                    char * o = tr_buildPath( oldDir, dirp->d_name, NULL );
380                    char * n = tr_buildPath( newDir, dirp->d_name, NULL );
381                    rename( o, n );
382                    ++count;
383                    tr_free( n );
384                    tr_free( o );
385                }
386            }
387
388            if( count )
389                tr_inf( _( "Migrated %1$d files from \"%2$s\" to \"%3$s\"" ),
390                        count, oldDir, newDir );
391            closedir( dirh );
392        }
393    }
394}
395
396static void
397migrateFiles( const tr_handle * handle )
398{
399    static int migrated = FALSE;
400
401    if( !migrated )
402    {
403        const char * oldDir;
404        const char * newDir;
405        migrated = TRUE;
406
407        oldDir = getOldTorrentsDir( );
408        newDir = tr_getTorrentDir( handle );
409        moveFiles( oldDir, newDir );
410
411        oldDir = getOldCacheDir( );
412        newDir = tr_getResumeDir( handle );
413        moveFiles( oldDir, newDir );
414    }
415}
416
417void
418tr_setConfigDir( tr_handle *  handle,
419                 const char * configDir )
420{
421    char * path;
422
423    handle->configDir = tr_strdup( configDir );
424
425    path = tr_buildPath( configDir, RESUME_SUBDIR, NULL );
426    tr_mkdirp( path, 0777 );
427    handle->resumeDir = path;
428
429    path = tr_buildPath( configDir, TORRENT_SUBDIR, NULL );
430    tr_mkdirp( path, 0777 );
431    handle->torrentDir = path;
432
433    migrateFiles( handle );
434}
435
436const char *
437tr_sessionGetConfigDir( const tr_handle * handle )
438{
439    return handle->configDir;
440}
441
442const char *
443tr_getTorrentDir( const tr_handle * handle )
444{
445    return handle->torrentDir;
446}
447
448const char *
449tr_getResumeDir( const tr_handle * handle )
450{
451    return handle->resumeDir;
452}
453
454const char*
455tr_getDefaultConfigDir( void )
456{
457    static char * s = NULL;
458
459    if( !s )
460    {
461        if( ( s = getenv( "TRANSMISSION_HOME" ) ) )
462        {
463            s = tr_strdup( s );
464        }
465        else
466        {
467#ifdef SYS_DARWIN
468            s = tr_buildPath( getHomeDir( ), "Library",
469                              "Application Support", "Transmission", NULL );
470#elif defined( WIN32 )
471            char appdata[MAX_PATH]; /* SHGetFolderPath() requires MAX_PATH */
472            SHGetFolderPath( NULL, CSIDL_APPDATA, NULL, 0, appdata );
473            s = tr_buildPath( appdata, "Transmission", NULL );
474#else
475            if( ( s = getenv( "XDG_CONFIG_HOME" ) ) )
476                s = tr_buildPath( s, "transmission", NULL );
477            else
478                s = tr_buildPath( getHomeDir( ), ".config", "transmission", NULL );
479#endif
480        }
481    }
482
483    return s;
484}
485
486const char*
487tr_getDefaultDownloadDir( void )
488{
489    static char * s = NULL;
490
491    if( s == NULL )
492        s = tr_buildPath( getHomeDir( ), "Downloads", NULL );
493
494    return s;
495}
496
497/***
498****
499***/
500
501static int
502isClutchDir( const char * path )
503{
504    struct stat sb;
505    char * tmp = tr_buildPath( path, "javascript", "transmission.js", NULL );
506    const int ret = !stat( tmp, &sb );
507    tr_inf( _( "Searching for web interface file \"%s\"" ), tmp );
508    tr_free( tmp );
509    return ret;
510   
511}
512
513const char *
514tr_getClutchDir( const tr_session * session UNUSED )
515{
516    static char * s = NULL;
517
518    if( !s )
519    {
520        if( ( s = getenv( "CLUTCH_HOME" ) ) )
521        {
522            s = tr_strdup( s );
523        }
524        else if( ( s = getenv( "TRANSMISSION_WEB_HOME" ) ) )
525        {
526            s = tr_strdup( s );
527        }
528        else
529        {
530
531#ifdef SYS_DARWIN
532
533            CFURLRef appURL = CFBundleCopyBundleURL( CFBundleGetMainBundle( ) );
534            CFStringRef appRef = CFURLCopyFileSystemPath( appURL,
535                                                         kCFURLPOSIXPathStyle );
536            const char * appString = CFStringGetCStringPtr( appRef,
537                                         CFStringGetFastestEncoding( appRef ) );
538            CFRelease( appURL );
539            CFRelease( appRef );
540
541            s = tr_buildPath( appString, "Contents", "Resources", "web", NULL );
542
543#elif defined( WIN32 )
544
545            /* SHGetFolderPath explicitly requires MAX_PATH length */
546            char dir[MAX_PATH];
547           
548            /* Generally, Web interface should be stored in a Web subdir of
549             * calling executable dir. */
550
551            if( s == NULL ) { 
552                /* First, we should check personal AppData/Transmission/Web */
553                SHGetFolderPath( NULL, CSIDL_COMMON_APPDATA, NULL, 0, dir );
554                s = tr_buildPath( dir, "Transmission", "Web", NULL );
555                if( !isClutchDir( s ) ) {
556                    tr_free( s );
557                    s = NULL;
558                }
559            }
560
561            if( s == NULL ) {
562                /* check personal AppData */
563                SHGetFolderPath( NULL, CSIDL_APPDATA, NULL, 0, dir );
564                s = tr_buildPath( dir, "Transmission", "Web", NULL );
565                if( !isClutchDir( s ) ) {
566                    tr_free( s );
567                    s = NULL;
568                }
569            }
570
571            if( s == NULL) {
572                /* check calling module place */
573                GetModuleFileName( GetModuleHandle( NULL ), dir, sizeof( dir ) );
574                s = tr_buildPath( dirname( dir ), "Web", NULL );
575                if( !isClutchDir( s ) ) {
576                    tr_free( s );
577                    s = NULL;
578                }
579            }
580
581#else /* everyone else, follow the XDG spec */
582
583            tr_list *candidates = NULL, *l;
584            const char * tmp;
585
586            /* XDG_DATA_HOME should be the first in the list of candidates */
587            tmp = getenv( "XDG_DATA_HOME" );
588            if( tmp && *tmp )
589                tr_list_append( &candidates, tr_strdup( tmp ) );
590            else {
591                char * dhome = tr_buildPath( getHomeDir( ), ".local", "share", NULL );
592                tr_list_append( &candidates, dhome );
593            }
594
595            /* XDG_DATA_DIRS are the backup directories */
596            tmp = getenv( "XDG_DATA_DIRS" );
597            if( !tmp || !*tmp )
598                tmp =  PACKAGE_DATA_DIR ":/usr/local/share/:/usr/share/";
599            while( tmp && *tmp ) {
600                const char * end = strchr( tmp, ':' );
601                if( end ) {
602                    tr_list_append( &candidates, tr_strndup( tmp, end - tmp ) );
603                    tmp = end + 1;
604                } else {
605                    tr_list_append( &candidates, tr_strdup( tmp ) );
606                    break;
607                }
608            }
609
610            /* walk through the candidates & look for a match */
611            for( l=candidates; l; l=l->next ) {
612                char * path = tr_buildPath( l->data, "transmission", "web", NULL );
613                const int found = isClutchDir( path );
614                if( found ) {
615                    s = path;
616                    break;
617                }
618                tr_free( path );
619            }
620
621            tr_list_free( &candidates, tr_free );
622
623#endif
624
625        }
626    }
627
628    return s;
629}
630
631/***
632****
633***/
634
635tr_lockfile_state_t
636tr_lockfile( const char * filename )
637{
638    tr_lockfile_state_t ret;
639
640#ifdef WIN32
641
642    HANDLE              file = CreateFile(
643        filename,
644        GENERIC_READ | GENERIC_WRITE,
645        FILE_SHARE_READ |
646        FILE_SHARE_WRITE,
647        NULL,
648        OPEN_ALWAYS,
649        FILE_ATTRIBUTE_NORMAL,
650        NULL );
651    if( file == INVALID_HANDLE_VALUE )
652        ret = TR_LOCKFILE_EOPEN;
653    else if( !LockFile( file, 0, 0, 1, 1 ) )
654        ret = TR_LOCKFILE_ELOCK;
655    else
656        ret = TR_LOCKFILE_SUCCESS;
657
658#else
659
660    int fd = open( filename, O_RDWR | O_CREAT, 0666 );
661    if( fd < 0 )
662        ret = TR_LOCKFILE_EOPEN;
663    else
664    {
665        struct flock lk;
666        memset( &lk, 0,  sizeof( lk ) );
667        lk.l_start = 0;
668        lk.l_len = 0;
669        lk.l_type = F_WRLCK;
670        lk.l_whence = SEEK_SET;
671        if( -1 == fcntl( fd, F_SETLK, &lk ) )
672            ret = TR_LOCKFILE_ELOCK;
673        else
674            ret = TR_LOCKFILE_SUCCESS;
675    }
676
677#endif
678
679    return ret;
680}
681
682#ifdef WIN32
683
684/* The following mmap functions are by Joerg Walter, and were taken from
685 * his paper at: http://www.genesys-e.de/jwalter/mix4win.htm
686 */
687
688#if defined(_MSC_VER)
689__declspec( align( 4 ) ) static LONG volatile g_sl;
690#else
691static LONG volatile g_sl __attribute__ ( ( aligned ( 4 ) ) );
692#endif
693
694/* Wait for spin lock */
695static int
696slwait( LONG volatile *sl )
697{
698    while( InterlockedCompareExchange ( sl, 1, 0 ) != 0 )
699        Sleep ( 0 );
700
701    return 0;
702}
703
704/* Release spin lock */
705static int
706slrelease( LONG volatile *sl )
707{
708    InterlockedExchange ( sl, 0 );
709    return 0;
710}
711
712/* getpagesize for windows */
713static long
714getpagesize( void )
715{
716    static long g_pagesize = 0;
717
718    if( !g_pagesize )
719    {
720        SYSTEM_INFO system_info;
721        GetSystemInfo ( &system_info );
722        g_pagesize = system_info.dwPageSize;
723    }
724    return g_pagesize;
725}
726
727static long
728getregionsize( void )
729{
730    static long g_regionsize = 0;
731
732    if( !g_regionsize )
733    {
734        SYSTEM_INFO system_info;
735        GetSystemInfo ( &system_info );
736        g_regionsize = system_info.dwAllocationGranularity;
737    }
738    return g_regionsize;
739}
740
741void *
742mmap( void *ptr,
743      long  size,
744      long  prot,
745      long  type,
746      long  handle,
747      long  arg )
748{
749    static long g_pagesize;
750    static long g_regionsize;
751
752    /* Wait for spin lock */
753    slwait ( &g_sl );
754    /* First time initialization */
755    if( !g_pagesize )
756        g_pagesize = getpagesize ( );
757    if( !g_regionsize )
758        g_regionsize = getregionsize ( );
759    /* Allocate this */
760    ptr = VirtualAlloc ( ptr, size,
761                         MEM_RESERVE | MEM_COMMIT | MEM_TOP_DOWN,
762                         PAGE_READWRITE );
763    if( !ptr )
764    {
765        ptr = (void *) -1;
766        goto mmap_exit;
767    }
768mmap_exit:
769    /* Release spin lock */
770    slrelease ( &g_sl );
771    return ptr;
772}
773
774long
775munmap( void *ptr,
776        long  size )
777{
778    static long g_pagesize;
779    static long g_regionsize;
780    int         rc = -1;
781
782    /* Wait for spin lock */
783    slwait ( &g_sl );
784    /* First time initialization */
785    if( !g_pagesize )
786        g_pagesize = getpagesize ( );
787    if( !g_regionsize )
788        g_regionsize = getregionsize ( );
789    /* Free this */
790    if( !VirtualFree ( ptr, 0,
791                       MEM_RELEASE ) )
792        goto munmap_exit;
793    rc = 0;
794munmap_exit:
795    /* Release spin lock */
796    slrelease ( &g_sl );
797    return rc;
798}
799
800#endif
801
Note: See TracBrowser for help on using the repository browser.