source: trunk/libtransmission/platform.c @ 13631

Last change on this file since 13631 was 13631, checked in by jordan, 8 years ago

(trunk, libT) #5165: fix r13625 oops

  • Property svn:keywords set to Date Rev Author Id
File size: 18.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 13631 2012-12-07 01:53:31Z 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 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 *), void * arg)
129{
130  tr_thread * t = tr_new0 (tr_thread, 1);
131
132  t->func = func;
133  t->arg  = arg;
134
135#ifdef WIN32
136  {
137    unsigned int id;
138    t->thread_handle = (HANDLE) _beginthreadex (NULL, 0, &ThreadFunc, t, 0, &id);
139    t->thread = (DWORD) id;
140  }
141#else
142  pthread_create (&t->thread, NULL, (void* (*)(void*))ThreadFunc, t);
143  pthread_detach (t->thread);
144#endif
145
146  return t;
147}
148
149/***
150****  LOCKS
151***/
152
153/** @brief portability wrapper around OS-dependent thread mutexes */
154struct tr_lock
155{
156  int                 depth;
157#ifdef WIN32
158  CRITICAL_SECTION    lock;
159  DWORD               lockThread;
160#else
161  pthread_mutex_t     lock;
162  pthread_t           lockThread;
163#endif
164};
165
166tr_lock*
167tr_lockNew (void)
168{
169  tr_lock * l = tr_new0 (tr_lock, 1);
170
171#ifdef WIN32
172  InitializeCriticalSection (&l->lock); /* supports recursion */
173#else
174  pthread_mutexattr_t attr;
175  pthread_mutexattr_init (&attr);
176  pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE);
177  pthread_mutex_init (&l->lock, &attr);
178#endif
179
180  return l;
181}
182
183void
184tr_lockFree (tr_lock * l)
185{
186#ifdef WIN32
187    DeleteCriticalSection (&l->lock);
188#else
189    pthread_mutex_destroy (&l->lock);
190#endif
191    tr_free (l);
192}
193
194void
195tr_lockLock (tr_lock * l)
196{
197#ifdef WIN32
198  EnterCriticalSection (&l->lock);
199#else
200  pthread_mutex_lock (&l->lock);
201#endif
202
203  assert (l->depth >= 0);
204  assert (!l->depth || tr_areThreadsEqual (l->lockThread, tr_getCurrentThread ()));
205  l->lockThread = tr_getCurrentThread ();
206  ++l->depth;
207}
208
209int
210tr_lockHave (const tr_lock * l)
211{
212  return (l->depth > 0) &&
213         (tr_areThreadsEqual (l->lockThread, tr_getCurrentThread ()));
214}
215
216void
217tr_lockUnlock (tr_lock * l)
218{
219  assert (l->depth > 0);
220  assert (tr_areThreadsEqual (l->lockThread, tr_getCurrentThread ()));
221
222  --l->depth;
223  assert (l->depth >= 0);
224#ifdef WIN32
225  LeaveCriticalSection (&l->lock);
226#else
227  pthread_mutex_unlock (&l->lock);
228#endif
229}
230
231/***
232****  PATHS
233***/
234
235#ifndef WIN32
236 #include <pwd.h>
237#endif
238
239static const char *
240getHomeDir (void)
241{
242  static char * home = NULL;
243
244  if (!home)
245    {
246      home = tr_strdup (getenv ("HOME"));
247
248      if (!home)
249        {
250#ifdef WIN32
251          char appdata[MAX_PATH]; /* SHGetFolderPath () requires MAX_PATH */
252          *appdata = '\0';
253          SHGetFolderPath (NULL, CSIDL_PERSONAL, NULL, 0, appdata);
254          home = tr_strdup (appdata);
255#else
256          struct passwd * pw = getpwuid (getuid ());
257          if (pw)
258            home = tr_strdup (pw->pw_dir);
259          endpwent ();
260#endif
261        }
262
263      if (!home)
264        home = tr_strdup ("");
265    }
266
267  return home;
268}
269
270static const char *
271getOldConfigDir (void)
272{
273  static char * path = NULL;
274
275  if (!path)
276    {
277#ifdef SYS_DARWIN
278      path = tr_buildPath (getHomeDir (), "Library",
279                           "Application Support",
280                           "Transmission", NULL);
281#elif defined (WIN32)
282      char appdata[MAX_PATH]; /* SHGetFolderPath () requires MAX_PATH */
283      SHGetFolderPath (NULL, CSIDL_APPDATA, NULL, 0, appdata);
284      path = tr_buildPath (appdata, "Transmission", NULL);
285#elif defined (__HAIKU__)
286      char buf[TR_PATH_MAX];
287      find_directory (B_USER_SETTINGS_DIRECTORY, -1, true, buf, sizeof (buf));
288      path = tr_buildPath (buf, "Transmission", NULL);
289#else
290      path = tr_buildPath (getHomeDir (), ".transmission", NULL);
291#endif
292    }
293
294  return path;
295}
296
297#if defined (SYS_DARWIN) || defined (WIN32)
298 #define RESUME_SUBDIR  "Resume"
299 #define TORRENT_SUBDIR "Torrents"
300#else
301 #define RESUME_SUBDIR  "resume"
302 #define TORRENT_SUBDIR "torrents"
303#endif
304
305static const char *
306getOldTorrentsDir (void)
307{
308  static char * path = NULL;
309
310  if (!path)
311    path = tr_buildPath (getOldConfigDir (), TORRENT_SUBDIR, NULL);
312
313  return path;
314}
315
316static const char *
317getOldCacheDir (void)
318{
319  static char * path = NULL;
320
321  if (!path)
322    {
323#if defined (WIN32)
324      path = tr_buildPath (getOldConfigDir (), "Cache", NULL);
325#elif defined (SYS_DARWIN)
326      path = tr_buildPath (getHomeDir (), "Library", "Caches", "Transmission", NULL);
327#else
328      path = tr_buildPath (getOldConfigDir (), "cache", NULL);
329#endif
330    }
331
332  return path;
333}
334
335static void
336moveFiles (const char * oldDir, const char * newDir)
337{
338  if (oldDir && newDir && strcmp (oldDir, newDir))
339    {
340      DIR * dirh = opendir (oldDir);
341      if (dirh)
342        {
343          int count = 0;
344          struct dirent * dirp;
345          while ((dirp = readdir (dirh)))
346            {
347              const char * name = dirp->d_name;
348              if (name && strcmp (name, ".") && strcmp (name, ".."))
349                {
350                  char * o = tr_buildPath (oldDir, name, NULL);
351                  char * n = tr_buildPath (newDir, name, NULL);
352                  rename (o, n);
353                  ++count;
354                  tr_free (n);
355                  tr_free (o);
356                }
357            }
358
359          if (count)
360            tr_inf (_("Migrated %1$d files from \"%2$s\" to \"%3$s\""), count, oldDir, newDir);
361
362          closedir (dirh);
363        }
364    }
365}
366
367/**
368 * This function is for transmission-gtk users to migrate the config files
369 * from $HOME/.transmission/ (where they were kept before Transmission 1.30)
370 * to $HOME/.config/$appname as per the XDG directory spec.
371 */
372static void
373migrateFiles (const tr_session * session)
374{
375  static int migrated = false;
376  const bool should_migrate = strstr (getOldConfigDir (), ".transmission") != NULL;
377
378  if (!migrated && should_migrate)
379    {
380      const char * oldDir;
381      const char * newDir;
382
383      migrated = true;
384
385      oldDir = getOldTorrentsDir ();
386      newDir = tr_getTorrentDir (session);
387      moveFiles (oldDir, newDir);
388
389      oldDir = getOldCacheDir ();
390      newDir = tr_getResumeDir (session);
391      moveFiles (oldDir, newDir);
392    }
393}
394
395void
396tr_setConfigDir (tr_session * session, const char * configDir)
397{
398  char * path;
399
400  session->configDir = tr_strdup (configDir);
401
402  path = tr_buildPath (configDir, RESUME_SUBDIR, NULL);
403  tr_mkdirp (path, 0777);
404  session->resumeDir = path;
405
406  path = tr_buildPath (configDir, TORRENT_SUBDIR, NULL);
407  tr_mkdirp (path, 0777);
408  session->torrentDir = path;
409
410  migrateFiles (session);
411}
412
413const char *
414tr_sessionGetConfigDir (const tr_session * session)
415{
416  return session->configDir;
417}
418
419const char *
420tr_getTorrentDir (const tr_session * session)
421{
422  return session->torrentDir;
423}
424
425const char *
426tr_getResumeDir (const tr_session * session)
427{
428  return session->resumeDir;
429}
430
431const char*
432tr_getDefaultConfigDir (const char * appname)
433{
434  static char * s = NULL;
435
436  if (!appname || !*appname)
437    appname = "Transmission";
438
439  if (!s)
440    {
441      if ((s = getenv ("TRANSMISSION_HOME")))
442        {
443          s = tr_strdup (s);
444        }
445        else
446        {
447#ifdef SYS_DARWIN
448          s = tr_buildPath (getHomeDir (), "Library", "Application Support", appname, NULL);
449#elif defined (WIN32)
450          char appdata[TR_PATH_MAX]; /* SHGetFolderPath () requires MAX_PATH */
451          SHGetFolderPath (NULL, CSIDL_APPDATA, NULL, 0, appdata);
452          s = tr_buildPath (appdata, appname, NULL);
453#elif defined (__HAIKU__)
454          char buf[TR_PATH_MAX];
455          find_directory (B_USER_SETTINGS_DIRECTORY, -1, true, buf, sizeof (buf));
456          s = tr_buildPath (buf, appname, NULL);
457#else
458          if ((s = getenv ("XDG_CONFIG_HOME")))
459            s = tr_buildPath (s, appname, NULL);
460          else
461            s = tr_buildPath (getHomeDir (), ".config", appname, NULL);
462#endif
463        }
464    }
465
466  return s;
467}
468
469const char*
470tr_getDefaultDownloadDir (void)
471{
472  static char * user_dir = NULL;
473
474  if (user_dir == NULL)
475    {
476      const char * config_home;
477      char * config_file;
478      char * content;
479      size_t content_len;
480
481      /* figure out where to look for user-dirs.dirs */
482      config_home = getenv ("XDG_CONFIG_HOME");
483      if (config_home && *config_home)
484        config_file = tr_buildPath (config_home, "user-dirs.dirs", NULL);
485      else
486        config_file = tr_buildPath (getHomeDir (), ".config", "user-dirs.dirs", NULL);
487
488      /* read in user-dirs.dirs and look for the download dir entry */
489      content = (char *) tr_loadFile (config_file, &content_len);
490      if (content && content_len>0)
491        {
492          const char * key = "XDG_DOWNLOAD_DIR=\"";
493          char * line = strstr (content, key);
494          if (line != NULL)
495            {
496              char * value = line + strlen (key);
497              char * end = strchr (value, '"');
498
499              if (end)
500                {
501                  *end = '\0';
502
503                  if (!memcmp (value, "$HOME/", 6))
504                    user_dir = tr_buildPath (getHomeDir (), value+6, NULL);
505                  else
506                    user_dir = tr_strdup (value);
507                }
508            }
509        }
510
511      if (user_dir == NULL)
512#ifdef __HAIKU__
513        user_dir = tr_buildPath (getHomeDir (), "Desktop", NULL);
514#else
515        user_dir = tr_buildPath (getHomeDir (), "Downloads", NULL);
516#endif
517
518      tr_free (content);
519      tr_free (config_file);
520    }
521
522  return user_dir;
523}
524
525/***
526****
527***/
528
529static int
530isWebClientDir (const char * path)
531{
532  struct stat sb;
533  char * tmp = tr_buildPath (path, "index.html", NULL);
534  const int ret = !stat (tmp, &sb);
535  tr_inf (_("Searching for web interface file \"%s\""), tmp);
536  tr_free (tmp);
537
538  return ret;
539}
540
541const char *
542tr_getWebClientDir (const tr_session * session UNUSED)
543{
544  static char * s = NULL;
545
546  if (!s)
547    {
548      if ((s = getenv ("CLUTCH_HOME")))
549        {
550          s = tr_strdup (s);
551        }
552      else if ((s = getenv ("TRANSMISSION_WEB_HOME")))
553        {
554          s = tr_strdup (s);
555        }
556      else
557        {
558
559#ifdef SYS_DARWIN /* on Mac, look in the Application Support folder first, then in the app bundle. */
560
561          /* Look in the Application Support folder */
562          s = tr_buildPath (tr_sessionGetConfigDir (session), "web", NULL);
563
564          if (!isWebClientDir (s))
565            {
566              tr_free (s);
567
568              CFURLRef appURL = CFBundleCopyBundleURL (CFBundleGetMainBundle ());
569              CFStringRef appRef = CFURLCopyFileSystemPath (appURL,
570                                                            kCFURLPOSIXPathStyle);
571              const CFIndex appStringLength = CFStringGetMaximumSizeOfFileSystemRepresentation (appRef);
572
573              char * appString = tr_malloc (appStringLength);
574              const bool success = CFStringGetFileSystemRepresentation (appRef, appString, appStringLength);
575              assert (success);
576
577              CFRelease (appURL);
578              CFRelease (appRef);
579
580              /* Fallback to the app bundle */
581              s = tr_buildPath (appString, "Contents", "Resources", "web", NULL);
582              if (!isWebClientDir (s))
583                {
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) /* check personal AppData/Transmission/Web */
600            {
601              SHGetFolderPath (NULL, CSIDL_COMMON_APPDATA, NULL, 0, dir);
602              s = tr_buildPath (dir, "Transmission", "Web", NULL);
603              if (!isWebClientDir (s))
604                {
605                  tr_free (s);
606                  s = NULL;
607                }
608            }
609
610          if (s == NULL) /* check personal AppData */
611            {
612              SHGetFolderPath (NULL, CSIDL_APPDATA, NULL, 0, dir);
613              s = tr_buildPath (dir, "Transmission", "Web", NULL);
614              if (!isWebClientDir (s))
615                {
616                  tr_free (s);
617                  s = NULL;
618                }
619            }
620
621            if (s == NULL) /* check calling module place */
622              {
623                GetModuleFileName (GetModuleHandle (NULL), dir, sizeof (dir));
624                s = tr_buildPath (dirname (dir), "Web", NULL);
625                if (!isWebClientDir (s))
626                  {
627                    tr_free (s);
628                    s = NULL;
629                  }
630            }
631
632#else /* everyone else, follow the XDG spec */
633
634          tr_list *candidates = NULL, *l;
635          const char * tmp;
636
637          /* XDG_DATA_HOME should be the first in the list of candidates */
638          tmp = getenv ("XDG_DATA_HOME");
639          if (tmp && *tmp)
640            {
641              tr_list_append (&candidates, tr_strdup (tmp));
642            }
643          else
644            {
645              char * dhome = tr_buildPath (getHomeDir (), ".local", "share", NULL);
646              tr_list_append (&candidates, dhome);
647            }
648
649          /* XDG_DATA_DIRS are the backup directories */
650          {
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);
655            tmp = buf;
656            while (tmp && *tmp)
657              {
658                const char * end = strchr (tmp, ':');
659                if (end)
660                  {
661                    if ((end - tmp) > 1)
662                      tr_list_append (&candidates, tr_strndup (tmp, end - tmp));
663                    tmp = end + 1;
664                  }
665                else if (tmp && *tmp)
666                  {
667                    tr_list_append (&candidates, tr_strdup (tmp));
668                    break;
669                  }
670              }
671            tr_free (buf);
672          }
673
674          /* walk through the candidates & look for a match */
675          for (l=candidates; l; l=l->next)
676            {
677              char * path = tr_buildPath (l->data, "transmission", "web", NULL);
678              const int found = isWebClientDir (path);
679              if (found)
680                {
681                  s = path;
682                  break;
683                }
684              tr_free (path);
685            }
686
687          tr_list_free (&candidates, tr_free);
688
689#endif
690
691        }
692    }
693
694  return s;
695}
696
697/***
698****
699***/
700
701int64_t
702tr_getFreeSpace (const char * path)
703{
704#ifdef WIN32
705  uint64_t freeBytesAvailable = 0;
706  return GetDiskFreeSpaceEx (path, &freeBytesAvailable, NULL, NULL)
707    ? (int64_t)freeBytesAvailable
708    : -1;
709#elif defined (HAVE_STATVFS)
710  struct statvfs buf;
711  return statvfs (path, &buf) ? -1 : (int64_t)buf.f_bavail * (int64_t)buf.f_frsize;
712#else
713  #warning FIXME: not implemented
714  return -1;
715#endif
716}
717
718/***
719****
720***/
721
722#ifdef WIN32
723
724/* The following mmap functions are by Joerg Walter, and were taken from
725 * his paper at: http://www.genesys-e.de/jwalter/mix4win.htm */
726
727#if defined (_MSC_VER)
728__declspec (align (4)) static LONG volatile g_sl;
729#else
730static LONG volatile g_sl __attribute__((aligned (4)));
731#endif
732
733/* Wait for spin lock */
734static int
735slwait (LONG volatile *sl)
736{
737  while (InterlockedCompareExchange (sl, 1, 0) != 0)
738    Sleep (0);
739
740  return 0;
741}
742
743/* Release spin lock */
744static int
745slrelease (LONG volatile *sl)
746{
747  InterlockedExchange (sl, 0);
748  return 0;
749}
750
751/* getpagesize for windows */
752static long
753getpagesize (void)
754{
755  static long g_pagesize = 0;
756
757  if (!g_pagesize)
758    {
759      SYSTEM_INFO system_info;
760      GetSystemInfo (&system_info);
761      g_pagesize = system_info.dwPageSize;
762    }
763
764  return g_pagesize;
765}
766
767static long
768getregionsize (void)
769{
770  static long g_regionsize = 0;
771
772  if (!g_regionsize)
773    {
774      SYSTEM_INFO system_info;
775      GetSystemInfo (&system_info);
776      g_regionsize = system_info.dwAllocationGranularity;
777    }
778
779  return g_regionsize;
780}
781
782void *
783mmap (void *ptr, long  size, long  prot, long  type, long  handle, long  arg)
784{
785  static long g_pagesize;
786  static long g_regionsize;
787
788  /* Wait for spin lock */
789  slwait (&g_sl);
790
791  /* First time initialization */
792  if (!g_pagesize)
793    g_pagesize = getpagesize ();
794  if (!g_regionsize)
795    g_regionsize = getregionsize ();
796
797  /* Allocate this */
798  ptr = VirtualAlloc (ptr, size, MEM_RESERVE | MEM_COMMIT | MEM_TOP_DOWN, PAGE_READWRITE);
799  if (!ptr)
800    {
801      ptr = (void *) -1;
802      goto mmap_exit;
803    }
804
805mmap_exit:
806  /* Release spin lock */
807  slrelease (&g_sl);
808  return ptr;
809}
810
811long
812munmap (void *ptr, long size)
813{
814  static long g_pagesize;
815  static long g_regionsize;
816  int rc = -1;
817
818  /* Wait for spin lock */
819  slwait (&g_sl);
820
821  /* First time initialization */
822  if (!g_pagesize)
823    g_pagesize = getpagesize ();
824  if (!g_regionsize)
825    g_regionsize = getregionsize ();
826
827  /* Free this */
828  if (!VirtualFree (ptr, 0, MEM_RELEASE))
829    goto munmap_exit;
830
831  rc = 0;
832
833munmap_exit:
834  /* Release spin lock */
835  slrelease (&g_sl);
836  return rc;
837}
838
839#endif
Note: See TracBrowser for help on using the repository browser.