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.

590 lines
14 KiB

  1. #include <sys/types.h>
  2. #include <sys/time.h>
  3. #include <err.h>
  4. #include <errno.h>
  5. #include <fcntl.h>
  6. #include <getopt.h>
  7. #include <inttypes.h>
  8. #include <math.h>
  9. #include <stdio.h>
  10. #include <stdlib.h>
  11. #include <string.h>
  12. #include <unistd.h>
  13. #include <openssl/sha.h>
  14. #include "benc.h"
  15. #include "metainfo.h"
  16. #include "stream.h"
  17. #include "subr.h"
  18. #include "btpd_if.h"
  19. static void
  20. usage()
  21. {
  22. printf("Usage: btcli command [options] [files]\n"
  23. "Commands:\n"
  24. "add <file_1> ... [file_n]\n"
  25. "\tAdd the given torrents to btpd.\n"
  26. "\n"
  27. "del <file_1> ... [file_n]\n"
  28. "\tRemove the given torrents from btpd.\n"
  29. "\n"
  30. "die\n"
  31. "\tShut down btpd.\n"
  32. "\n"
  33. "list\n"
  34. "\tList active torrents.\n"
  35. "\n"
  36. "stat [-i] [-w n] [file_1] ... [file_n]\n"
  37. "\tShow stats for either all active or the given torrents.\n"
  38. "\tThe stats displayed are:\n"
  39. "\t%% of pieces seen, %% of pieces verified, \n"
  40. "\tMB down, rate down, MB up, rate up, no peers\n"
  41. "-i\n"
  42. "\tShow stats per torrent in addition to total stats.\n"
  43. "-w n\n"
  44. "\tRepeat every n seconds.\n"
  45. "\n"
  46. "Common options:\n"
  47. "--ipc key\n"
  48. "\tTalk to the btpd started with the same key.\n"
  49. "\n"
  50. "--help\n"
  51. "\tShow this help.\n"
  52. "\n");
  53. exit(1);
  54. }
  55. static void
  56. handle_error(int error)
  57. {
  58. switch (error) {
  59. case 0:
  60. break;
  61. case ENOENT:
  62. case ECONNREFUSED:
  63. errx(1, "Couldn't connect. Check that btpd is running.");
  64. default:
  65. errx(1, "%s", strerror(error));
  66. }
  67. }
  68. static void
  69. do_ipc_open(char *ipctok, struct ipc **ipc)
  70. {
  71. switch (ipc_open(ipctok, ipc)) {
  72. case 0:
  73. break;
  74. case EINVAL:
  75. errx(1, "--ipc argument only takes letters and digits.");
  76. case ENAMETOOLONG:
  77. errx(1, "--ipc argument is too long.");
  78. }
  79. }
  80. struct cb {
  81. char *path;
  82. uint8_t *piece_field;
  83. uint32_t have;
  84. struct metainfo *meta;
  85. };
  86. static void
  87. hash_cb(uint32_t index, uint8_t *hash, void *arg)
  88. {
  89. struct cb *cb = arg;
  90. if (hash != NULL)
  91. if (bcmp(hash, cb->meta->piece_hash[index], SHA_DIGEST_LENGTH) == 0) {
  92. set_bit(cb->piece_field, index);
  93. cb->have++;
  94. }
  95. printf("\rTested: %5.1f%%", 100.0 * (index + 1) / cb->meta->npieces);
  96. fflush(stdout);
  97. }
  98. static int
  99. fd_cb(const char *path, int *fd, void *arg)
  100. {
  101. struct cb *fp = arg;
  102. return vopen(fd, O_RDONLY, "%s.d/%s", fp->path, path);
  103. }
  104. static void
  105. gen_ifile(char *path)
  106. {
  107. int fd;
  108. struct cb cb;
  109. struct metainfo *mi;
  110. size_t field_len;
  111. if ((errno = load_metainfo(path, -1, 1, &mi)) != 0)
  112. err(1, "load_metainfo: %s", path);
  113. field_len = ceil(mi->npieces / 8.0);
  114. cb.path = path;
  115. cb.piece_field = calloc(1, field_len);
  116. cb.have = 0;
  117. cb.meta = mi;
  118. if (cb.piece_field == NULL)
  119. errx(1, "Out of memory.\n");
  120. if ((errno = bts_hashes(mi, fd_cb, hash_cb, &cb)) != 0)
  121. err(1, "bts_hashes");
  122. printf("\nHave: %5.1f%%\n", 100.0 * cb.have / cb.meta->npieces);
  123. if ((errno = vopen(&fd, O_WRONLY|O_CREAT, "%s.i", path)) != 0)
  124. err(1, "opening %s.i", path);
  125. if (ftruncate(fd, field_len + mi->npieces *
  126. (off_t)ceil(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, double ds)
  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) / ds / (1 << 10),
  359. cur->up / (double)(1 << 20),
  360. (cur->up - old->up) / ds / (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. struct timeval tv_cur, tv_old;
  374. double ds;
  375. again:
  376. do_stat(ipctok, &res);
  377. gettimeofday(&tv_cur, NULL);
  378. if (old == NULL)
  379. ds = wait;
  380. else {
  381. struct timeval delta;
  382. timersub(&tv_old, &tv_cur, &delta);
  383. ds = delta.tv_sec + delta.tv_usec / 1000000.0;
  384. if (ds < 0)
  385. ds = wait;
  386. }
  387. tv_old = tv_cur;
  388. cur = parse_tors(res, hashes, nhashes);
  389. free(res);
  390. if (iflag) {
  391. for (i = 0; cur[i] != NULL; i++) {
  392. if (old == NULL) {
  393. printf("%s:\n", rindex(cur[i]->path, '/') + 1);
  394. print_stat(cur[i], NULL, ds);
  395. } else {
  396. for (j = 0; old[j] != NULL; j++)
  397. if (bcmp(cur[i]->hash, old[j]->hash, 20) == 0)
  398. break;
  399. printf("%s:\n", rindex(cur[i]->path, '/') + 1);
  400. print_stat(cur[i], old[j], ds);
  401. }
  402. }
  403. }
  404. bzero(&curtot, sizeof(curtot));
  405. for (i = 0; cur[i] != NULL; i++) {
  406. curtot.down += cur[i]->down;
  407. curtot.up += cur[i]->up;
  408. curtot.npeers += cur[i]->npeers;
  409. curtot.npieces += cur[i]->npieces;
  410. curtot.have_npieces += cur[i]->have_npieces;
  411. curtot.seen_npieces += cur[i]->seen_npieces;
  412. }
  413. if (iflag)
  414. printf("Total:\n");
  415. if (old != NULL)
  416. print_stat(&curtot, &oldtot, ds);
  417. else
  418. print_stat(&curtot, NULL, ds);
  419. if (wait) {
  420. if (old != NULL)
  421. free_tors(old);
  422. old = cur;
  423. oldtot = curtot;
  424. sleep(wait);
  425. goto again;
  426. }
  427. free_tors(cur);
  428. }
  429. static void
  430. cmd_stat(int argc, char **argv)
  431. {
  432. int ch;
  433. char *ipctok = NULL;
  434. int wait = 0;
  435. int iflag = 0;
  436. while ((ch = getopt_long(argc, argv, "iw:", stat_opts, NULL)) != -1) {
  437. switch (ch) {
  438. case 'i':
  439. iflag = 1;
  440. break;
  441. case 'w':
  442. wait = atoi(optarg);
  443. if (wait <= 0)
  444. errx(1, "-w argument must be an integer > 0.");
  445. break;
  446. case 1:
  447. ipctok = optarg;
  448. break;
  449. default:
  450. usage();
  451. }
  452. }
  453. argc -= optind;
  454. argv += optind;
  455. if (argc > 0) {
  456. uint8_t hashes[argc][20];
  457. for (int i = 0; i < argc; i++) {
  458. struct metainfo *mi;
  459. if ((errno = load_metainfo(argv[i], -1, 0, &mi)) != 0)
  460. err(1, "load_metainfo: %s", argv[i]);
  461. bcopy(mi->info_hash, hashes[i], 20);
  462. clear_metainfo(mi);
  463. free(mi);
  464. }
  465. grok_stat(ipctok, iflag, wait, hashes, argc);
  466. } else
  467. grok_stat(ipctok, iflag, wait, NULL, 0);
  468. }
  469. static struct option list_opts[] = {
  470. { "ipc", required_argument, NULL, 1 },
  471. { "help", no_argument, NULL, 2 },
  472. {NULL, 0, NULL, 0}
  473. };
  474. static void
  475. cmd_list(int argc, char **argv)
  476. {
  477. int ch;
  478. char *ipctok = NULL;
  479. while ((ch = getopt_long(argc, argv, "", list_opts, NULL)) != -1) {
  480. switch (ch) {
  481. case 1:
  482. ipctok = optarg;
  483. break;
  484. default:
  485. usage();
  486. }
  487. }
  488. char *res;
  489. const char *p;
  490. char *path;
  491. do_stat(ipctok, &res);
  492. benc_dget_lst(res, "torrents", &p);
  493. int count = 0;
  494. for (p = benc_first(p); p; p = benc_next(p)) {
  495. count++;
  496. benc_dget_strz(p, "path", &path, NULL);
  497. printf("%s\n", path);
  498. free(path);
  499. }
  500. printf("%d torrent%s.\n", count, count == 1 ? "" : "s");
  501. }
  502. static struct {
  503. const char *name;
  504. void (*fun)(int, char **);
  505. } cmd_table[] = {
  506. { "add", cmd_add },
  507. { "del", cmd_del },
  508. { "die", cmd_die },
  509. { "list", cmd_list},
  510. { "stat", cmd_stat }
  511. };
  512. static int ncmds = sizeof(cmd_table) / sizeof(cmd_table[0]);
  513. int
  514. main(int argc, char **argv)
  515. {
  516. if (argc < 2)
  517. usage();
  518. int found = 0;
  519. for (int i = 0; !found && i < ncmds; i++) {
  520. if (strcmp(argv[1], cmd_table[i].name) == 0) {
  521. found = 1;
  522. cmd_table[i].fun(argc - 1, argv + 1);
  523. }
  524. }
  525. if (!found)
  526. usage();
  527. return 0;
  528. }