source: trunk/libtransmission/platform.c @ 10396

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

(trunk libT) #2911 "requirement of javascript/transmission.js for webinterface unnecessary" -- implemented in trunk for 2.00

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