A clone of btpd with my configuration changes.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

496 lines
11 KiB

  1. #include <sys/types.h>
  2. #include <sys/stat.h>
  3. #include <err.h>
  4. #include <errno.h>
  5. #include <fcntl.h>
  6. #include <getopt.h>
  7. #include <inttypes.h>
  8. #include <limits.h>
  9. #include <stdio.h>
  10. #include <stdlib.h>
  11. #include <string.h>
  12. #include <unistd.h>
  13. #include "btpd_if.h"
  14. #include "metainfo.h"
  15. #include "subr.h"
  16. const char *btpd_dir;
  17. struct ipc *ipc;
  18. void
  19. btpd_connect(void)
  20. {
  21. if ((errno = ipc_open(btpd_dir, &ipc)) != 0)
  22. err(1, "cannot open connection to btpd in %s", btpd_dir);
  23. }
  24. enum ipc_code
  25. handle_ipc_res(enum ipc_code code, const char *target)
  26. {
  27. switch (code) {
  28. case IPC_OK:
  29. break;
  30. case IPC_FAIL:
  31. warnx("btpd couldn't execute the requested operation for %s", target);
  32. break;
  33. case IPC_ERROR:
  34. warnx("btpd encountered an error for %s", target);
  35. break;
  36. default:
  37. errx(1, "fatal error in communication with btpd");
  38. }
  39. return code;
  40. }
  41. void
  42. print_state_name(struct tpstat *ts)
  43. {
  44. char c;
  45. switch (ts->state) {
  46. case T_STARTING:
  47. c = '+';
  48. break;
  49. case T_ACTIVE:
  50. c = ts->pieces_got == ts->torrent_pieces ? 'S' : 'L';
  51. break;
  52. case T_STOPPING:
  53. c = '-';
  54. break;
  55. default:
  56. c = 'U';
  57. break;
  58. }
  59. printf("%c. %s", c, ts->name);
  60. }
  61. void
  62. print_stat(struct tpstat *ts)
  63. {
  64. printf("%5.1f%% %6.1fM %7.2fkB/s %6.1fM %7.2fkB/s %4u %5.1f%%",
  65. 100.0 * ts->content_got / ts->content_size,
  66. (double)ts->downloaded / (1 << 20),
  67. (double)ts->rate_down / (20 << 10),
  68. (double)ts->uploaded / (1 << 20),
  69. (double)ts->rate_up / (20 << 10),
  70. ts->peers,
  71. 100.0 * ts->pieces_seen / ts->torrent_pieces);
  72. if (ts->tr_errors > 0)
  73. printf(" E%u", ts->tr_errors);
  74. printf("\n");
  75. }
  76. void
  77. usage_add(void)
  78. {
  79. printf(
  80. "Add torrents to btpd.\n"
  81. "\n"
  82. "Usage: add [--topdir] -d dir file\n"
  83. " add file ...\n"
  84. "\n"
  85. "Arguments:\n"
  86. "file ...\n"
  87. "\tOne or more torrents to add.\n"
  88. "\n"
  89. "Options:\n"
  90. "-d dir\n"
  91. "\tUse the dir for content.\n"
  92. "\n"
  93. "--topdir\n"
  94. "\tAppend the torrent top directory (if any) to the content path.\n"
  95. "\tThis option cannot be used without the '-d' option.\n"
  96. "\n"
  97. );
  98. exit(1);
  99. }
  100. struct option add_opts [] = {
  101. { "help", no_argument, NULL, 'H' },
  102. { "topdir", no_argument, NULL, 'T'},
  103. {NULL, 0, NULL, 0}
  104. };
  105. int
  106. content_link(uint8_t *hash, char *buf)
  107. {
  108. int n;
  109. char relpath[41];
  110. char path[PATH_MAX];
  111. for (int i = 0; i < 20; i++)
  112. snprintf(relpath + i * 2, 3, "%.2x", hash[i]);
  113. snprintf(path, PATH_MAX, "%s/torrents/%s/content", btpd_dir, relpath);
  114. if ((n = readlink(path, buf, PATH_MAX)) == -1)
  115. return errno;
  116. buf[min(n, PATH_MAX)] = '\0';
  117. return 0;
  118. }
  119. void
  120. cmd_add(int argc, char **argv)
  121. {
  122. int ch, topdir = 0;
  123. char *dir = NULL;
  124. while ((ch = getopt_long(argc, argv, "d:", add_opts, NULL)) != -1) {
  125. switch (ch) {
  126. case 'T':
  127. topdir = 1;
  128. break;
  129. case 'd':
  130. dir = optarg;
  131. break;
  132. default:
  133. usage_add();
  134. }
  135. }
  136. argc -= optind;
  137. argv += optind;
  138. if (argc < 1 || (topdir == 1 && dir == NULL) || (dir != NULL && argc > 1))
  139. usage_add();
  140. btpd_connect();
  141. for (int i = 0; i < argc; i++) {
  142. struct metainfo *mi;
  143. char rdpath[PATH_MAX], dpath[PATH_MAX], fpath[PATH_MAX];
  144. if ((errno = load_metainfo(argv[i], -1, 0, &mi)) != 0) {
  145. warn("error loading torrent %s", argv[i]);
  146. continue;
  147. }
  148. if ((topdir &&
  149. !(mi->nfiles == 1
  150. && strcmp(mi->name, mi->files[0].path) == 0)))
  151. snprintf(dpath, PATH_MAX, "%s/%s", dir, mi->name);
  152. else if (dir != NULL)
  153. strncpy(dpath, dir, PATH_MAX);
  154. else {
  155. if (content_link(mi->info_hash, dpath) != 0) {
  156. warnx("unknown content dir for %s", argv[i]);
  157. errx(1, "use the '-d' option");
  158. }
  159. }
  160. if (mkdir(dpath, 0777) != 0 && errno != EEXIST)
  161. err(1, "couldn't create directory %s", dpath);
  162. if (realpath(dpath, rdpath) == NULL)
  163. err(1, "path error on %s", dpath);
  164. if (realpath(argv[i], fpath) == NULL)
  165. err(1, "path error on %s", fpath);
  166. handle_ipc_res(btpd_add(ipc, mi->info_hash, fpath, rdpath), argv[i]);
  167. clear_metainfo(mi);
  168. free(mi);
  169. }
  170. }
  171. void
  172. usage_del(void)
  173. {
  174. printf(
  175. "Remove torrents from btpd.\n"
  176. "\n"
  177. "Usage: del file ...\n"
  178. "\n"
  179. "Arguments:\n"
  180. "file ...\n"
  181. "\tThe torrents to remove.\n"
  182. "\n");
  183. exit(1);
  184. }
  185. void
  186. cmd_del(int argc, char **argv)
  187. {
  188. if (argc < 2)
  189. usage_del();
  190. btpd_connect();
  191. for (int i = 1; i < argc; i++) {
  192. struct metainfo *mi;
  193. if ((errno = load_metainfo(argv[i], -1, 0, &mi)) != 0) {
  194. warn("error loading torrent %s", argv[i]);
  195. continue;
  196. }
  197. handle_ipc_res(btpd_del(ipc, mi->info_hash), argv[i]);
  198. clear_metainfo(mi);
  199. free(mi);
  200. }
  201. }
  202. void
  203. usage_kill(void)
  204. {
  205. printf(
  206. "Shutdown btpd.\n"
  207. "\n"
  208. "Usage: kill [seconds]\n"
  209. "\n"
  210. "Arguments:\n"
  211. "seconds\n"
  212. "\tThe number of seconds btpd waits before giving up on unresponsive\n"
  213. "\ttrackers.\n"
  214. "\n"
  215. );
  216. exit(1);
  217. }
  218. void
  219. cmd_kill(int argc, char **argv)
  220. {
  221. int seconds = -1;
  222. char *endptr;
  223. if (argc == 2) {
  224. seconds = strtol(argv[1], &endptr, 10);
  225. if (strlen(argv[1]) > endptr - argv[1] || seconds < 0)
  226. usage_kill();
  227. } else if (argc > 2)
  228. usage_kill();
  229. btpd_connect();
  230. handle_ipc_res(btpd_die(ipc, seconds), "kill");
  231. }
  232. void
  233. usage_list(void)
  234. {
  235. printf(
  236. "List active torrents.\n"
  237. "\n"
  238. "Usage: list\n"
  239. "\n"
  240. );
  241. exit(1);
  242. }
  243. void
  244. cmd_list(int argc, char **argv)
  245. {
  246. struct btstat *st;
  247. if (argc > 1)
  248. usage_list();
  249. btpd_connect();
  250. if (handle_ipc_res(btpd_stat(ipc, &st), "list") != IPC_OK)
  251. exit(1);
  252. for (int i = 0; i < st->ntorrents; i++) {
  253. print_state_name(&st->torrents[i]);
  254. putchar('\n');
  255. }
  256. printf("%u torrent%s.\n", st->ntorrents,
  257. st->ntorrents == 1 ? "" : "s");
  258. }
  259. void
  260. usage_stat(void)
  261. {
  262. printf(
  263. "Display stats for active torrents.\n"
  264. "The displayed stats are:\n"
  265. "%% got, MB down, rate down. MB up, rate up\n"
  266. "peer count, %% of pieces seen, tracker errors\n"
  267. "\n"
  268. "Usage: stat [-i] [-w seconds] [file ...]\n"
  269. "\n"
  270. "Arguments:\n"
  271. "file ...\n"
  272. "\tOnly display stats for the given torrent(s).\n"
  273. "\n"
  274. "Options:\n"
  275. "-i\n"
  276. "\tDisplay individual lines for each torrent.\n"
  277. "\n"
  278. "-w n\n"
  279. "\tDisplay stats every n seconds.\n"
  280. "\n");
  281. exit(1);
  282. }
  283. void
  284. do_stat(int individual, int seconds, int hash_count, uint8_t (*hashes)[20])
  285. {
  286. struct btstat *st;
  287. struct tpstat tot;
  288. again:
  289. bzero(&tot, sizeof(tot));
  290. if (handle_ipc_res(btpd_stat(ipc, &st), "stat") != IPC_OK)
  291. exit(1);
  292. for (int i = 0; i < st->ntorrents; i++) {
  293. struct tpstat *cur = &st->torrents[i];
  294. if (hash_count > 0) {
  295. int found = 0;
  296. for (int h = 0; !found && h < hash_count; h++)
  297. if (bcmp(cur->hash, hashes[h], 20) == 0)
  298. found = 1;
  299. if (!found)
  300. continue;
  301. }
  302. tot.uploaded += cur->uploaded;
  303. tot.downloaded += cur->downloaded;
  304. tot.rate_up += cur->rate_up;
  305. tot.rate_down += cur->rate_down;
  306. tot.peers += cur->peers;
  307. tot.pieces_seen += cur->pieces_seen;
  308. tot.torrent_pieces += cur->torrent_pieces;
  309. tot.content_got += cur->content_got;
  310. tot.content_size += cur->content_size;
  311. if (individual) {
  312. print_state_name(cur);
  313. printf(":\n");
  314. print_stat(cur);
  315. }
  316. }
  317. free_btstat(st);
  318. if (individual)
  319. printf("Total:\n");
  320. print_stat(&tot);
  321. if (seconds > 0) {
  322. sleep(seconds);
  323. goto again;
  324. }
  325. }
  326. struct option stat_opts [] = {
  327. { "help", no_argument, NULL, 'H' },
  328. {NULL, 0, NULL, 0}
  329. };
  330. void
  331. cmd_stat(int argc, char **argv)
  332. {
  333. int ch;
  334. int wflag = 0, iflag = 0, seconds = 0;
  335. uint8_t (*hashes)[20] = NULL;
  336. char *endptr;
  337. while ((ch = getopt_long(argc, argv, "iw:", stat_opts, NULL)) != -1) {
  338. switch (ch) {
  339. case 'i':
  340. iflag = 1;
  341. break;
  342. case 'w':
  343. wflag = 1;
  344. seconds = strtol(optarg, &endptr, 10);
  345. if (strlen(optarg) > endptr - optarg || seconds < 1)
  346. usage_stat();
  347. break;
  348. default:
  349. usage_stat();
  350. }
  351. }
  352. argc -= optind;
  353. argv += optind;
  354. if (argc > 0) {
  355. hashes = malloc(argc * 20);
  356. for (int i = 0; i < argc; i++) {
  357. struct metainfo *mi;
  358. if ((errno = load_metainfo(argv[i], -1, 0, &mi)) != 0)
  359. err(1, "error loading torrent %s", argv[i]);
  360. bcopy(mi->info_hash, hashes[i], 20);
  361. clear_metainfo(mi);
  362. free(mi);
  363. }
  364. }
  365. btpd_connect();
  366. do_stat(iflag, seconds, argc, hashes);
  367. }
  368. struct {
  369. const char *name;
  370. void (*fun)(int, char **);
  371. void (*help)(void);
  372. } cmd_table[] = {
  373. { "add", cmd_add, usage_add },
  374. { "del", cmd_del, usage_del },
  375. { "kill", cmd_kill, usage_kill },
  376. { "list", cmd_list, usage_list },
  377. { "stat", cmd_stat, usage_stat }
  378. };
  379. int ncmds = sizeof(cmd_table) / sizeof(cmd_table[0]);
  380. void
  381. usage(void)
  382. {
  383. printf(
  384. "btcli is the btpd command line interface.\n"
  385. "\n"
  386. "Usage: btcli [main options] command [command options]\n"
  387. "\n"
  388. "Main options:\n"
  389. "-d dir\n"
  390. "\tThe btpd directory.\n"
  391. "\n"
  392. "--help [command]\n"
  393. "\tShow this text or help for the specified command.\n"
  394. "\n"
  395. "Commands:\n"
  396. "add\n"
  397. "del\n"
  398. "kill\n"
  399. "list\n"
  400. "stat\n"
  401. "\n");
  402. exit(1);
  403. }
  404. struct option base_opts [] = {
  405. { "help", no_argument, NULL, 'H' },
  406. {NULL, 0, NULL, 0}
  407. };
  408. int
  409. main(int argc, char **argv)
  410. {
  411. int ch, help = 0;
  412. if (argc < 2)
  413. usage();
  414. while ((ch = getopt_long(argc, argv, "+d:", base_opts, NULL)) != -1) {
  415. switch (ch) {
  416. case 'd':
  417. btpd_dir = optarg;
  418. break;
  419. case 'H':
  420. help = 1;
  421. break;
  422. default:
  423. usage();
  424. }
  425. }
  426. argc -= optind;
  427. argv += optind;
  428. if (argc == 0)
  429. usage();
  430. if (btpd_dir == NULL)
  431. if ((btpd_dir = find_btpd_dir()) == NULL)
  432. errx(1, "cannot find the btpd directory");
  433. optind = 0;
  434. int found = 0;
  435. for (int i = 0; !found && i < ncmds; i++) {
  436. if (strcmp(argv[0], cmd_table[i].name) == 0) {
  437. found = 1;
  438. if (help)
  439. cmd_table[i].help();
  440. else
  441. cmd_table[i].fun(argc, argv);
  442. }
  443. }
  444. if (!found)
  445. usage();
  446. return 0;
  447. }