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