source: trunk/libtransmission/platform.c @ 9965

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

(trunk) No code changes here... filling in some of the blanks in the "peers" and "utils" doxygen groups' documentation.

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