source: trunk/libtransmission/platform.c @ 13690

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

(trunk, libT) #5184 'Transmission 2.73 keeps creating a directory called /home/charles' -- fixed.

  • Property svn:keywords set to Date Rev Author Id
File size: 18.7 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 13690 2012-12-26 23:44:33Z 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 if (!strcmp (value, "$HOME"))
506                    user_dir = tr_strdup (getHomeDir ());
507                  else
508                    user_dir = tr_strdup (value);
509                }
510            }
511        }
512
513      if (user_dir == NULL)
514#ifdef __HAIKU__
515        user_dir = tr_buildPath (getHomeDir (), "Desktop", NULL);
516#else
517        user_dir = tr_buildPath (getHomeDir (), "Downloads", NULL);
518#endif
519
520      tr_free (content);
521      tr_free (config_file);
522    }
523
524  return user_dir;
525}
526
527/***
528****
529***/
530
531static int
532isWebClientDir (const char * path)
533{
534  struct stat sb;
535  char * tmp = tr_buildPath (path, "index.html", NULL);
536  const int ret = !stat (tmp, &sb);
537  tr_inf (_("Searching for web interface file \"%s\""), tmp);
538  tr_free (tmp);
539
540  return ret;
541}
542
543const char *
544tr_getWebClientDir (const tr_session * session UNUSED)
545{
546  static char * s = NULL;
547
548  if (!s)
549    {
550      if ((s = getenv ("CLUTCH_HOME")))
551        {
552          s = tr_strdup (s);
553        }
554      else if ((s = getenv ("TRANSMISSION_WEB_HOME")))
555        {
556          s = tr_strdup (s);
557        }
558      else
559        {
560
561#ifdef SYS_DARWIN /* on Mac, look in the Application Support folder first, then in the app bundle. */
562
563          /* Look in the Application Support folder */
564          s = tr_buildPath (tr_sessionGetConfigDir (session), "web", NULL);
565
566          if (!isWebClientDir (s))
567            {
568              tr_free (s);
569
570              CFURLRef appURL = CFBundleCopyBundleURL (CFBundleGetMainBundle ());
571              CFStringRef appRef = CFURLCopyFileSystemPath (appURL,
572                                                            kCFURLPOSIXPathStyle);
573              const CFIndex appStringLength = CFStringGetMaximumSizeOfFileSystemRepresentation (appRef);
574
575              char * appString = tr_malloc (appStringLength);
576              const bool success = CFStringGetFileSystemRepresentation (appRef, appString, appStringLength);
577              assert (success);
578
579              CFRelease (appURL);
580              CFRelease (appRef);
581
582              /* Fallback to the app bundle */
583              s = tr_buildPath (appString, "Contents", "Resources", "web", NULL);
584              if (!isWebClientDir (s))
585                {
586                  tr_free (s);
587                  s = NULL;
588                }
589
590              tr_free (appString);
591            }
592
593#elif defined (WIN32)
594
595          /* SHGetFolderPath explicitly requires MAX_PATH length */
596          char dir[MAX_PATH];
597
598          /* Generally, Web interface should be stored in a Web subdir of
599           * calling executable dir. */
600
601          if (s == NULL) /* check personal AppData/Transmission/Web */
602            {
603              SHGetFolderPath (NULL, CSIDL_COMMON_APPDATA, NULL, 0, dir);
604              s = tr_buildPath (dir, "Transmission", "Web", NULL);
605              if (!isWebClientDir (s))
606                {
607                  tr_free (s);
608                  s = NULL;
609                }
610            }
611
612          if (s == NULL) /* check personal AppData */
613            {
614              SHGetFolderPath (NULL, CSIDL_APPDATA, NULL, 0, dir);
615              s = tr_buildPath (dir, "Transmission", "Web", NULL);
616              if (!isWebClientDir (s))
617                {
618                  tr_free (s);
619                  s = NULL;
620                }
621            }
622
623            if (s == NULL) /* check calling module place */
624              {
625                GetModuleFileName (GetModuleHandle (NULL), dir, sizeof (dir));
626                s = tr_buildPath (dirname (dir), "Web", NULL);
627                if (!isWebClientDir (s))
628                  {
629                    tr_free (s);
630                    s = NULL;
631                  }
632            }
633
634#else /* everyone else, follow the XDG spec */
635
636          tr_list *candidates = NULL, *l;
637          const char * tmp;
638
639          /* XDG_DATA_HOME should be the first in the list of candidates */
640          tmp = getenv ("XDG_DATA_HOME");
641          if (tmp && *tmp)
642            {
643              tr_list_append (&candidates, tr_strdup (tmp));
644            }
645          else
646            {
647              char * dhome = tr_buildPath (getHomeDir (), ".local", "share", NULL);
648              tr_list_append (&candidates, dhome);
649            }
650
651          /* XDG_DATA_DIRS are the backup directories */
652          {
653            const char * pkg = PACKAGE_DATA_DIR;
654            const char * xdg = getenv ("XDG_DATA_DIRS");
655            const char * fallback = "/usr/local/share:/usr/share";
656            char * buf = tr_strdup_printf ("%s:%s:%s", (pkg?pkg:""), (xdg?xdg:""), fallback);
657            tmp = buf;
658            while (tmp && *tmp)
659              {
660                const char * end = strchr (tmp, ':');
661                if (end)
662                  {
663                    if ((end - tmp) > 1)
664                      tr_list_append (&candidates, tr_strndup (tmp, end - tmp));
665                    tmp = end + 1;
666                  }
667                else if (tmp && *tmp)
668                  {
669                    tr_list_append (&candidates, tr_strdup (tmp));
670                    break;
671                  }
672              }
673            tr_free (buf);
674          }
675
676          /* walk through the candidates & look for a match */
677          for (l=candidates; l; l=l->next)
678            {
679              char * path = tr_buildPath (l->data, "transmission", "web", NULL);
680              const int found = isWebClientDir (path);
681              if (found)
682                {
683                  s = path;
684                  break;
685                }
686              tr_free (path);
687            }
688
689          tr_list_free (&candidates, tr_free);
690
691#endif
692
693        }
694    }
695
696  return s;
697}
698
699/***
700****
701***/
702
703int64_t
704tr_getFreeSpace (const char * path)
705{
706#ifdef WIN32
707  uint64_t freeBytesAvailable = 0;
708  return GetDiskFreeSpaceEx (path, &freeBytesAvailable, NULL, NULL)
709    ? (int64_t)freeBytesAvailable
710    : -1;
711#elif defined (HAVE_STATVFS)
712  struct statvfs buf;
713  return statvfs (path, &buf) ? -1 : (int64_t)buf.f_bavail * (int64_t)buf.f_frsize;
714#else
715  #warning FIXME: not implemented
716  return -1;
717#endif
718}
719
720/***
721****
722***/
723
724#ifdef WIN32
725
726/* The following mmap functions are by Joerg Walter, and were taken from
727 * his paper at: http://www.genesys-e.de/jwalter/mix4win.htm */
728
729#if defined (_MSC_VER)
730__declspec (align (4)) static LONG volatile g_sl;
731#else
732static LONG volatile g_sl __attribute__((aligned (4)));
733#endif
734
735/* Wait for spin lock */
736static int
737slwait (LONG volatile *sl)
738{
739  while (InterlockedCompareExchange (sl, 1, 0) != 0)
740    Sleep (0);
741
742  return 0;
743}
744
745/* Release spin lock */
746static int
747slrelease (LONG volatile *sl)
748{
749  InterlockedExchange (sl, 0);
750  return 0;
751}
752
753/* getpagesize for windows */
754static long
755getpagesize (void)
756{
757  static long g_pagesize = 0;
758
759  if (!g_pagesize)
760    {
761      SYSTEM_INFO system_info;
762      GetSystemInfo (&system_info);
763      g_pagesize = system_info.dwPageSize;
764    }
765
766  return g_pagesize;
767}
768
769static long
770getregionsize (void)
771{
772  static long g_regionsize = 0;
773
774  if (!g_regionsize)
775    {
776      SYSTEM_INFO system_info;
777      GetSystemInfo (&system_info);
778      g_regionsize = system_info.dwAllocationGranularity;
779    }
780
781  return g_regionsize;
782}
783
784void *
785mmap (void *ptr, long  size, long  prot, long  type, long  handle, long  arg)
786{
787  static long g_pagesize;
788  static long g_regionsize;
789
790  /* Wait for spin lock */
791  slwait (&g_sl);
792
793  /* First time initialization */
794  if (!g_pagesize)
795    g_pagesize = getpagesize ();
796  if (!g_regionsize)
797    g_regionsize = getregionsize ();
798
799  /* Allocate this */
800  ptr = VirtualAlloc (ptr, size, MEM_RESERVE | MEM_COMMIT | MEM_TOP_DOWN, PAGE_READWRITE);
801  if (!ptr)
802    {
803      ptr = (void *) -1;
804      goto mmap_exit;
805    }
806
807mmap_exit:
808  /* Release spin lock */
809  slrelease (&g_sl);
810  return ptr;
811}
812
813long
814munmap (void *ptr, long size)
815{
816  static long g_pagesize;
817  static long g_regionsize;
818  int rc = -1;
819
820  /* Wait for spin lock */
821  slwait (&g_sl);
822
823  /* First time initialization */
824  if (!g_pagesize)
825    g_pagesize = getpagesize ();
826  if (!g_regionsize)
827    g_regionsize = getregionsize ();
828
829  /* Free this */
830  if (!VirtualFree (ptr, 0, MEM_RELEASE))
831    goto munmap_exit;
832
833  rc = 0;
834
835munmap_exit:
836  /* Release spin lock */
837  slrelease (&g_sl);
838  return rc;
839}
840
841#endif
Note: See TracBrowser for help on using the repository browser.