source: trunk/libtransmission/platform.c @ 8136

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

(trunk libT) #1957: tr_getDownloadDir() should honor XDG

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