source: trunk/libtransmission/platform.c @ 11627

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

(trunk, daemon) #3833 "'freespace' argument for 'session-get' RPC method" -- committing patch from taem, reardon, and rb07

  • Property svn:keywords set to Date Rev Author Id
File size: 19.6 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 11627 2011-01-05 04:41:19Z 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 tr_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
101tr_bool
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
372static void
373migrateFiles( const tr_session * session )
374{
375    static int migrated = FALSE;
376
377    if( !migrated )
378    {
379        const char * oldDir;
380        const char * newDir;
381        migrated = TRUE;
382
383        oldDir = getOldTorrentsDir( );
384        newDir = tr_getTorrentDir( session );
385        moveFiles( oldDir, newDir );
386
387        oldDir = getOldCacheDir( );
388        newDir = tr_getResumeDir( session );
389        moveFiles( oldDir, newDir );
390    }
391}
392
393void
394tr_setConfigDir( tr_session * session, const char * configDir )
395{
396    char * path;
397
398    session->configDir = tr_strdup( configDir );
399
400    path = tr_buildPath( configDir, RESUME_SUBDIR, NULL );
401    tr_mkdirp( path, 0777 );
402    session->resumeDir = path;
403
404    path = tr_buildPath( configDir, TORRENT_SUBDIR, NULL );
405    tr_mkdirp( path, 0777 );
406    session->torrentDir = path;
407
408    migrateFiles( session );
409}
410
411const char *
412tr_sessionGetConfigDir( const tr_session * session )
413{
414    return session->configDir;
415}
416
417const char *
418tr_getTorrentDir( const tr_session * session )
419{
420    return session->torrentDir;
421}
422
423const char *
424tr_getResumeDir( const tr_session * session )
425{
426    return session->resumeDir;
427}
428
429const char*
430tr_getDefaultConfigDir( const char * appname )
431{
432    static char * s = NULL;
433
434    if( !appname || !*appname )
435        appname = "Transmission";
436
437    if( !s )
438    {
439        if( ( s = getenv( "TRANSMISSION_HOME" ) ) )
440        {
441            s = tr_strdup( s );
442        }
443        else
444        {
445#ifdef SYS_DARWIN
446            s = tr_buildPath( getHomeDir( ), "Library", "Application Support",
447                              appname, NULL );
448#elif defined( WIN32 )
449            char appdata[TR_PATH_MAX]; /* SHGetFolderPath() requires MAX_PATH */
450            SHGetFolderPath( NULL, CSIDL_APPDATA, NULL, 0, appdata );
451            s = tr_buildPath( appdata, appname, NULL );
452#elif defined( __HAIKU__ )
453            char buf[TR_PATH_MAX];
454            find_directory( B_USER_SETTINGS_DIRECTORY, -1, true, buf, sizeof(buf) );
455            s = tr_buildPath( buf, appname, NULL );
456#else
457            if( ( s = getenv( "XDG_CONFIG_HOME" ) ) )
458                s = tr_buildPath( s, appname, NULL );
459            else
460                s = tr_buildPath( getHomeDir( ), ".config", appname, NULL );
461#endif
462        }
463    }
464
465    return s;
466}
467
468const char*
469tr_getDefaultDownloadDir( void )
470{
471    static char * user_dir = NULL;
472
473    if( user_dir == NULL )
474    {
475        const char * config_home;
476        char * config_file;
477        char * content;
478        size_t content_len;
479
480        /* figure out where to look for user-dirs.dirs */
481        config_home = getenv( "XDG_CONFIG_HOME" );
482        if( config_home && *config_home )
483            config_file = tr_buildPath( config_home, "user-dirs.dirs", NULL );
484        else
485            config_file = tr_buildPath( getHomeDir( ), ".config", "user-dirs.dirs", NULL );
486
487        /* read in user-dirs.dirs and look for the download dir entry */
488        content = (char *) tr_loadFile( config_file, &content_len );
489        if( content && content_len>0 )
490        {
491            const char * key = "XDG_DOWNLOAD_DIR=\"";
492            char * line = strstr( content, key );
493            if( line != NULL )
494            {
495                char * value = line + strlen( key );
496                char * end = strchr( value, '"' );
497
498                if( end )
499                {
500                    *end = '\0';
501
502                    if( !memcmp( value, "$HOME/", 6 ) )
503                        user_dir = tr_buildPath( getHomeDir( ), value+6, NULL );
504                    else
505                        user_dir = tr_strdup( value );
506                }
507            }
508        }
509
510        if( user_dir == NULL )
511#ifdef __HAIKU__
512            user_dir = tr_buildPath( getHomeDir( ), "Desktop", NULL );
513#else
514            user_dir = tr_buildPath( getHomeDir( ), "Downloads", NULL );
515#endif
516
517        tr_free( content );
518        tr_free( config_file );
519    }
520
521    return user_dir;
522}
523
524/***
525****
526***/
527
528static int
529isWebClientDir( const char * path )
530{
531    struct stat sb;
532    char * tmp = tr_buildPath( path, "index.html", NULL );
533    const int ret = !stat( tmp, &sb );
534    tr_inf( _( "Searching for web interface file \"%s\"" ), tmp );
535    tr_free( tmp );
536    return ret;
537}
538
539const char *
540tr_getWebClientDir( const tr_session * session UNUSED )
541{
542    static char * s = NULL;
543
544    if( !s )
545    {
546        if( ( s = getenv( "CLUTCH_HOME" ) ) )
547        {
548            s = tr_strdup( s );
549        }
550        else if( ( s = getenv( "TRANSMISSION_WEB_HOME" ) ) )
551        {
552            s = tr_strdup( s );
553        }
554        else
555        {
556
557#ifdef SYS_DARWIN /* on Mac, look in the Application Support folder first, then in the app bundle. */
558
559            /* Look in the Application Support folder */
560            s = tr_buildPath( tr_sessionGetConfigDir( session ), "web", NULL );
561
562            if( !isWebClientDir( s ) ) {
563                tr_free( s );
564
565                CFURLRef appURL = CFBundleCopyBundleURL( CFBundleGetMainBundle( ) );
566                CFStringRef appRef = CFURLCopyFileSystemPath( appURL,
567                                                              kCFURLPOSIXPathStyle );
568                CFIndex appLength = CFStringGetMaximumSizeForEncoding( CFStringGetLength(appRef),
569                                                                       CFStringGetFastestEncoding( appRef ));
570
571                char * appString = tr_malloc( appLength + 1 );
572                tr_bool success = CFStringGetCString( appRef,
573                                              appString,
574                                              appLength + 1,
575                                              CFStringGetFastestEncoding( appRef ));
576                assert( success );
577
578                CFRelease( appURL );
579                CFRelease( appRef );
580
581                /* Fallback to the app bundle */
582                s = tr_buildPath( appString, "Contents", "Resources", "web", NULL );
583                if( !isWebClientDir( s ) ) {
584                    tr_free( s );
585                    s = NULL;
586                }
587
588                tr_free( appString );
589            }
590
591#elif defined( WIN32 )
592
593            /* SHGetFolderPath explicitly requires MAX_PATH length */
594            char dir[MAX_PATH];
595
596            /* Generally, Web interface should be stored in a Web subdir of
597             * calling executable dir. */
598
599            if( s == NULL ) {
600                /* First, we should check personal AppData/Transmission/Web */
601                SHGetFolderPath( NULL, CSIDL_COMMON_APPDATA, NULL, 0, dir );
602                s = tr_buildPath( dir, "Transmission", "Web", NULL );
603                if( !isWebClientDir( s ) ) {
604                    tr_free( s );
605                    s = NULL;
606                }
607            }
608
609            if( s == NULL ) {
610                /* check personal AppData */
611                SHGetFolderPath( NULL, CSIDL_APPDATA, NULL, 0, dir );
612                s = tr_buildPath( dir, "Transmission", "Web", NULL );
613                if( !isWebClientDir( s ) ) {
614                    tr_free( s );
615                    s = NULL;
616                }
617            }
618
619            if( s == NULL) {
620                /* check calling module place */
621                GetModuleFileName( GetModuleHandle( NULL ), dir, sizeof( dir ) );
622                s = tr_buildPath( dirname( dir ), "Web", NULL );
623                if( !isWebClientDir( s ) ) {
624                    tr_free( s );
625                    s = NULL;
626                }
627            }
628
629#else /* everyone else, follow the XDG spec */
630
631            tr_list *candidates = NULL, *l;
632            const char * tmp;
633
634            /* XDG_DATA_HOME should be the first in the list of candidates */
635            tmp = getenv( "XDG_DATA_HOME" );
636            if( tmp && *tmp )
637                tr_list_append( &candidates, tr_strdup( tmp ) );
638            else {
639                char * dhome = tr_buildPath( getHomeDir( ), ".local", "share", NULL );
640                tr_list_append( &candidates, dhome );
641            }
642
643            /* XDG_DATA_DIRS are the backup directories */
644            {
645                const char * pkg = PACKAGE_DATA_DIR;
646                const char * xdg = getenv( "XDG_DATA_DIRS" );
647                const char * fallback = "/usr/local/share:/usr/share";
648                char * buf = tr_strdup_printf( "%s:%s:%s", (pkg?pkg:""), (xdg?xdg:""), fallback );
649                tmp = buf;
650                while( tmp && *tmp ) {
651                    const char * end = strchr( tmp, ':' );
652                    if( end ) {
653                        if( ( end - tmp ) > 1 )
654                            tr_list_append( &candidates, tr_strndup( tmp, end - tmp ) );
655                        tmp = end + 1;
656                    } else if( tmp && *tmp ) {
657                        tr_list_append( &candidates, tr_strdup( tmp ) );
658                        break;
659                    }
660                }
661                tr_free( buf );
662            }
663
664            /* walk through the candidates & look for a match */
665            for( l=candidates; l; l=l->next ) {
666                char * path = tr_buildPath( l->data, "transmission", "web", NULL );
667                const int found = isWebClientDir( path );
668                if( found ) {
669                    s = path;
670                    break;
671                }
672                tr_free( path );
673            }
674
675            tr_list_free( &candidates, tr_free );
676
677#endif
678
679        }
680    }
681
682    return s;
683}
684
685/***
686****
687***/
688
689int64_t
690tr_getFreeSpace( const char * path )
691{
692#ifdef WIN32
693    uint64_t freeBytesAvailable = 0;
694    return GetDiskFreeSpaceEx( path, &freeBytesAvailable, NULL, NULL)
695        ? (int64_t)freeBytesAvailable
696        : -1;
697#elif defined(HAVE_STATVFS)
698    struct statvfs buf;
699    return statvfs( path, &buf ) ? -1 : (int64_t)buf.f_bavail * (int64_t)buf.f_bsize;
700#else
701    #warning FIXME: not implemented
702    return -1;
703#endif
704}
705
706/***
707****
708***/
709
710#ifdef WIN32
711
712/* The following mmap functions are by Joerg Walter, and were taken from
713 * his paper at: http://www.genesys-e.de/jwalter/mix4win.htm
714 */
715
716#if defined(_MSC_VER)
717__declspec( align( 4 ) ) static LONG volatile g_sl;
718#else
719static LONG volatile g_sl __attribute__ ( ( aligned ( 4 ) ) );
720#endif
721
722/* Wait for spin lock */
723static int
724slwait( LONG volatile *sl )
725{
726    while( InterlockedCompareExchange ( sl, 1, 0 ) != 0 )
727        Sleep ( 0 );
728
729    return 0;
730}
731
732/* Release spin lock */
733static int
734slrelease( LONG volatile *sl )
735{
736    InterlockedExchange ( sl, 0 );
737    return 0;
738}
739
740/* getpagesize for windows */
741static long
742getpagesize( void )
743{
744    static long g_pagesize = 0;
745
746    if( !g_pagesize )
747    {
748        SYSTEM_INFO system_info;
749        GetSystemInfo ( &system_info );
750        g_pagesize = system_info.dwPageSize;
751    }
752    return g_pagesize;
753}
754
755static long
756getregionsize( void )
757{
758    static long g_regionsize = 0;
759
760    if( !g_regionsize )
761    {
762        SYSTEM_INFO system_info;
763        GetSystemInfo ( &system_info );
764        g_regionsize = system_info.dwAllocationGranularity;
765    }
766    return g_regionsize;
767}
768
769void *
770mmap( void *ptr,
771      long  size,
772      long  prot,
773      long  type,
774      long  handle,
775      long  arg )
776{
777    static long g_pagesize;
778    static long g_regionsize;
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    /* Allocate this */
788    ptr = VirtualAlloc ( ptr, size,
789                         MEM_RESERVE | MEM_COMMIT | MEM_TOP_DOWN,
790                         PAGE_READWRITE );
791    if( !ptr )
792    {
793        ptr = (void *) -1;
794        goto mmap_exit;
795    }
796mmap_exit:
797    /* Release spin lock */
798    slrelease ( &g_sl );
799    return ptr;
800}
801
802long
803munmap( void *ptr,
804        long  size )
805{
806    static long g_pagesize;
807    static long g_regionsize;
808    int         rc = -1;
809
810    /* Wait for spin lock */
811    slwait ( &g_sl );
812    /* First time initialization */
813    if( !g_pagesize )
814        g_pagesize = getpagesize ( );
815    if( !g_regionsize )
816        g_regionsize = getregionsize ( );
817    /* Free this */
818    if( !VirtualFree ( ptr, 0,
819                       MEM_RELEASE ) )
820        goto munmap_exit;
821    rc = 0;
822munmap_exit:
823    /* Release spin lock */
824    slrelease ( &g_sl );
825    return rc;
826}
827
828#endif
Note: See TracBrowser for help on using the repository browser.