source: trunk/libtransmission/platform.c @ 12684

Last change on this file since 12684 was 12684, checked in by livings124, 9 years ago

#4422 On Mac, tr_getWebClientDir() uses incorrect encoding for creating the web content dir path

  • Property svn:keywords set to Date Rev Author Id
File size: 19.7 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 12684 2011-08-15 00:10:06Z livings124 $
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                const CFIndex appStringLength = CFStringGetMaximumSizeOfFileSystemRepresentation(appRef);
575
576                char * appString = tr_malloc( appStringLength );
577                const bool success = CFStringGetFileSystemRepresentation( appRef, appString, appStringLength );
578                assert( success );
579
580                CFRelease( appURL );
581                CFRelease( appRef );
582
583                /* Fallback to the app bundle */
584                s = tr_buildPath( appString, "Contents", "Resources", "web", NULL );
585                if( !isWebClientDir( s ) ) {
586                    tr_free( s );
587                    s = NULL;
588                }
589
590                tr_free( appString );
591            }
592
593#elif defined( WIN32 )
594
595            /* SHGetFolderPath explicitly requires MAX_PATH length */
596            char dir[MAX_PATH];
597
598            /* Generally, Web interface should be stored in a Web subdir of
599             * calling executable dir. */
600
601            if( s == NULL ) {
602                /* First, we should check personal AppData/Transmission/Web */
603                SHGetFolderPath( NULL, CSIDL_COMMON_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 personal AppData */
613                SHGetFolderPath( NULL, CSIDL_APPDATA, NULL, 0, dir );
614                s = tr_buildPath( dir, "Transmission", "Web", NULL );
615                if( !isWebClientDir( s ) ) {
616                    tr_free( s );
617                    s = NULL;
618                }
619            }
620
621            if( s == NULL) {
622                /* check calling module place */
623                GetModuleFileName( GetModuleHandle( NULL ), dir, sizeof( dir ) );
624                s = tr_buildPath( dirname( dir ), "Web", NULL );
625                if( !isWebClientDir( s ) ) {
626                    tr_free( s );
627                    s = NULL;
628                }
629            }
630
631#else /* everyone else, follow the XDG spec */
632
633            tr_list *candidates = NULL, *l;
634            const char * tmp;
635
636            /* XDG_DATA_HOME should be the first in the list of candidates */
637            tmp = getenv( "XDG_DATA_HOME" );
638            if( tmp && *tmp )
639                tr_list_append( &candidates, tr_strdup( tmp ) );
640            else {
641                char * dhome = tr_buildPath( getHomeDir( ), ".local", "share", NULL );
642                tr_list_append( &candidates, dhome );
643            }
644
645            /* XDG_DATA_DIRS are the backup directories */
646            {
647                const char * pkg = PACKAGE_DATA_DIR;
648                const char * xdg = getenv( "XDG_DATA_DIRS" );
649                const char * fallback = "/usr/local/share:/usr/share";
650                char * buf = tr_strdup_printf( "%s:%s:%s", (pkg?pkg:""), (xdg?xdg:""), fallback );
651                tmp = buf;
652                while( tmp && *tmp ) {
653                    const char * end = strchr( tmp, ':' );
654                    if( end ) {
655                        if( ( end - tmp ) > 1 )
656                            tr_list_append( &candidates, tr_strndup( tmp, end - tmp ) );
657                        tmp = end + 1;
658                    } else if( tmp && *tmp ) {
659                        tr_list_append( &candidates, tr_strdup( tmp ) );
660                        break;
661                    }
662                }
663                tr_free( buf );
664            }
665
666            /* walk through the candidates & look for a match */
667            for( l=candidates; l; l=l->next ) {
668                char * path = tr_buildPath( l->data, "transmission", "web", NULL );
669                const int found = isWebClientDir( path );
670                if( found ) {
671                    s = path;
672                    break;
673                }
674                tr_free( path );
675            }
676
677            tr_list_free( &candidates, tr_free );
678
679#endif
680
681        }
682    }
683
684    return s;
685}
686
687/***
688****
689***/
690
691int64_t
692tr_getFreeSpace( const char * path )
693{
694#ifdef WIN32
695    uint64_t freeBytesAvailable = 0;
696    return GetDiskFreeSpaceEx( path, &freeBytesAvailable, NULL, NULL)
697        ? (int64_t)freeBytesAvailable
698        : -1;
699#elif defined(HAVE_STATVFS)
700    struct statvfs buf;
701    return statvfs( path, &buf ) ? -1 : (int64_t)buf.f_bavail * (int64_t)buf.f_frsize;
702#else
703    #warning FIXME: not implemented
704    return -1;
705#endif
706}
707
708/***
709****
710***/
711
712#ifdef WIN32
713
714/* The following mmap functions are by Joerg Walter, and were taken from
715 * his paper at: http://www.genesys-e.de/jwalter/mix4win.htm
716 */
717
718#if defined(_MSC_VER)
719__declspec( align( 4 ) ) static LONG volatile g_sl;
720#else
721static LONG volatile g_sl __attribute__ ( ( aligned ( 4 ) ) );
722#endif
723
724/* Wait for spin lock */
725static int
726slwait( LONG volatile *sl )
727{
728    while( InterlockedCompareExchange ( sl, 1, 0 ) != 0 )
729        Sleep ( 0 );
730
731    return 0;
732}
733
734/* Release spin lock */
735static int
736slrelease( LONG volatile *sl )
737{
738    InterlockedExchange ( sl, 0 );
739    return 0;
740}
741
742/* getpagesize for windows */
743static long
744getpagesize( void )
745{
746    static long g_pagesize = 0;
747
748    if( !g_pagesize )
749    {
750        SYSTEM_INFO system_info;
751        GetSystemInfo ( &system_info );
752        g_pagesize = system_info.dwPageSize;
753    }
754    return g_pagesize;
755}
756
757static long
758getregionsize( void )
759{
760    static long g_regionsize = 0;
761
762    if( !g_regionsize )
763    {
764        SYSTEM_INFO system_info;
765        GetSystemInfo ( &system_info );
766        g_regionsize = system_info.dwAllocationGranularity;
767    }
768    return g_regionsize;
769}
770
771void *
772mmap( void *ptr,
773      long  size,
774      long  prot,
775      long  type,
776      long  handle,
777      long  arg )
778{
779    static long g_pagesize;
780    static long g_regionsize;
781
782    /* Wait for spin lock */
783    slwait ( &g_sl );
784    /* First time initialization */
785    if( !g_pagesize )
786        g_pagesize = getpagesize ( );
787    if( !g_regionsize )
788        g_regionsize = getregionsize ( );
789    /* Allocate this */
790    ptr = VirtualAlloc ( ptr, size,
791                         MEM_RESERVE | MEM_COMMIT | MEM_TOP_DOWN,
792                         PAGE_READWRITE );
793    if( !ptr )
794    {
795        ptr = (void *) -1;
796        goto mmap_exit;
797    }
798mmap_exit:
799    /* Release spin lock */
800    slrelease ( &g_sl );
801    return ptr;
802}
803
804long
805munmap( void *ptr,
806        long  size )
807{
808    static long g_pagesize;
809    static long g_regionsize;
810    int         rc = -1;
811
812    /* Wait for spin lock */
813    slwait ( &g_sl );
814    /* First time initialization */
815    if( !g_pagesize )
816        g_pagesize = getpagesize ( );
817    if( !g_regionsize )
818        g_regionsize = getregionsize ( );
819    /* Free this */
820    if( !VirtualFree ( ptr, 0,
821                       MEM_RELEASE ) )
822        goto munmap_exit;
823    rc = 0;
824munmap_exit:
825    /* Release spin lock */
826    slrelease ( &g_sl );
827    return rc;
828}
829
830#endif
Note: See TracBrowser for help on using the repository browser.