source: trunk/libtransmission/platform.c @ 12204

Last change on this file since 12204 was 12204, checked in by jordan, 10 years ago

(trunk) #4138 "use stdbool.h instead of tr_bool" -- done.

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