source: trunk/libtransmission/platform.c @ 10634

Last change on this file since 10634 was 10634, checked in by charles, 11 years ago

re-tweak the previous commit based on feedback from kklimonda

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