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