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.

578 lines
13 KiB

  1. #include <sys/types.h>
  2. #include <err.h>
  3. #include <errno.h>
  4. #include <fcntl.h>
  5. #include <getopt.h>
  6. #include <inttypes.h>
  7. #include <math.h>
  8. #include <stdio.h>
  9. #include <stdlib.h>
  10. #include <string.h>
  11. #include <unistd.h>
  12. #include <openssl/sha.h>
  13. #include "benc.h"
  14. #include "metainfo.h"
  15. #include "stream.h"
  16. #include "subr.h"
  17. #include "btpd_if.h"
  18. static void
  19. usage()
  20. {
  21. printf("Usage: btcli command [options] [files]\n"
  22. "Commands:\n"
  23. "add <file_1> ... [file_n]\n"
  24. "\tAdd the given torrents to btpd.\n"
  25. "\n"
  26. "del <file_1> ... [file_n]\n"
  27. "\tRemove the given torrents from btpd.\n"
  28. "\n"
  29. "die\n"
  30. "\tShut down btpd.\n"
  31. "\n"
  32. "list\n"
  33. "\tList active torrents.\n"
  34. "\n"
  35. "stat [-i] [-w n] [file_1] ... [file_n]\n"
  36. "\tShow stats for either all active or the given torrents.\n"
  37. "\tThe stats displayed are:\n"
  38. "\t%% of pieces seen, %% of pieces verified, \n"
  39. "\tMB down, rate down, MB up, rate up, no peers\n"
  40. "-i\n"
  41. "\tShow stats per torrent in addition to total stats.\n"
  42. "-w n\n"
  43. "\tRepeat every n seconds.\n"
  44. "\n"
  45. "Common options:\n"
  46. "--ipc key\n"
  47. "\tTalk to the btpd started with the same key.\n"
  48. "\n"
  49. "--help\n"
  50. "\tShow this help.\n"
  51. "\n");
  52. exit(1);
  53. }
  54. static void
  55. handle_error(int error)
  56. {
  57. switch (error) {
  58. case 0:
  59. break;
  60. case ENOENT:
  61. case ECONNREFUSED:
  62. errx(1, "Couldn't connect. Check that btpd is running.");
  63. default:
  64. errx(1, "%s", strerror(error));
  65. }
  66. }
  67. static void
  68. do_ipc_open(char *ipctok, struct ipc **ipc)
  69. {
  70. switch (ipc_open(ipctok, ipc)) {
  71. case 0:
  72. break;
  73. case EINVAL:
  74. errx(1, "--ipc argument only takes letters and digits.");
  75. case ENAMETOOLONG:
  76. errx(1, "--ipc argument is too long.");
  77. }
  78. }
  79. struct cb {
  80. char *path;
  81. uint8_t *piece_field;
  82. uint32_t have;
  83. struct metainfo *meta;
  84. };
  85. static void
  86. hash_cb(uint32_t index, uint8_t *hash, void *arg)
  87. {
  88. struct cb *cb = arg;
  89. if (hash != NULL)
  90. if (bcmp(hash, cb->meta->piece_hash[index], SHA_DIGEST_LENGTH) == 0) {
  91. set_bit(cb->piece_field, index);
  92. cb->have++;
  93. }
  94. printf("\rTested: %5.1f%%", 100.0 * (index + 1) / cb->meta->npieces);
  95. fflush(stdout);
  96. }
  97. static int
  98. fd_cb(const char *path, int *fd, void *arg)
  99. {
  100. struct cb *fp = arg;
  101. return vopen(fd, O_RDONLY, "%s.d/%s", fp->path, path);
  102. }
  103. static void
  104. gen_ifile(char *path)
  105. {
  106. int fd;
  107. struct cb cb;
  108. struct metainfo *mi;
  109. size_t field_len;
  110. if ((errno = load_metainfo(path, -1, 1, &mi)) != 0)
  111. err(1, "load_metainfo: %s", path);
  112. field_len = ceil(mi->npieces / 8.0);
  113. cb.path = path;
  114. cb.piece_field = calloc(1, field_len);
  115. cb.have = 0;
  116. cb.meta = mi;
  117. if (cb.piece_field == NULL)
  118. errx(1, "Out of memory.\n");
  119. if ((errno = bts_hashes(mi, fd_cb, hash_cb, &cb)) != 0)
  120. err(1, "bts_hashes");
  121. printf("\nHave: %5.1f%%\n", 100.0 * cb.have / cb.meta->npieces);
  122. if ((errno = vopen(&fd, O_WRONLY|O_CREAT, "%s.i", path)) != 0)
  123. err(1, "opening %s.i", path);
  124. if (ftruncate(fd,
  125. field_len +
  126. (off_t)ceil(mi->npieces * mi->piece_length / (double)(1<<17))) < 0)
  127. err(1, "ftruncate: %s", path);
  128. if (write(fd, cb.piece_field, field_len) != field_len)
  129. err(1, "write %s.i", path);
  130. if (close(fd) < 0)
  131. err(1, "close %s.i", path);
  132. clear_metainfo(mi);
  133. free(mi);
  134. }
  135. static struct option add_opts[] = {
  136. { "ipc", required_argument, NULL, 1 },
  137. { "help", required_argument, NULL, 2},
  138. {NULL, 0, NULL, 0}
  139. };
  140. static void
  141. do_add(char *ipctok, char **paths, int npaths, char **out)
  142. {
  143. struct ipc *ipc;
  144. do_ipc_open(ipctok, &ipc);
  145. handle_error(btpd_add(ipc, paths, npaths, out));
  146. ipc_close(ipc);
  147. }
  148. static void
  149. cmd_add(int argc, char **argv)
  150. {
  151. int ch;
  152. char *ipctok = NULL;
  153. while ((ch = getopt_long(argc, argv, "", add_opts, NULL)) != -1) {
  154. switch(ch) {
  155. case 1:
  156. ipctok = optarg;
  157. break;
  158. default:
  159. usage();
  160. }
  161. }
  162. argc -= optind;
  163. argv += optind;
  164. if (argc < 1)
  165. usage();
  166. for (int i = 0; i < argc; i++) {
  167. int64_t code;
  168. char *res;
  169. int fd;
  170. char *path;
  171. errno = vopen(&fd, O_RDONLY, "%s.i", argv[i]);
  172. if (errno == ENOENT) {
  173. printf("Testing %s for content.\n", argv[i]);
  174. gen_ifile(argv[i]);
  175. } else if (errno != 0)
  176. err(1, "open %s.i", argv[i]);
  177. else
  178. close(fd);
  179. if ((errno = canon_path(argv[i], &path)) != 0)
  180. err(1, "canon_path");
  181. do_add(ipctok, &path, 1, &res);
  182. free(path);
  183. benc_dget_int64(benc_first(res), "code", &code);
  184. if (code == EEXIST)
  185. printf("btpd already had %s.\n", argv[i]);
  186. else if (code != 0) {
  187. printf("btpd indicates error: %s for %s.\n",
  188. strerror(code), argv[i]);
  189. }
  190. free(res);
  191. }
  192. }
  193. static struct option del_opts[] = {
  194. { "ipc", required_argument, NULL, 1 },
  195. { "help", required_argument, NULL, 2},
  196. {NULL, 0, NULL, 0}
  197. };
  198. static void
  199. do_del(char *ipctok, uint8_t (*hashes)[20], int nhashes, char **out)
  200. {
  201. struct ipc *ipc;
  202. do_ipc_open(ipctok, &ipc);
  203. handle_error(btpd_del(ipc, hashes, nhashes, out));
  204. ipc_close(ipc);
  205. }
  206. static void
  207. cmd_del(int argc, char **argv)
  208. {
  209. int ch;
  210. char *ipctok = NULL;
  211. while ((ch = getopt_long(argc, argv, "", del_opts, NULL)) != -1) {
  212. switch(ch) {
  213. case 1:
  214. ipctok = optarg;
  215. break;
  216. default:
  217. usage();
  218. }
  219. }
  220. argc -= optind;
  221. argv += optind;
  222. if (argc < 1)
  223. usage();
  224. uint8_t hashes[argc][20];
  225. char *res;
  226. const char *d;
  227. for (int i = 0; i < argc; i++) {
  228. struct metainfo *mi;
  229. if ((errno = load_metainfo(argv[i], -1, 0, &mi)) != 0)
  230. err(1, "load_metainfo: %s", argv[i]);
  231. bcopy(mi->info_hash, hashes[i], 20);
  232. clear_metainfo(mi);
  233. free(mi);
  234. }
  235. do_del(ipctok, hashes, argc, &res);
  236. d = benc_first(res);
  237. for (int i = 0; i < argc; i++) {
  238. int64_t code;
  239. benc_dget_int64(d, "code", &code);
  240. if (code == ENOENT)
  241. printf("btpd didn't have %s.\n", argv[i]);
  242. else if (code != 0) {
  243. printf("btpd indicates error: %s for %s.\n",
  244. strerror(code), argv[i]);
  245. }
  246. d = benc_next(d);
  247. }
  248. free(res);
  249. }
  250. static struct option die_opts[] = {
  251. { "ipc", required_argument, NULL, 1 },
  252. { "help", no_argument, NULL, 2 },
  253. {NULL, 0, NULL, 0}
  254. };
  255. static void
  256. do_die(char *ipctok)
  257. {
  258. struct ipc *ipc;
  259. do_ipc_open(ipctok, &ipc);
  260. handle_error(btpd_die(ipc));
  261. ipc_close(ipc);
  262. }
  263. static void
  264. cmd_die(int argc, char **argv)
  265. {
  266. int ch;
  267. char *ipctok = NULL;
  268. while ((ch = getopt_long(argc, argv, "", die_opts, NULL)) != -1) {
  269. switch (ch) {
  270. case 1:
  271. ipctok = optarg;
  272. break;
  273. default:
  274. usage();
  275. }
  276. }
  277. do_die(ipctok);
  278. }
  279. static struct option stat_opts[] = {
  280. { "ipc", required_argument, NULL, 1 },
  281. { "help", no_argument, NULL, 2 },
  282. {NULL, 0, NULL, 0}
  283. };
  284. static void
  285. do_stat(char *ipctok, char **out)
  286. {
  287. struct ipc *ipc;
  288. do_ipc_open(ipctok, &ipc);
  289. handle_error(btpd_stat(ipc, out));
  290. ipc_close(ipc);
  291. }
  292. struct tor {
  293. char *path;
  294. uint8_t hash[20];
  295. uint64_t down;
  296. uint64_t up;
  297. uint64_t npeers;
  298. uint64_t npieces;
  299. uint64_t have_npieces;
  300. uint64_t seen_npieces;
  301. };
  302. struct tor **parse_tors(char *res, uint8_t (*hashes)[20], int nhashes)
  303. {
  304. struct tor **tors;
  305. int64_t num;
  306. const char *p;
  307. benc_dget_int64(res, "ntorrents", &num);
  308. benc_dget_lst(res, "torrents", &p);
  309. tors = calloc(sizeof(*tors), num + 1);
  310. int i = 0;
  311. for (p = benc_first(p); p; p = benc_next(p)) {
  312. int j;
  313. const char *hash;
  314. benc_dget_str(p, "hash", &hash, NULL);
  315. for (j = 0; j < nhashes; j++) {
  316. if (bcmp(hashes[i], hash, 20) == 0)
  317. break;
  318. }
  319. if (j < nhashes || nhashes == 0) {
  320. tors[i] = calloc(sizeof(*tors[i]), 1);
  321. bcopy(hash, tors[i]->hash, 20);
  322. benc_dget_int64(p, "down", &tors[i]->down);
  323. benc_dget_int64(p, "up", &tors[i]->up);
  324. benc_dget_int64(p, "npeers", &tors[i]->npeers);
  325. benc_dget_int64(p, "npieces", &tors[i]->npieces);
  326. benc_dget_int64(p, "have npieces", &tors[i]->have_npieces);
  327. benc_dget_int64(p, "seen npieces", &tors[i]->seen_npieces);
  328. benc_dget_strz(p, "path", &tors[i]->path, NULL);
  329. i++;
  330. }
  331. }
  332. return tors;
  333. }
  334. static void
  335. free_tors(struct tor **tors)
  336. {
  337. for (int i = 0; tors[i] != NULL; i++) {
  338. free(tors[i]->path);
  339. free(tors[i]);
  340. }
  341. free(tors);
  342. }
  343. static void
  344. print_stat(struct tor *cur, struct tor *old, int wait)
  345. {
  346. if (old == NULL) {
  347. printf("%5.1f%% %5.1f%% %6.1fM - kB/s %6.1fM - kB/s %4u\n",
  348. 100 * cur->seen_npieces / (double)cur->npieces,
  349. 100 * cur->have_npieces / (double)cur->npieces,
  350. cur->down / (double)(1 << 20),
  351. cur->up / (double)(1 << 20),
  352. (unsigned)cur->npeers);
  353. } else {
  354. printf("%5.1f%% %5.1f%% %6.1fM %7.2fkB/s %6.1fM %7.2fkB/s %4u\n",
  355. 100 * cur->seen_npieces / (double)cur->npieces,
  356. 100 * cur->have_npieces / (double)cur->npieces,
  357. cur->down / (double)(1 << 20),
  358. (cur->down - old->down) / (double)wait / (double)(1 << 10),
  359. cur->up / (double)(1 << 20),
  360. (cur->up - old->up) / (double)wait / (double)(1 << 10),
  361. (unsigned)cur->npeers
  362. );
  363. }
  364. }
  365. static void
  366. grok_stat(char *ipctok, int iflag, int wait,
  367. uint8_t (*hashes)[20], int nhashes)
  368. {
  369. int i, j;
  370. char *res;
  371. struct tor **cur, **old = NULL;
  372. struct tor curtot, oldtot;
  373. again:
  374. do_stat(ipctok, &res);
  375. cur = parse_tors(res, hashes, nhashes);
  376. free(res);
  377. if (iflag) {
  378. for (i = 0; cur[i] != NULL; i++) {
  379. if (old == NULL) {
  380. printf("%s:\n", rindex(cur[i]->path, '/') + 1);
  381. print_stat(cur[i], NULL, wait);
  382. } else {
  383. for (j = 0; old[j] != NULL; j++)
  384. if (bcmp(cur[i]->hash, old[j]->hash, 20) == 0)
  385. break;
  386. printf("%s:\n", rindex(cur[i]->path, '/') + 1);
  387. print_stat(cur[i], old[j], wait);
  388. }
  389. }
  390. }
  391. bzero(&curtot, sizeof(curtot));
  392. for (i = 0; cur[i] != NULL; i++) {
  393. curtot.down += cur[i]->down;
  394. curtot.up += cur[i]->up;
  395. curtot.npeers += cur[i]->npeers;
  396. curtot.npieces += cur[i]->npieces;
  397. curtot.have_npieces += cur[i]->have_npieces;
  398. curtot.seen_npieces += cur[i]->seen_npieces;
  399. }
  400. if (iflag)
  401. printf("Total:\n");
  402. if (old != NULL)
  403. print_stat(&curtot, &oldtot, wait);
  404. else
  405. print_stat(&curtot, NULL, wait);
  406. if (wait) {
  407. if (old != NULL)
  408. free_tors(old);
  409. old = cur;
  410. oldtot = curtot;
  411. sleep(wait);
  412. goto again;
  413. }
  414. free_tors(cur);
  415. }
  416. static void
  417. cmd_stat(int argc, char **argv)
  418. {
  419. int ch;
  420. char *ipctok = NULL;
  421. int wait = 0;
  422. int iflag = 0;
  423. while ((ch = getopt_long(argc, argv, "iw:", stat_opts, NULL)) != -1) {
  424. switch (ch) {
  425. case 'i':
  426. iflag = 1;
  427. break;
  428. case 'w':
  429. wait = atoi(optarg);
  430. if (wait <= 0)
  431. errx(1, "-w argument must be an integer > 0.");
  432. break;
  433. case 1:
  434. ipctok = optarg;
  435. break;
  436. default:
  437. usage();
  438. }
  439. }
  440. argc -= optind;
  441. argv += optind;
  442. if (argc > 0) {
  443. uint8_t hashes[argc][20];
  444. for (int i = 0; i < argc; i++) {
  445. struct metainfo *mi;
  446. if ((errno = load_metainfo(argv[i], -1, 0, &mi)) != 0)
  447. err(1, "load_metainfo: %s", argv[i]);
  448. bcopy(mi->info_hash, hashes[i], 20);
  449. clear_metainfo(mi);
  450. free(mi);
  451. }
  452. grok_stat(ipctok, iflag, wait, hashes, argc);
  453. } else
  454. grok_stat(ipctok, iflag, wait, NULL, 0);
  455. }
  456. static struct option list_opts[] = {
  457. { "ipc", required_argument, NULL, 1 },
  458. { "help", no_argument, NULL, 2 },
  459. {NULL, 0, NULL, 0}
  460. };
  461. static void
  462. cmd_list(int argc, char **argv)
  463. {
  464. int ch;
  465. char *ipctok = NULL;
  466. while ((ch = getopt_long(argc, argv, "", list_opts, NULL)) != -1) {
  467. switch (ch) {
  468. case 1:
  469. ipctok = optarg;
  470. break;
  471. default:
  472. usage();
  473. }
  474. }
  475. char *res;
  476. const char *p;
  477. char *path;
  478. do_stat(ipctok, &res);
  479. benc_dget_lst(res, "torrents", &p);
  480. int count = 0;
  481. for (p = benc_first(p); p; p = benc_next(p)) {
  482. count++;
  483. benc_dget_strz(p, "path", &path, NULL);
  484. printf("%s\n", path);
  485. free(path);
  486. }
  487. printf("%d torrents.\n", count);
  488. }
  489. static struct {
  490. const char *name;
  491. void (*fun)(int, char **);
  492. } cmd_table[] = {
  493. { "add", cmd_add },
  494. { "del", cmd_del },
  495. { "die", cmd_die },
  496. { "list", cmd_list},
  497. { "stat", cmd_stat }
  498. };
  499. static int ncmds = sizeof(cmd_table) / sizeof(cmd_table[0]);
  500. int
  501. main(int argc, char **argv)
  502. {
  503. if (argc < 2)
  504. usage();
  505. int found = 0;
  506. for (int i = 0; !found && i < ncmds; i++) {
  507. if (strcmp(argv[1], cmd_table[i].name) == 0) {
  508. found = 1;
  509. cmd_table[i].fun(argc - 1, argv + 1);
  510. }
  511. }
  512. if (!found)
  513. usage();
  514. return 0;
  515. }