source: trunk/libtransmission/platform.c @ 8138

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

(trunk libT) fix xdg oops

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