source: trunk/libtransmission/platform.c @ 10919

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

(trunk libT) a couple more minor -Wconversion warnings

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