1 /******************************************************************************
2 * Copyright (c) Transmission authors and contributors
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20 * DEALINGS IN THE SOFTWARE.
21 *****************************************************************************/
22
23 #include <stdio.h> /* fprintf () */
24 #include <stdlib.h> /* atoi () */
25 #include <string.h> /* memcmp () */
26 #include <signal.h>
27
28 #include <libtransmission/transmission.h>
29 #include <libtransmission/error.h>
30 #include <libtransmission/file.h>
31 #include <libtransmission/tr-getopt.h>
32 #include <libtransmission/utils.h> /* tr_wait_msec */
33 #include <libtransmission/variant.h>
34 #include <libtransmission/version.h>
35 #include <libtransmission/web.h> /* tr_webRun */
36
37 /***
38 ****
39 ***/
40
41 #define MEM_K 1024
42 #define MEM_K_STR "KiB"
43 #define MEM_M_STR "MiB"
44 #define MEM_G_STR "GiB"
45 #define MEM_T_STR "TiB"
46
47 #define DISK_K 1000
48 #define DISK_B_STR "B"
49 #define DISK_K_STR "kB"
50 #define DISK_M_STR "MB"
51 #define DISK_G_STR "GB"
52 #define DISK_T_STR "TB"
53
54 #define SPEED_K 1000
55 #define SPEED_B_STR "B/s"
56 #define SPEED_K_STR "kB/s"
57 #define SPEED_M_STR "MB/s"
58 #define SPEED_G_STR "GB/s"
59 #define SPEED_T_STR "TB/s"
60
61 /***
62 ****
63 ***/
64
65 #define LINEWIDTH 80
66 #define MY_CONFIG_NAME "transmission"
67 #define MY_READABLE_NAME "transmission-cli"
68
69 static bool showVersion = false;
70 static bool verify = false;
71 static sig_atomic_t gotsig = false;
72 static sig_atomic_t manualUpdate = false;
73
74 static char const* torrentPath = NULL;
75
76 static struct tr_option const options[] =
77 {
78 { 'b', "blocklist", "Enable peer blocklists", "b", false, NULL },
79 { 'B', "no-blocklist", "Disable peer blocklists", "B", false, NULL },
80 { 'd', "downlimit", "Set max download speed in "SPEED_K_STR, "d", true, "<speed>" },
81 { 'D', "no-downlimit", "Don't limit the download speed", "D", false, NULL },
82 { 910, "encryption-required", "Encrypt all peer connections", "er", false, NULL },
83 { 911, "encryption-preferred", "Prefer encrypted peer connections", "ep", false, NULL },
84 { 912, "encryption-tolerated", "Prefer unencrypted peer connections", "et", false, NULL },
85 { 'f', "finish", "Run a script when the torrent finishes", "f", true, "<script>" },
86 { 'g', "config-dir", "Where to find configuration files", "g", true, "<path>" },
87 { 'm', "portmap", "Enable portmapping via NAT-PMP or UPnP", "m", false, NULL },
88 { 'M', "no-portmap", "Disable portmapping", "M", false, NULL },
89 { 'p', "port", "Port for incoming peers (Default: " TR_DEFAULT_PEER_PORT_STR ")", "p", true, "<port>" },
90 { 't', "tos", "Peer socket TOS (0 to 255, default=" TR_DEFAULT_PEER_SOCKET_TOS_STR ")", "t", true, "<tos>" },
91 { 'u', "uplimit", "Set max upload speed in "SPEED_K_STR, "u", true, "<speed>" },
92 { 'U', "no-uplimit", "Don't limit the upload speed", "U", false, NULL },
93 { 'v', "verify", "Verify the specified torrent", "v", false, NULL },
94 { 'V', "version", "Show version number and exit", "V", false, NULL },
95 { 'w', "download-dir", "Where to save downloaded data", "w", true, "<path>" },
96 { 0, NULL, NULL, NULL, false, NULL }
97 };
98
getUsage(void)99 static char const* getUsage(void)
100 {
101 return "A fast and easy BitTorrent client\n"
102 "\n"
103 "Usage: " MY_READABLE_NAME " [options] <file|url|magnet>";
104 }
105
106 static int parseCommandLine(tr_variant*, int argc, char const** argv);
107
108 static void sigHandler(int signal);
109
tr_strlratio(char * buf,double ratio,size_t buflen)110 static char* tr_strlratio(char* buf, double ratio, size_t buflen)
111 {
112 if ((int)ratio == TR_RATIO_NA)
113 {
114 tr_strlcpy(buf, _("None"), buflen);
115 }
116 else if ((int)ratio == TR_RATIO_INF)
117 {
118 tr_strlcpy(buf, "Inf", buflen);
119 }
120 else if (ratio < 10.0)
121 {
122 tr_snprintf(buf, buflen, "%.2f", ratio);
123 }
124 else if (ratio < 100.0)
125 {
126 tr_snprintf(buf, buflen, "%.1f", ratio);
127 }
128 else
129 {
130 tr_snprintf(buf, buflen, "%.0f", ratio);
131 }
132
133 return buf;
134 }
135
136 static bool waitingOnWeb;
137
onTorrentFileDownloaded(tr_session * session UNUSED,bool did_connect UNUSED,bool did_timeout UNUSED,long response_code UNUSED,void const * response,size_t response_byte_count,void * ctor)138 static void onTorrentFileDownloaded(tr_session* session UNUSED, bool did_connect UNUSED, bool did_timeout UNUSED,
139 long response_code UNUSED, void const* response, size_t response_byte_count, void* ctor)
140 {
141 tr_ctorSetMetainfo(ctor, response, response_byte_count);
142 waitingOnWeb = false;
143 }
144
getStatusStr(tr_stat const * st,char * buf,size_t buflen)145 static void getStatusStr(tr_stat const* st, char* buf, size_t buflen)
146 {
147 if (st->activity == TR_STATUS_CHECK_WAIT)
148 {
149 tr_snprintf(buf, buflen, "Waiting to verify local files");
150 }
151 else if (st->activity == TR_STATUS_CHECK)
152 {
153 tr_snprintf(buf, buflen, "Verifying local files (%.2f%%, %.2f%% valid)", tr_truncd(100 * st->recheckProgress, 2),
154 tr_truncd(100 * st->percentDone, 2));
155 }
156 else if (st->activity == TR_STATUS_DOWNLOAD)
157 {
158 char upStr[80];
159 char dnStr[80];
160 char ratioStr[80];
161
162 tr_formatter_speed_KBps(upStr, st->pieceUploadSpeed_KBps, sizeof(upStr));
163 tr_formatter_speed_KBps(dnStr, st->pieceDownloadSpeed_KBps, sizeof(dnStr));
164 tr_strlratio(ratioStr, st->ratio, sizeof(ratioStr));
165
166 tr_snprintf(buf, buflen, "Progress: %.1f%%, dl from %d of %d peers (%s), ul to %d (%s) [%s]",
167 tr_truncd(100 * st->percentDone, 1), st->peersSendingToUs, st->peersConnected, dnStr, st->peersGettingFromUs, upStr,
168 ratioStr);
169 }
170 else if (st->activity == TR_STATUS_SEED)
171 {
172 char upStr[80];
173 char ratioStr[80];
174
175 tr_formatter_speed_KBps(upStr, st->pieceUploadSpeed_KBps, sizeof(upStr));
176 tr_strlratio(ratioStr, st->ratio, sizeof(ratioStr));
177
178 tr_snprintf(buf, buflen, "Seeding, uploading to %d of %d peer(s), %s [%s]", st->peersGettingFromUs, st->peersConnected,
179 upStr, ratioStr);
180 }
181 else
182 {
183 *buf = '\0';
184 }
185 }
186
getConfigDir(int argc,char const ** argv)187 static char const* getConfigDir(int argc, char const** argv)
188 {
189 int c;
190 char const* configDir = NULL;
191 char const* optarg;
192 int const ind = tr_optind;
193
194 while ((c = tr_getopt(getUsage(), argc, argv, options, &optarg)) != TR_OPT_DONE)
195 {
196 if (c == 'g')
197 {
198 configDir = optarg;
199 break;
200 }
201 }
202
203 tr_optind = ind;
204
205 if (configDir == NULL)
206 {
207 configDir = tr_getDefaultConfigDir(MY_CONFIG_NAME);
208 }
209
210 return configDir;
211 }
212
tr_main(int argc,char * argv[])213 int tr_main(int argc, char* argv[])
214 {
215 tr_session* h;
216 tr_ctor* ctor;
217 tr_torrent* tor = NULL;
218 tr_variant settings;
219 char const* configDir;
220 uint8_t* fileContents;
221 size_t fileLength;
222 char const* str;
223
224 tr_formatter_mem_init(MEM_K, MEM_K_STR, MEM_M_STR, MEM_G_STR, MEM_T_STR);
225 tr_formatter_size_init(DISK_K, DISK_K_STR, DISK_M_STR, DISK_G_STR, DISK_T_STR);
226 tr_formatter_speed_init(SPEED_K, SPEED_K_STR, SPEED_M_STR, SPEED_G_STR, SPEED_T_STR);
227
228 printf("%s %s\n", MY_READABLE_NAME, LONG_VERSION_STRING);
229
230 /* user needs to pass in at least one argument */
231 if (argc < 2)
232 {
233 tr_getopt_usage(MY_READABLE_NAME, getUsage(), options);
234 return EXIT_FAILURE;
235 }
236
237 /* load the defaults from config file + libtransmission defaults */
238 tr_variantInitDict(&settings, 0);
239 configDir = getConfigDir(argc, (char const**)argv);
240 tr_sessionLoadSettings(&settings, configDir, MY_CONFIG_NAME);
241
242 /* the command line overrides defaults */
243 if (parseCommandLine(&settings, argc, (char const**)argv) != 0)
244 {
245 return EXIT_FAILURE;
246 }
247
248 if (showVersion)
249 {
250 return EXIT_SUCCESS;
251 }
252
253 /* Check the options for validity */
254 if (torrentPath == NULL)
255 {
256 fprintf(stderr, "No torrent specified!\n");
257 return EXIT_FAILURE;
258 }
259
260 if (tr_variantDictFindStr(&settings, TR_KEY_download_dir, &str, NULL))
261 {
262 if (!tr_sys_path_exists(str, NULL))
263 {
264 tr_error* error = NULL;
265
266 if (!tr_sys_dir_create(str, TR_SYS_DIR_CREATE_PARENTS, 0700, &error))
267 {
268 fprintf(stderr, "Unable to create download directory \"%s\": %s\n", str, error->message);
269 tr_error_free(error);
270 return EXIT_FAILURE;
271 }
272 }
273 }
274
275 h = tr_sessionInit(configDir, false, &settings);
276
277 ctor = tr_ctorNew(h);
278
279 fileContents = tr_loadFile(torrentPath, &fileLength, NULL);
280 tr_ctorSetPaused(ctor, TR_FORCE, false);
281
282 if (fileContents != NULL)
283 {
284 tr_ctorSetMetainfo(ctor, fileContents, fileLength);
285 }
286 else if (memcmp(torrentPath, "magnet:?", 8) == 0)
287 {
288 tr_ctorSetMetainfoFromMagnetLink(ctor, torrentPath);
289 }
290 else if (memcmp(torrentPath, "http", 4) == 0)
291 {
292 tr_webRun(h, torrentPath, onTorrentFileDownloaded, ctor);
293 waitingOnWeb = true;
294
295 while (waitingOnWeb)
296 {
297 tr_wait_msec(1000);
298 }
299 }
300 else
301 {
302 fprintf(stderr, "ERROR: Unrecognized torrent \"%s\".\n", torrentPath);
303 fprintf(stderr, " * If you're trying to create a torrent, use transmission-create.\n");
304 fprintf(stderr, " * If you're trying to see a torrent's info, use transmission-show.\n");
305 tr_sessionClose(h);
306 return EXIT_FAILURE;
307 }
308
309 tr_free(fileContents);
310
311 tor = tr_torrentNew(ctor, NULL, NULL);
312 tr_ctorFree(ctor);
313
314 if (tor == NULL)
315 {
316 fprintf(stderr, "Failed opening torrent file `%s'\n", torrentPath);
317 tr_sessionClose(h);
318 return EXIT_FAILURE;
319 }
320
321 signal(SIGINT, sigHandler);
322 #ifndef _WIN32
323 signal(SIGHUP, sigHandler);
324 #endif
325 tr_torrentStart(tor);
326
327 if (verify)
328 {
329 verify = false;
330 tr_torrentVerify(tor, NULL, NULL);
331 }
332
333 for (;;)
334 {
335 char line[LINEWIDTH];
336 tr_stat const* st;
337 char const* messageName[] =
338 {
339 NULL,
340 "Tracker gave a warning:",
341 "Tracker gave an error:",
342 "Error:"
343 };
344
345 tr_wait_msec(200);
346
347 if (gotsig)
348 {
349 gotsig = false;
350 printf("\nStopping torrent...\n");
351 tr_torrentStop(tor);
352 }
353
354 if (manualUpdate)
355 {
356 manualUpdate = false;
357
358 if (!tr_torrentCanManualUpdate(tor))
359 {
360 fprintf(stderr, "\nReceived SIGHUP, but can't send a manual update now\n");
361 }
362 else
363 {
364 fprintf(stderr, "\nReceived SIGHUP: manual update scheduled\n");
365 tr_torrentManualUpdate(tor);
366 }
367 }
368
369 st = tr_torrentStat(tor);
370
371 if (st->activity == TR_STATUS_STOPPED)
372 {
373 break;
374 }
375
376 getStatusStr(st, line, sizeof(line));
377 printf("\r%-*s", LINEWIDTH, line);
378
379 if (messageName[st->error])
380 {
381 fprintf(stderr, "\n%s: %s\n", messageName[st->error], st->errorString);
382 }
383 }
384
385 tr_sessionSaveSettings(h, configDir, &settings);
386
387 printf("\n");
388 tr_variantFree(&settings);
389 tr_sessionClose(h);
390 return EXIT_SUCCESS;
391 }
392
393 /***
394 ****
395 ****
396 ***/
397
parseCommandLine(tr_variant * d,int argc,char const ** argv)398 static int parseCommandLine(tr_variant* d, int argc, char const** argv)
399 {
400 int c;
401 char const* optarg;
402
403 while ((c = tr_getopt(getUsage(), argc, argv, options, &optarg)) != TR_OPT_DONE)
404 {
405 switch (c)
406 {
407 case 'b':
408 tr_variantDictAddBool(d, TR_KEY_blocklist_enabled, true);
409 break;
410
411 case 'B':
412 tr_variantDictAddBool(d, TR_KEY_blocklist_enabled, false);
413 break;
414
415 case 'd':
416 tr_variantDictAddInt(d, TR_KEY_speed_limit_down, atoi(optarg));
417 tr_variantDictAddBool(d, TR_KEY_speed_limit_down_enabled, true);
418 break;
419
420 case 'D':
421 tr_variantDictAddBool(d, TR_KEY_speed_limit_down_enabled, false);
422 break;
423
424 case 'f':
425 tr_variantDictAddStr(d, TR_KEY_script_torrent_done_filename, optarg);
426 tr_variantDictAddBool(d, TR_KEY_script_torrent_done_enabled, true);
427 break;
428
429 case 'g': /* handled above */
430 break;
431
432 case 'm':
433 tr_variantDictAddBool(d, TR_KEY_port_forwarding_enabled, true);
434 break;
435
436 case 'M':
437 tr_variantDictAddBool(d, TR_KEY_port_forwarding_enabled, false);
438 break;
439
440 case 'p':
441 tr_variantDictAddInt(d, TR_KEY_peer_port, atoi(optarg));
442 break;
443
444 case 't':
445 tr_variantDictAddInt(d, TR_KEY_peer_socket_tos, atoi(optarg));
446 break;
447
448 case 'u':
449 tr_variantDictAddInt(d, TR_KEY_speed_limit_up, atoi(optarg));
450 tr_variantDictAddBool(d, TR_KEY_speed_limit_up_enabled, true);
451 break;
452
453 case 'U':
454 tr_variantDictAddBool(d, TR_KEY_speed_limit_up_enabled, false);
455 break;
456
457 case 'v':
458 verify = true;
459 break;
460
461 case 'V':
462 showVersion = true;
463 break;
464
465 case 'w':
466 tr_variantDictAddStr(d, TR_KEY_download_dir, optarg);
467 break;
468
469 case 910:
470 tr_variantDictAddInt(d, TR_KEY_encryption, TR_ENCRYPTION_REQUIRED);
471 break;
472
473 case 911:
474 tr_variantDictAddInt(d, TR_KEY_encryption, TR_ENCRYPTION_PREFERRED);
475 break;
476
477 case 912:
478 tr_variantDictAddInt(d, TR_KEY_encryption, TR_CLEAR_PREFERRED);
479 break;
480
481 case TR_OPT_UNK:
482 if (torrentPath == NULL)
483 {
484 torrentPath = optarg;
485 }
486
487 break;
488
489 default:
490 return 1;
491 }
492 }
493
494 return 0;
495 }
496
sigHandler(int signal)497 static void sigHandler(int signal)
498 {
499 switch (signal)
500 {
501 case SIGINT:
502 gotsig = true;
503 break;
504
505 #ifndef _WIN32
506
507 case SIGHUP:
508 manualUpdate = true;
509 break;
510
511 #endif
512
513 default:
514 break;
515 }
516 }
517