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.

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