A clone of btpd with my configuration changes.
Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

590 lines
13 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,
  126. field_len +
  127. (off_t)ceil(mi->npieces * mi->piece_length / (double)(1<<17))) < 0)
  128. err(1, "ftruncate: %s", path);
  129. if (write(fd, cb.piece_field, field_len) != field_len)
  130. err(1, "write %s.i", path);
  131. if (close(fd) < 0)
  132. err(1, "close %s.i", path);
  133. clear_metainfo(mi);
  134. free(mi);
  135. }
  136. static struct option add_opts[] = {
  137. { "ipc", required_argument, NULL, 1 },
  138. { "help", required_argument, NULL, 2},
  139. {NULL, 0, NULL, 0}
  140. };
  141. static void
  142. do_add(char *ipctok, char **paths, int npaths, char **out)
  143. {
  144. struct ipc *ipc;
  145. do_ipc_open(ipctok, &ipc);
  146. handle_error(btpd_add(ipc, paths, npaths, out));
  147. ipc_close(ipc);
  148. }
  149. static void
  150. cmd_add(int argc, char **argv)
  151. {
  152. int ch;
  153. char *ipctok = NULL;
  154. while ((ch = getopt_long(argc, argv, "", add_opts, NULL)) != -1) {
  155. switch(ch) {
  156. case 1:
  157. ipctok = optarg;
  158. break;
  159. default:
  160. usage();
  161. }
  162. }
  163. argc -= optind;
  164. argv += optind;
  165. if (argc < 1)
  166. usage();
  167. for (int i = 0; i < argc; i++) {
  168. int64_t code;
  169. char *res;
  170. int fd;
  171. char *path;
  172. errno = vopen(&fd, O_RDONLY, "%s.i", argv[i]);
  173. if (errno == ENOENT) {
  174. printf("Testing %s for content.\n", argv[i]);
  175. gen_ifile(argv[i]);
  176. } else if (errno != 0)
  177. err(1, "open %s.i", argv[i]);
  178. else
  179. close(fd);
  180. if ((errno = canon_path(argv[i], &path)) != 0)
  181. err(1, "canon_path");
  182. do_add(ipctok, &path, 1, &res);
  183. free(path);
  184. benc_dget_int64(benc_first(res), "code", &code);
  185. if (code == EEXIST)
  186. printf("btpd already had %s.\n", argv[i]);
  187. else if (code != 0) {
  188. printf("btpd indicates error: %s for %s.\n",
  189. strerror(code), argv[i]);
  190. }
  191. free(res);
  192. }
  193. }
  194. static struct option del_opts[] = {
  195. { "ipc", required_argument, NULL, 1 },
  196. { "help", required_argument, NULL, 2},
  197. {NULL, 0, NULL, 0}
  198. };
  199. static void
  200. do_del(char *ipctok, uint8_t (*hashes)[20], int nhashes, char **out)
  201. {
  202. struct ipc *ipc;
  203. do_ipc_open(ipctok, &ipc);
  204. handle_error(btpd_del(ipc, hashes, nhashes, out));
  205. ipc_close(ipc);
  206. }
  207. static void
  208. cmd_del(int argc, char **argv)
  209. {
  210. int ch;
  211. char *ipctok = NULL;
  212. while ((ch = getopt_long(argc, argv, "", del_opts, NULL)) != -1) {
  213. switch(ch) {
  214. case 1:
  215. ipctok = optarg;
  216. break;
  217. default:
  218. usage();
  219. }
  220. }
  221. argc -= optind;
  222. argv += optind;
  223. if (argc < 1)
  224. usage();
  225. uint8_t hashes[argc][20];
  226. char *res;
  227. const char *d;
  228. for (int i = 0; i < argc; i++) {
  229. struct metainfo *mi;
  230. if ((errno = load_metainfo(argv[i], -1, 0, &mi)) != 0)
  231. err(1, "load_metainfo: %s", argv[i]);
  232. bcopy(mi->info_hash, hashes[i], 20);
  233. clear_metainfo(mi);
  234. free(mi);
  235. }
  236. do_del(ipctok, hashes, argc, &res);
  237. d = benc_first(res);
  238. for (int i = 0; i < argc; i++) {
  239. int64_t code;
  240. benc_dget_int64(d, "code", &code);
  241. if (code == ENOENT)
  242. printf("btpd didn't have %s.\n", argv[i]);
  243. else if (code != 0) {
  244. printf("btpd indicates error: %s for %s.\n",
  245. strerror(code), argv[i]);
  246. }
  247. d = benc_next(d);
  248. }
  249. free(res);
  250. }
  251. static struct option die_opts[] = {
  252. { "ipc", required_argument, NULL, 1 },
  253. { "help", no_argument, NULL, 2 },
  254. {NULL, 0, NULL, 0}
  255. };
  256. static void
  257. do_die(char *ipctok)
  258. {
  259. struct ipc *ipc;
  260. do_ipc_open(ipctok, &ipc);
  261. handle_error(btpd_die(ipc));
  262. ipc_close(ipc);
  263. }
  264. static void
  265. cmd_die(int argc, char **argv)
  266. {
  267. int ch;
  268. char *ipctok = NULL;
  269. while ((ch = getopt_long(argc, argv, "", die_opts, NULL)) != -1) {
  270. switch (ch) {
  271. case 1:
  272. ipctok = optarg;
  273. break;
  274. default:
  275. usage();
  276. }
  277. }
  278. do_die(ipctok);
  279. }
  280. static struct option stat_opts[] = {
  281. { "ipc", required_argument, NULL, 1 },
  282. { "help", no_argument, NULL, 2 },
  283. {NULL, 0, NULL, 0}
  284. };
  285. static void
  286. do_stat(char *ipctok, char **out)
  287. {
  288. struct ipc *ipc;
  289. do_ipc_open(ipctok, &ipc);
  290. handle_error(btpd_stat(ipc, out));
  291. ipc_close(ipc);
  292. }
  293. struct tor {
  294. char *path;
  295. uint8_t hash[20];
  296. uint64_t down;
  297. uint64_t up;
  298. uint64_t npeers;
  299. uint64_t npieces;
  300. uint64_t have_npieces;
  301. uint64_t seen_npieces;
  302. };
  303. struct tor **parse_tors(char *res, uint8_t (*hashes)[20], int nhashes)
  304. {
  305. struct tor **tors;
  306. int64_t num;
  307. const char *p;
  308. benc_dget_int64(res, "ntorrents", &num);
  309. benc_dget_lst(res, "torrents", &p);
  310. tors = calloc(sizeof(*tors), num + 1);
  311. int i = 0;
  312. for (p = benc_first(p); p; p = benc_next(p)) {
  313. int j;
  314. const char *hash;
  315. benc_dget_str(p, "hash", &hash, NULL);
  316. for (j = 0; j < nhashes; j++) {
  317. if (bcmp(hashes[i], hash, 20) == 0)
  318. break;
  319. }
  320. if (j < nhashes || nhashes == 0) {
  321. tors[i] = calloc(sizeof(*tors[i]), 1);
  322. bcopy(hash, tors[i]->hash, 20);
  323. benc_dget_int64(p, "down", &tors[i]->down);
  324. benc_dget_int64(p, "up", &tors[i]->up);
  325. benc_dget_int64(p, "npeers", &tors[i]->npeers);
  326. benc_dget_int64(p, "npieces", &tors[i]->npieces);
  327. benc_dget_int64(p, "have npieces", &tors[i]->have_npieces);
  328. benc_dget_int64(p, "seen npieces", &tors[i]->seen_npieces);
  329. benc_dget_strz(p, "path", &tors[i]->path, NULL);
  330. i++;
  331. }
  332. }
  333. return tors;
  334. }
  335. static void
  336. free_tors(struct tor **tors)
  337. {
  338. for (int i = 0; tors[i] != NULL; i++) {
  339. free(tors[i]->path);
  340. free(tors[i]);
  341. }
  342. free(tors);
  343. }
  344. static void
  345. print_stat(struct tor *cur, struct tor *old, double ds)
  346. {
  347. if (old == NULL) {
  348. printf("%5.1f%% %5.1f%% %6.1fM - kB/s %6.1fM - kB/s %4u\n",
  349. 100 * cur->seen_npieces / (double)cur->npieces,
  350. 100 * cur->have_npieces / (double)cur->npieces,
  351. cur->down / (double)(1 << 20),
  352. cur->up / (double)(1 << 20),
  353. (unsigned)cur->npeers);
  354. } else {
  355. printf("%5.1f%% %5.1f%% %6.1fM %7.2fkB/s %6.1fM %7.2fkB/s %4u\n",
  356. 100 * cur->seen_npieces / (double)cur->npieces,
  357. 100 * cur->have_npieces / (double)cur->npieces,
  358. cur->down / (double)(1 << 20),
  359. (cur->down - old->down) / ds / (1 << 10),
  360. cur->up / (double)(1 << 20),
  361. (cur->up - old->up) / ds / (1 << 10),
  362. (unsigned)cur->npeers
  363. );
  364. }
  365. }
  366. static void
  367. grok_stat(char *ipctok, int iflag, int wait,
  368. uint8_t (*hashes)[20], int nhashes)
  369. {
  370. int i, j;
  371. char *res;
  372. struct tor **cur, **old = NULL;
  373. struct tor curtot, oldtot;
  374. struct timeval tv_cur, tv_old;
  375. double ds;
  376. again:
  377. do_stat(ipctok, &res);
  378. gettimeofday(&tv_cur, NULL);
  379. if (old == NULL)
  380. ds = wait;
  381. else {
  382. struct timeval delta;
  383. timersub(&tv_old, &tv_cur, &delta);
  384. ds = delta.tv_sec + delta.tv_usec / 1000000.0;
  385. if (ds < 0)
  386. ds = wait;
  387. }
  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 torrents.\n", count);
  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. }