source: trunk/libtransmission/platform.c @ 10814

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

(trunk libT) part of rb07's win32 portability patches from ticket #3311

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