1 /*
2  * This file Copyright (C) 2009-2014 Mnemosyne LLC
3  *
4  * It may be used under the GNU GPL versions 2 or 3
5  * or any future license endorsed by Mnemosyne LLC.
6  *
7  */
8 
9 #define _XOPEN_SOURCE 600 /* needed for recursive locks. */
10 #ifndef __USE_UNIX98
11 #define __USE_UNIX98 /* some older Linuxes need it spelt out for them */
12 #endif
13 
14 #include <stdlib.h>
15 #include <string.h>
16 
17 #ifdef __HAIKU__
18 #include <limits.h> /* PATH_MAX */
19 #endif
20 
21 #ifdef _WIN32
22 #include <process.h> /* _beginthreadex(), _endthreadex() */
23 #include <windows.h>
24 #include <shlobj.h> /* SHGetKnownFolderPath(), FOLDERID_... */
25 #else
26 #include <unistd.h> /* getuid() */
27 #ifdef BUILD_MAC_CLIENT
28 #include <CoreFoundation/CoreFoundation.h>
29 #endif
30 #ifdef __HAIKU__
31 #include <FindDirectory.h>
32 #endif
33 #include <pthread.h>
34 #endif
35 
36 #include "transmission.h"
37 #include "file.h"
38 #include "list.h"
39 #include "log.h"
40 #include "platform.h"
41 #include "session.h"
42 #include "tr-assert.h"
43 #include "utils.h"
44 
45 /***
46 ****  THREADS
47 ***/
48 
49 #ifdef _WIN32
50 typedef DWORD tr_thread_id;
51 #else
52 typedef pthread_t tr_thread_id;
53 #endif
54 
tr_getCurrentThread(void)55 static tr_thread_id tr_getCurrentThread(void)
56 {
57 #ifdef _WIN32
58     return GetCurrentThreadId();
59 #else
60     return pthread_self();
61 #endif
62 }
63 
tr_areThreadsEqual(tr_thread_id a,tr_thread_id b)64 static bool tr_areThreadsEqual(tr_thread_id a, tr_thread_id b)
65 {
66 #ifdef _WIN32
67     return a == b;
68 #else
69     return pthread_equal(a, b) != 0;
70 #endif
71 }
72 
73 /** @brief portability wrapper around OS-dependent threads */
74 struct tr_thread
75 {
76     void (* func)(void*);
77     void* arg;
78     tr_thread_id thread;
79 
80 #ifdef _WIN32
81     HANDLE thread_handle;
82 #endif
83 };
84 
tr_amInThread(tr_thread const * t)85 bool tr_amInThread(tr_thread const* t)
86 {
87     return tr_areThreadsEqual(tr_getCurrentThread(), t->thread);
88 }
89 
90 #ifdef _WIN32
91 #define ThreadFuncReturnType unsigned WINAPI
92 #else
93 #define ThreadFuncReturnType void*
94 #endif
95 
ThreadFunc(void * _t)96 static ThreadFuncReturnType ThreadFunc(void* _t)
97 {
98 #ifndef _WIN32
99     pthread_detach(pthread_self());
100 #endif
101 
102     tr_thread* t = _t;
103 
104     t->func(t->arg);
105 
106     tr_free(t);
107 
108 #ifdef _WIN32
109     _endthreadex(0);
110     return 0;
111 #else
112     return NULL;
113 #endif
114 }
115 
tr_threadNew(void (* func)(void *),void * arg)116 tr_thread* tr_threadNew(void (* func)(void*), void* arg)
117 {
118     tr_thread* t = tr_new0(tr_thread, 1);
119 
120     t->func = func;
121     t->arg = arg;
122 
123 #ifdef _WIN32
124 
125     {
126         unsigned int id;
127         t->thread_handle = (HANDLE)_beginthreadex(NULL, 0, &ThreadFunc, t, 0, &id);
128         t->thread = (DWORD)id;
129     }
130 
131 #else
132 
133     pthread_create(&t->thread, NULL, (void* (*)(void*))ThreadFunc, t);
134 
135 #endif
136 
137     return t;
138 }
139 
140 /***
141 ****  LOCKS
142 ***/
143 
144 /** @brief portability wrapper around OS-dependent thread mutexes */
145 struct tr_lock
146 {
147     int depth;
148 #ifdef _WIN32
149     CRITICAL_SECTION lock;
150     DWORD lockThread;
151 #else
152     pthread_mutex_t lock;
153     pthread_t lockThread;
154 #endif
155 };
156 
tr_lockNew(void)157 tr_lock* tr_lockNew(void)
158 {
159     tr_lock* l = tr_new0(tr_lock, 1);
160 
161 #ifdef _WIN32
162 
163     InitializeCriticalSection(&l->lock); /* supports recursion */
164 
165 #else
166 
167     pthread_mutexattr_t attr;
168     pthread_mutexattr_init(&attr);
169     pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
170     pthread_mutex_init(&l->lock, &attr);
171 
172 #endif
173 
174     return l;
175 }
176 
tr_lockFree(tr_lock * l)177 void tr_lockFree(tr_lock* l)
178 {
179 #ifdef _WIN32
180     DeleteCriticalSection(&l->lock);
181 #else
182     pthread_mutex_destroy(&l->lock);
183 #endif
184 
185     tr_free(l);
186 }
187 
tr_lockLock(tr_lock * l)188 void tr_lockLock(tr_lock* l)
189 {
190 #ifdef _WIN32
191     EnterCriticalSection(&l->lock);
192 #else
193     pthread_mutex_lock(&l->lock);
194 #endif
195 
196     TR_ASSERT(l->depth >= 0);
197     TR_ASSERT(l->depth == 0 || tr_areThreadsEqual(l->lockThread, tr_getCurrentThread()));
198 
199     l->lockThread = tr_getCurrentThread();
200     ++l->depth;
201 }
202 
tr_lockHave(tr_lock const * l)203 bool tr_lockHave(tr_lock const* l)
204 {
205     return l->depth > 0 && tr_areThreadsEqual(l->lockThread, tr_getCurrentThread());
206 }
207 
tr_lockUnlock(tr_lock * l)208 void tr_lockUnlock(tr_lock* l)
209 {
210     TR_ASSERT(l->depth > 0);
211     TR_ASSERT(tr_areThreadsEqual(l->lockThread, tr_getCurrentThread()));
212 
213     --l->depth;
214     TR_ASSERT(l->depth >= 0);
215 
216 #ifdef _WIN32
217     LeaveCriticalSection(&l->lock);
218 #else
219     pthread_mutex_unlock(&l->lock);
220 #endif
221 }
222 
223 /***
224 ****  PATHS
225 ***/
226 
227 #ifndef _WIN32
228 #include <pwd.h>
229 #endif
230 
231 #ifdef _WIN32
232 
win32_get_known_folder_ex(REFKNOWNFOLDERID folder_id,DWORD flags)233 static char* win32_get_known_folder_ex(REFKNOWNFOLDERID folder_id, DWORD flags)
234 {
235     char* ret = NULL;
236     PWSTR path;
237 
238     if (SHGetKnownFolderPath(folder_id, flags | KF_FLAG_DONT_UNEXPAND, NULL, &path) == S_OK)
239     {
240         ret = tr_win32_native_to_utf8(path, -1);
241         CoTaskMemFree(path);
242     }
243 
244     return ret;
245 }
246 
win32_get_known_folder(REFKNOWNFOLDERID folder_id)247 static char* win32_get_known_folder(REFKNOWNFOLDERID folder_id)
248 {
249     return win32_get_known_folder_ex(folder_id, KF_FLAG_DONT_VERIFY);
250 }
251 
252 #endif
253 
getHomeDir(void)254 static char const* getHomeDir(void)
255 {
256     static char* home = NULL;
257 
258     if (home == NULL)
259     {
260         home = tr_env_get_string("HOME", NULL);
261 
262         if (home == NULL)
263         {
264 #ifdef _WIN32
265 
266             home = win32_get_known_folder(&FOLDERID_Profile);
267 
268 #else
269 
270             struct passwd* pw = getpwuid(getuid());
271 
272             if (pw != NULL)
273             {
274                 home = tr_strdup(pw->pw_dir);
275             }
276 
277             endpwent();
278 
279 #endif
280         }
281 
282         if (home == NULL)
283         {
284             home = tr_strdup("");
285         }
286     }
287 
288     return home;
289 }
290 
291 #if defined(__APPLE__) || 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 
tr_setConfigDir(tr_session * session,char const * configDir)299 void tr_setConfigDir(tr_session* session, char const* configDir)
300 {
301     char* path;
302 
303     session->configDir = tr_strdup(configDir);
304 
305     path = tr_buildPath(configDir, RESUME_SUBDIR, NULL);
306     tr_sys_dir_create(path, TR_SYS_DIR_CREATE_PARENTS, 0777, NULL);
307     session->resumeDir = path;
308 
309     path = tr_buildPath(configDir, TORRENT_SUBDIR, NULL);
310     tr_sys_dir_create(path, TR_SYS_DIR_CREATE_PARENTS, 0777, NULL);
311     session->torrentDir = path;
312 }
313 
tr_sessionGetConfigDir(tr_session const * session)314 char const* tr_sessionGetConfigDir(tr_session const* session)
315 {
316     return session->configDir;
317 }
318 
tr_getTorrentDir(tr_session const * session)319 char const* tr_getTorrentDir(tr_session const* session)
320 {
321     return session->torrentDir;
322 }
323 
tr_getResumeDir(tr_session const * session)324 char const* tr_getResumeDir(tr_session const* session)
325 {
326     return session->resumeDir;
327 }
328 
tr_getDefaultConfigDir(char const * appname)329 char const* tr_getDefaultConfigDir(char const* appname)
330 {
331     static char* s = NULL;
332 
333     if (tr_str_is_empty(appname))
334     {
335         appname = "Transmission";
336     }
337 
338     if (s == NULL)
339     {
340         s = tr_env_get_string("TRANSMISSION_HOME", NULL);
341 
342         if (s == NULL)
343         {
344 #ifdef __APPLE__
345 
346             s = tr_buildPath(getHomeDir(), "Library", "Application Support", appname, NULL);
347 
348 #elif defined(_WIN32)
349 
350             char* appdata = win32_get_known_folder(&FOLDERID_LocalAppData);
351             s = tr_buildPath(appdata, appname, NULL);
352             tr_free(appdata);
353 
354 #elif defined(__HAIKU__)
355 
356             char buf[PATH_MAX];
357             find_directory(B_USER_SETTINGS_DIRECTORY, -1, true, buf, sizeof(buf));
358             s = tr_buildPath(buf, appname, NULL);
359 
360 #else
361 
362             char* const xdg_config_home = tr_env_get_string("XDG_CONFIG_HOME", NULL);
363 
364             if (xdg_config_home != NULL)
365             {
366                 s = tr_buildPath(xdg_config_home, appname, NULL);
367                 tr_free(xdg_config_home);
368             }
369             else
370             {
371                 s = tr_buildPath(getHomeDir(), ".config", appname, NULL);
372             }
373 
374 #endif
375         }
376     }
377 
378     return s;
379 }
380 
tr_getDefaultDownloadDir(void)381 char const* tr_getDefaultDownloadDir(void)
382 {
383     static char* user_dir = NULL;
384 
385     if (user_dir == NULL)
386     {
387         char* config_home;
388         char* config_file;
389         char* content;
390         size_t content_len;
391 
392         /* figure out where to look for user-dirs.dirs */
393         config_home = tr_env_get_string("XDG_CONFIG_HOME", NULL);
394 
395         if (!tr_str_is_empty(config_home))
396         {
397             config_file = tr_buildPath(config_home, "user-dirs.dirs", NULL);
398         }
399         else
400         {
401             config_file = tr_buildPath(getHomeDir(), ".config", "user-dirs.dirs", NULL);
402         }
403 
404         tr_free(config_home);
405 
406         /* read in user-dirs.dirs and look for the download dir entry */
407         content = (char*)tr_loadFile(config_file, &content_len, NULL);
408 
409         if (content != NULL && content_len > 0)
410         {
411             char const* key = "XDG_DOWNLOAD_DIR=\"";
412             char* line = strstr(content, key);
413 
414             if (line != NULL)
415             {
416                 char* value = line + strlen(key);
417                 char* end = strchr(value, '"');
418 
419                 if (end != NULL)
420                 {
421                     *end = '\0';
422 
423                     if (strncmp(value, "$HOME/", 6) == 0)
424                     {
425                         user_dir = tr_buildPath(getHomeDir(), value + 6, NULL);
426                     }
427                     else if (strcmp(value, "$HOME") == 0)
428                     {
429                         user_dir = tr_strdup(getHomeDir());
430                     }
431                     else
432                     {
433                         user_dir = tr_strdup(value);
434                     }
435                 }
436             }
437         }
438 
439 #ifdef _WIN32
440 
441         if (user_dir == NULL)
442         {
443             user_dir = win32_get_known_folder(&FOLDERID_Downloads);
444         }
445 
446 #endif
447 
448         if (user_dir == NULL)
449         {
450 #ifdef __HAIKU__
451             user_dir = tr_buildPath(getHomeDir(), "Desktop", NULL);
452 #else
453             user_dir = tr_buildPath(getHomeDir(), "Downloads", NULL);
454 #endif
455         }
456 
457         tr_free(content);
458         tr_free(config_file);
459     }
460 
461     return user_dir;
462 }
463 
464 /***
465 ****
466 ***/
467 
isWebClientDir(char const * path)468 static bool isWebClientDir(char const* path)
469 {
470     char* tmp = tr_buildPath(path, "index.html", NULL);
471     bool const ret = tr_sys_path_exists(tmp, NULL);
472     tr_logAddInfo(_("Searching for web interface file \"%s\""), tmp);
473     tr_free(tmp);
474 
475     return ret;
476 }
477 
tr_getWebClientDir(tr_session const * session UNUSED)478 char const* tr_getWebClientDir(tr_session const* session UNUSED)
479 {
480     static char* s = NULL;
481 
482     if (s == NULL)
483     {
484         s = tr_env_get_string("CLUTCH_HOME", NULL);
485 
486         if (s == NULL)
487         {
488             s = tr_env_get_string("TRANSMISSION_WEB_HOME", NULL);
489         }
490 
491         if (s == NULL)
492         {
493 #ifdef BUILD_MAC_CLIENT /* on Mac, look in the Application Support folder first, then in the app bundle. */
494 
495             /* Look in the Application Support folder */
496             s = tr_buildPath(tr_sessionGetConfigDir(session), "web", NULL);
497 
498             if (!isWebClientDir(s))
499             {
500                 tr_free(s);
501 
502                 CFURLRef appURL = CFBundleCopyBundleURL(CFBundleGetMainBundle());
503                 CFStringRef appRef = CFURLCopyFileSystemPath(appURL, kCFURLPOSIXPathStyle);
504                 CFIndex const appStringLength = CFStringGetMaximumSizeOfFileSystemRepresentation(appRef);
505 
506                 char* appString = tr_malloc(appStringLength);
507                 bool const success = CFStringGetFileSystemRepresentation(appRef, appString, appStringLength);
508                 TR_ASSERT(success);
509 
510                 CFRelease(appURL);
511                 CFRelease(appRef);
512 
513                 /* Fallback to the app bundle */
514                 s = tr_buildPath(appString, "Contents", "Resources", "web", NULL);
515 
516                 if (!isWebClientDir(s))
517                 {
518                     tr_free(s);
519                     s = NULL;
520                 }
521 
522                 tr_free(appString);
523             }
524 
525 #elif defined(_WIN32)
526 
527             /* Generally, Web interface should be stored in a Web subdir of
528              * calling executable dir. */
529 
530             static REFKNOWNFOLDERID known_folder_ids[] =
531             {
532                 &FOLDERID_LocalAppData,
533                 &FOLDERID_RoamingAppData,
534                 &FOLDERID_ProgramData
535             };
536 
537             for (size_t i = 0; s == NULL && i < TR_N_ELEMENTS(known_folder_ids); ++i)
538             {
539                 char* dir = win32_get_known_folder(known_folder_ids[i]);
540                 s = tr_buildPath(dir, "Transmission", "Web", NULL);
541                 tr_free(dir);
542 
543                 if (!isWebClientDir(s))
544                 {
545                     tr_free(s);
546                     s = NULL;
547                 }
548             }
549 
550             if (s == NULL) /* check calling module place */
551             {
552                 wchar_t wide_module_path[MAX_PATH];
553                 char* module_path;
554                 char* dir;
555                 GetModuleFileNameW(NULL, wide_module_path, TR_N_ELEMENTS(wide_module_path));
556                 module_path = tr_win32_native_to_utf8(wide_module_path, -1);
557                 dir = tr_sys_path_dirname(module_path, NULL);
558                 tr_free(module_path);
559 
560                 if (dir != NULL)
561                 {
562                     s = tr_buildPath(dir, "Web", NULL);
563                     tr_free(dir);
564 
565                     if (!isWebClientDir(s))
566                     {
567                         tr_free(s);
568                         s = NULL;
569                     }
570                 }
571             }
572 
573 #else /* everyone else, follow the XDG spec */
574 
575             tr_list* candidates = NULL;
576             char* tmp;
577 
578             /* XDG_DATA_HOME should be the first in the list of candidates */
579             tmp = tr_env_get_string("XDG_DATA_HOME", NULL);
580 
581             if (!tr_str_is_empty(tmp))
582             {
583                 tr_list_append(&candidates, tmp);
584             }
585             else
586             {
587                 char* dhome = tr_buildPath(getHomeDir(), ".local", "share", NULL);
588                 tr_list_append(&candidates, dhome);
589                 tr_free(tmp);
590             }
591 
592             /* XDG_DATA_DIRS are the backup directories */
593             {
594                 char const* pkg = PACKAGE_DATA_DIR;
595                 char* xdg = tr_env_get_string("XDG_DATA_DIRS", NULL);
596                 char const* fallback = "/usr/local/share:/usr/share";
597                 char* buf = tr_strdup_printf("%s:%s:%s", pkg != NULL ? pkg : "", xdg != NULL ? xdg : "", fallback);
598                 tr_free(xdg);
599                 tmp = buf;
600 
601                 while (!tr_str_is_empty(tmp))
602                 {
603                     char const* end = strchr(tmp, ':');
604 
605                     if (end != NULL)
606                     {
607                         if (end - tmp > 1)
608                         {
609                             tr_list_append(&candidates, tr_strndup(tmp, (size_t)(end - tmp)));
610                         }
611 
612                         tmp = (char*)end + 1;
613                     }
614                     else if (!tr_str_is_empty(tmp))
615                     {
616                         tr_list_append(&candidates, tr_strdup(tmp));
617                         break;
618                     }
619                 }
620 
621                 tr_free(buf);
622             }
623 
624             /* walk through the candidates & look for a match */
625             for (tr_list* l = candidates; l != NULL; l = l->next)
626             {
627                 char* path = tr_buildPath(l->data, "transmission", "web", NULL);
628                 bool const found = isWebClientDir(path);
629 
630                 if (found)
631                 {
632                     s = path;
633                     break;
634                 }
635 
636                 tr_free(path);
637             }
638 
639             tr_list_free(&candidates, tr_free);
640 
641 #endif
642         }
643     }
644 
645     return s;
646 }
647 
tr_getSessionIdDir(void)648 char* tr_getSessionIdDir(void)
649 {
650 #ifndef _WIN32
651 
652     return tr_strdup("/tmp");
653 
654 #else
655 
656     char* program_data_dir = win32_get_known_folder_ex(&FOLDERID_ProgramData, KF_FLAG_CREATE);
657     char* result = tr_buildPath(program_data_dir, "Transmission", NULL);
658     tr_free(program_data_dir);
659     tr_sys_dir_create(result, 0, 0, NULL);
660     return result;
661 
662 #endif
663 }
664