2 * This file Copyright (C) Mnemosyne LLC
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.
10 * $Id: platform.c 11785 2011-01-30 01:33:53Z jordan $
15 #define WINVER WindowsXP
17 #include <shlobj.h> /* for CSIDL_APPDATA, CSIDL_MYDOCUMENTS */
20 #include <CoreFoundation/CoreFoundation.h>
23 #include <FindDirectory.h>
25 #define _XOPEN_SOURCE 600 /* needed for recursive locks. */
27 #define __USE_UNIX98 /* some older Linuxes need it spelt out for them */
38 #define HAVE_SYS_STATVFS_H
43 #include <sys/types.h>
44 #ifdef HAVE_SYS_STATVFS_H
45 #include <sys/statvfs.h>
52 #include <unistd.h> /* getuid getpid close */
54 #include "transmission.h"
65 typedef DWORD tr_thread_id
;
67 typedef pthread_t tr_thread_id
;
71 tr_getCurrentThread( void )
74 return GetCurrentThreadId( );
76 return pthread_self( );
81 tr_areThreadsEqual( tr_thread_id a
, tr_thread_id b
)
86 return pthread_equal( a
, b
) != 0;
90 /** @brief portability wrapper around OS-dependent threads */
93 void ( * func
)( void * );
102 tr_amInThread( const tr_thread
* t
)
104 return tr_areThreadsEqual( tr_getCurrentThread( ), t
->thread
);
108 #define ThreadFuncReturnType unsigned WINAPI
110 #define ThreadFuncReturnType void
113 static ThreadFuncReturnType
114 ThreadFunc( void * _t
)
128 tr_threadNew( void ( *func
)(void *),
131 tr_thread
* t
= tr_new0( tr_thread
, 1 );
140 (HANDLE
) _beginthreadex( NULL
, 0, &ThreadFunc
, t
, 0,
142 t
->thread
= (DWORD
) id
;
145 pthread_create( &t
->thread
, NULL
, (void*(*)(void*))ThreadFunc
, t
);
146 pthread_detach( t
->thread
);
157 /** @brief portability wrapper around OS-dependent thread mutexes */
162 CRITICAL_SECTION lock
;
165 pthread_mutex_t lock
;
166 pthread_t lockThread
;
173 tr_lock
* l
= tr_new0( tr_lock
, 1 );
176 InitializeCriticalSection( &l
->lock
); /* supports recursion */
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
);
188 tr_lockFree( tr_lock
* l
)
191 DeleteCriticalSection( &l
->lock
);
193 pthread_mutex_destroy( &l
->lock
);
199 tr_lockLock( tr_lock
* l
)
202 EnterCriticalSection( &l
->lock
);
204 pthread_mutex_lock( &l
->lock
);
206 assert( l
->depth
>= 0 );
208 assert( tr_areThreadsEqual( l
->lockThread
, tr_getCurrentThread( ) ) );
209 l
->lockThread
= tr_getCurrentThread( );
214 tr_lockHave( const tr_lock
* l
)
216 return ( l
->depth
> 0 )
217 && ( tr_areThreadsEqual( l
->lockThread
, tr_getCurrentThread( ) ) );
221 tr_lockUnlock( tr_lock
* l
)
223 assert( l
->depth
> 0 );
224 assert( tr_areThreadsEqual( l
->lockThread
, tr_getCurrentThread( ) ) );
227 assert( l
->depth
>= 0 );
229 LeaveCriticalSection( &l
->lock
);
231 pthread_mutex_unlock( &l
->lock
);
246 static char * home
= NULL
;
250 home
= tr_strdup( getenv( "HOME" ) );
255 char appdata
[MAX_PATH
]; /* SHGetFolderPath() requires MAX_PATH */
257 SHGetFolderPath( NULL
, CSIDL_PERSONAL
, NULL
, 0, appdata
);
258 home
= tr_strdup( appdata
);
260 struct passwd
* pw
= getpwuid( getuid( ) );
262 home
= tr_strdup( pw
->pw_dir
);
268 home
= tr_strdup( "" );
275 getOldConfigDir( void )
277 static char * path
= NULL
;
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
);
294 path
= tr_buildPath( getHomeDir( ), ".transmission", NULL
);
301 #if defined(SYS_DARWIN) || defined(WIN32)
302 #define RESUME_SUBDIR "Resume"
303 #define TORRENT_SUBDIR "Torrents"
305 #define RESUME_SUBDIR "resume"
306 #define TORRENT_SUBDIR "torrents"
310 getOldTorrentsDir( void )
312 static char * path
= NULL
;
315 path
= tr_buildPath( getOldConfigDir( ), TORRENT_SUBDIR
, NULL
);
321 getOldCacheDir( void )
323 static char * path
= NULL
;
328 path
= tr_buildPath( getOldConfigDir( ), "Cache", NULL
);
329 #elif defined( SYS_DARWIN )
330 path
= tr_buildPath( getHomeDir( ), "Library", "Caches", "Transmission", NULL
);
332 path
= tr_buildPath( getOldConfigDir( ), "cache", NULL
);
340 moveFiles( const char * oldDir
,
341 const char * newDir
)
343 if( oldDir
&& newDir
&& strcmp( oldDir
, newDir
) )
345 DIR * dirh
= opendir( oldDir
);
349 struct dirent
* dirp
;
350 while( ( dirp
= readdir( dirh
) ) )
352 const char * name
= dirp
->d_name
;
353 if( name
&& strcmp( name
, "." ) && strcmp( name
, ".." ) )
355 char * o
= tr_buildPath( oldDir
, name
, NULL
);
356 char * n
= tr_buildPath( newDir
, name
, NULL
);
365 tr_inf( _( "Migrated %1$d files from \"%2$s\" to \"%3$s\"" ),
366 count
, oldDir
, newDir
);
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.
378 migrateFiles( const tr_session
* session
)
380 static int migrated
= FALSE
;
381 const tr_bool should_migrate
= strstr( getOldConfigDir(), ".transmission" ) != NULL
;
383 if( !migrated
&& should_migrate
)
389 oldDir
= getOldTorrentsDir( );
390 newDir
= tr_getTorrentDir( session
);
391 moveFiles( oldDir
, newDir
);
393 oldDir
= getOldCacheDir( );
394 newDir
= tr_getResumeDir( session
);
395 moveFiles( oldDir
, newDir
);
400 tr_setConfigDir( tr_session
* session
, const char * configDir
)
404 session
->configDir
= tr_strdup( configDir
);
406 path
= tr_buildPath( configDir
, RESUME_SUBDIR
, NULL
);
407 tr_mkdirp( path
, 0777 );
408 session
->resumeDir
= path
;
410 path
= tr_buildPath( configDir
, TORRENT_SUBDIR
, NULL
);
411 tr_mkdirp( path
, 0777 );
412 session
->torrentDir
= path
;
414 migrateFiles( session
);
418 tr_sessionGetConfigDir( const tr_session
* session
)
420 return session
->configDir
;
424 tr_getTorrentDir( const tr_session
* session
)
426 return session
->torrentDir
;
430 tr_getResumeDir( const tr_session
* session
)
432 return session
->resumeDir
;
436 tr_getDefaultConfigDir( const char * appname
)
438 static char * s
= NULL
;
440 if( !appname
|| !*appname
)
441 appname
= "Transmission";
445 if( ( s
= getenv( "TRANSMISSION_HOME" ) ) )
452 s
= tr_buildPath( getHomeDir( ), "Library", "Application Support",
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
);
463 if( ( s
= getenv( "XDG_CONFIG_HOME" ) ) )
464 s
= tr_buildPath( s
, appname
, NULL
);
466 s
= tr_buildPath( getHomeDir( ), ".config", appname
, NULL
);
475 tr_getDefaultDownloadDir( void )
477 static char * user_dir
= NULL
;
479 if( user_dir
== NULL
)
481 const char * config_home
;
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
);
491 config_file
= tr_buildPath( getHomeDir( ), ".config", "user-dirs.dirs", NULL
);
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 )
497 const char * key
= "XDG_DOWNLOAD_DIR=\"";
498 char * line
= strstr( content
, key
);
501 char * value
= line
+ strlen( key
);
502 char * end
= strchr( value
, '"' );
508 if( !memcmp( value
, "$HOME/", 6 ) )
509 user_dir
= tr_buildPath( getHomeDir( ), value
+6, NULL
);
511 user_dir
= tr_strdup( value
);
516 if( user_dir
== NULL
)
518 user_dir
= tr_buildPath( getHomeDir( ), "Desktop", NULL
);
520 user_dir
= tr_buildPath( getHomeDir( ), "Downloads", NULL
);
524 tr_free( config_file
);
535 isWebClientDir( const char * path
)
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
);
546 tr_getWebClientDir( const tr_session
* session UNUSED
)
548 static char * s
= NULL
;
552 if( ( s
= getenv( "CLUTCH_HOME" ) ) )
556 else if( ( s
= getenv( "TRANSMISSION_WEB_HOME" ) ) )
563 #ifdef SYS_DARWIN /* on Mac, look in the Application Support folder first, then in the app bundle. */
565 /* Look in the Application Support folder */
566 s
= tr_buildPath( tr_sessionGetConfigDir( session
), "web", NULL
);
568 if( !isWebClientDir( s
) ) {
571 CFURLRef appURL
= CFBundleCopyBundleURL( CFBundleGetMainBundle( ) );
572 CFStringRef appRef
= CFURLCopyFileSystemPath( appURL
,
573 kCFURLPOSIXPathStyle
);
574 CFIndex appLength
= CFStringGetMaximumSizeForEncoding( CFStringGetLength(appRef
),
575 CFStringGetFastestEncoding( appRef
));
577 char * appString
= tr_malloc( appLength
+ 1 );
578 tr_bool success
= CFStringGetCString( appRef
,
581 CFStringGetFastestEncoding( appRef
));
587 /* Fallback to the app bundle */
588 s
= tr_buildPath( appString
, "Contents", "Resources", "web", NULL
);
589 if( !isWebClientDir( s
) ) {
594 tr_free( appString
);
597 #elif defined( WIN32 )
599 /* SHGetFolderPath explicitly requires MAX_PATH length */
602 /* Generally, Web interface should be stored in a Web subdir of
603 * calling executable dir. */
606 /* First, we should check personal AppData/Transmission/Web */
607 SHGetFolderPath( NULL
, CSIDL_COMMON_APPDATA
, NULL
, 0, dir
);
608 s
= tr_buildPath( dir
, "Transmission", "Web", NULL
);
609 if( !isWebClientDir( s
) ) {
616 /* check personal AppData */
617 SHGetFolderPath( NULL
, CSIDL_APPDATA
, NULL
, 0, dir
);
618 s
= tr_buildPath( dir
, "Transmission", "Web", NULL
);
619 if( !isWebClientDir( s
) ) {
626 /* check calling module place */
627 GetModuleFileName( GetModuleHandle( NULL
), dir
, sizeof( dir
) );
628 s
= tr_buildPath( dirname( dir
), "Web", NULL
);
629 if( !isWebClientDir( s
) ) {
635 #else /* everyone else, follow the XDG spec */
637 tr_list
*candidates
= NULL
, *l
;
640 /* XDG_DATA_HOME should be the first in the list of candidates */
641 tmp
= getenv( "XDG_DATA_HOME" );
643 tr_list_append( &candidates
, tr_strdup( tmp
) );
645 char * dhome
= tr_buildPath( getHomeDir( ), ".local", "share", NULL
);
646 tr_list_append( &candidates
, dhome
);
649 /* XDG_DATA_DIRS are the backup directories */
651 const char * pkg
= PACKAGE_DATA_DIR
;
652 const char * xdg
= getenv( "XDG_DATA_DIRS" );
653 const char * fallback
= "/usr/local/share:/usr/share";
654 char * buf
= tr_strdup_printf( "%s:%s:%s", (pkg
?pkg
:""), (xdg
?xdg
:""), fallback
);
656 while( tmp
&& *tmp
) {
657 const char * end
= strchr( tmp
, ':' );
659 if( ( end
- tmp
) > 1 )
660 tr_list_append( &candidates
, tr_strndup( tmp
, end
- tmp
) );
662 } else if( tmp
&& *tmp
) {
663 tr_list_append( &candidates
, tr_strdup( tmp
) );
670 /* walk through the candidates & look for a match */
671 for( l
=candidates
; l
; l
=l
->next
) {
672 char * path
= tr_buildPath( l
->data
, "transmission", "web", NULL
);
673 const int found
= isWebClientDir( path
);
681 tr_list_free( &candidates
, tr_free
);
696 tr_getFreeSpace( const char * path
)
699 uint64_t freeBytesAvailable
= 0;
700 return GetDiskFreeSpaceEx( path
, &freeBytesAvailable
, NULL
, NULL
)
701 ? (int64_t)freeBytesAvailable
703 #elif defined(HAVE_STATVFS)
705 return statvfs( path
, &buf
) ? -1 : (int64_t)buf
.f_bavail
* (int64_t)buf
.f_bsize
;
707 #warning FIXME: not implemented
718 /* The following mmap functions are by Joerg Walter, and were taken from
719 * his paper at: http://www.genesys-e.de/jwalter/mix4win.htm
722 #if defined(_MSC_VER)
723 __declspec( align( 4 ) ) static LONG
volatile g_sl
;
725 static LONG
volatile g_sl
__attribute__ ( ( aligned ( 4 ) ) );
728 /* Wait for spin lock */
730 slwait( LONG
volatile *sl
)
732 while( InterlockedCompareExchange ( sl
, 1, 0 ) != 0 )
738 /* Release spin lock */
740 slrelease( LONG
volatile *sl
)
742 InterlockedExchange ( sl
, 0 );
746 /* getpagesize for windows */
750 static long g_pagesize
= 0;
754 SYSTEM_INFO system_info
;
755 GetSystemInfo ( &system_info
);
756 g_pagesize
= system_info
.dwPageSize
;
762 getregionsize( void )
764 static long g_regionsize
= 0;
768 SYSTEM_INFO system_info
;
769 GetSystemInfo ( &system_info
);
770 g_regionsize
= system_info
.dwAllocationGranularity
;
783 static long g_pagesize
;
784 static long g_regionsize
;
786 /* Wait for spin lock */
788 /* First time initialization */
790 g_pagesize
= getpagesize ( );
792 g_regionsize
= getregionsize ( );
794 ptr
= VirtualAlloc ( ptr
, size
,
795 MEM_RESERVE
| MEM_COMMIT
| MEM_TOP_DOWN
,
803 /* Release spin lock */
812 static long g_pagesize
;
813 static long g_regionsize
;
816 /* Wait for spin lock */
818 /* First time initialization */
820 g_pagesize
= getpagesize ( );
822 g_regionsize
= getregionsize ( );
824 if( !VirtualFree ( ptr
, 0,
829 /* Release spin lock */