source: trunk/libtransmission/platform.c @ 11709

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

Update the copyright year in the source code comments.

The Berne Convention says that the copyright year is moot, so instead of adding another year to each file as in previous years, I've removed the year altogether from the source code comments in libtransmission, gtk, qt, utils, daemon, and cli.

Juliusz's copyright notice in tr-dht and Johannes' copyright notice in tr-lpd have been left alone; it didn't seem appropriate to modify them.

  • Property svn:keywords set to Date Rev Author Id
File size: 19.6 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 11709 2011-01-19 13:48:47Z 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.