A clone of btpd with my configuration changes.
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

497 linhas
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, bdir[PATH_MAX];
  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. if (dir != NULL)
  141. if (realpath(dir, bdir) == NULL)
  142. err(1, "path error on %s", bdir);
  143. btpd_connect();
  144. for (int i = 0; i < argc; i++) {
  145. struct metainfo *mi;
  146. char dpath[PATH_MAX], fpath[PATH_MAX];
  147. if ((errno = load_metainfo(argv[i], -1, 0, &mi)) != 0) {
  148. warn("error loading torrent %s", argv[i]);
  149. continue;
  150. }
  151. if ((topdir &&
  152. !(mi->nfiles == 1
  153. && strcmp(mi->name, mi->files[0].path) == 0)))
  154. snprintf(dpath, PATH_MAX, "%s/%s", bdir, mi->name);
  155. else if (dir != NULL)
  156. strncpy(dpath, bdir, PATH_MAX);
  157. else {
  158. if (content_link(mi->info_hash, dpath) != 0) {
  159. warnx("unknown content dir for %s", argv[i]);
  160. errx(1, "use the '-d' option");
  161. }
  162. }
  163. if (mkdir(dpath, 0777) != 0 && errno != EEXIST)
  164. err(1, "couldn't create directory %s", dpath);
  165. if (realpath(argv[i], fpath) == NULL)
  166. err(1, "path error on %s", fpath);
  167. handle_ipc_res(btpd_add(ipc, mi->info_hash, fpath, dpath), argv[i]);
  168. clear_metainfo(mi);
  169. free(mi);
  170. }
  171. }
  172. void
  173. usage_del(void)
  174. {
  175. printf(
  176. "Remove torrents from btpd.\n"
  177. "\n"
  178. "Usage: del file ...\n"
  179. "\n"
  180. "Arguments:\n"
  181. "file ...\n"
  182. "\tThe torrents to remove.\n"
  183. "\n");
  184. exit(1);
  185. }
  186. void
  187. cmd_del(int argc, char **argv)
  188. {
  189. if (argc < 2)
  190. usage_del();
  191. btpd_connect();
  192. for (int i = 1; i < argc; i++) {
  193. struct metainfo *mi;
  194. if ((errno = load_metainfo(argv[i], -1, 0, &mi)) != 0) {
  195. warn("error loading torrent %s", argv[i]);
  196. continue;
  197. }
  198. handle_ipc_res(btpd_del(ipc, mi->info_hash), argv[i]);
  199. clear_metainfo(mi);
  200. free(mi);
  201. }
  202. }
  203. void
  204. usage_kill(void)
  205. {
  206. printf(
  207. "Shutdown btpd.\n"
  208. "\n"
  209. "Usage: kill [seconds]\n"
  210. "\n"
  211. "Arguments:\n"
  212. "seconds\n"
  213. "\tThe number of seconds btpd waits before giving up on unresponsive\n"
  214. "\ttrackers.\n"
  215. "\n"
  216. );
  217. exit(1);
  218. }
  219. void
  220. cmd_kill(int argc, char **argv)
  221. {
  222. int seconds = -1;
  223. char *endptr;
  224. if (argc == 2) {
  225. seconds = strtol(argv[1], &endptr, 10);
  226. if (strlen(argv[1]) > endptr - argv[1] || seconds < 0)
  227. usage_kill();
  228. } else if (argc > 2)
  229. usage_kill();
  230. btpd_connect();
  231. handle_ipc_res(btpd_die(ipc, seconds), "kill");
  232. }
  233. void
  234. usage_list(void)
  235. {
  236. printf(
  237. "List active torrents.\n"
  238. "\n"
  239. "Usage: list\n"
  240. "\n"
  241. );
  242. exit(1);
  243. }
  244. void
  245. cmd_list(int argc, char **argv)
  246. {
  247. struct btstat *st;
  248. if (argc > 1)
  249. usage_list();
  250. btpd_connect();
  251. if (handle_ipc_res(btpd_stat(ipc, &st), "list") != IPC_OK)
  252. exit(1);
  253. for (int i = 0; i < st->ntorrents; i++) {
  254. print_state_name(&st->torrents[i]);
  255. putchar('\n');
  256. }
  257. printf("%u torrent%s.\n", st->ntorrents,
  258. st->ntorrents == 1 ? "" : "s");
  259. }
  260. void
  261. usage_stat(void)
  262. {
  263. printf(
  264. "Display stats for active torrents.\n"
  265. "The displayed stats are:\n"
  266. "%% got, MB down, rate down. MB up, rate up\n"
  267. "peer count, %% of pieces seen, tracker errors\n"
  268. "\n"
  269. "Usage: stat [-i] [-w seconds] [file ...]\n"
  270. "\n"
  271. "Arguments:\n"
  272. "file ...\n"
  273. "\tOnly display stats for the given torrent(s).\n"
  274. "\n"
  275. "Options:\n"
  276. "-i\n"
  277. "\tDisplay individual lines for each torrent.\n"
  278. "\n"
  279. "-w n\n"
  280. "\tDisplay stats every n seconds.\n"
  281. "\n");
  282. exit(1);
  283. }
  284. void
  285. do_stat(int individual, int seconds, int hash_count, uint8_t (*hashes)[20])
  286. {
  287. struct btstat *st;
  288. struct tpstat tot;
  289. again:
  290. bzero(&tot, sizeof(tot));
  291. if (handle_ipc_res(btpd_stat(ipc, &st), "stat") != IPC_OK)
  292. exit(1);
  293. for (int i = 0; i < st->ntorrents; i++) {
  294. struct tpstat *cur = &st->torrents[i];
  295. if (hash_count > 0) {
  296. int found = 0;
  297. for (int h = 0; !found && h < hash_count; h++)
  298. if (bcmp(cur->hash, hashes[h], 20) == 0)
  299. found = 1;
  300. if (!found)
  301. continue;
  302. }
  303. tot.uploaded += cur->uploaded;
  304. tot.downloaded += cur->downloaded;
  305. tot.rate_up += cur->rate_up;
  306. tot.rate_down += cur->rate_down;
  307. tot.peers += cur->peers;
  308. tot.pieces_seen += cur->pieces_seen;
  309. tot.torrent_pieces += cur->torrent_pieces;
  310. tot.content_got += cur->content_got;
  311. tot.content_size += cur->content_size;
  312. if (individual) {
  313. print_state_name(cur);
  314. printf(":\n");
  315. print_stat(cur);
  316. }
  317. }
  318. free_btstat(st);
  319. if (individual)
  320. printf("Total:\n");
  321. print_stat(&tot);
  322. if (seconds > 0) {
  323. sleep(seconds);
  324. goto again;
  325. }
  326. }
  327. struct option stat_opts [] = {
  328. { "help", no_argument, NULL, 'H' },
  329. {NULL, 0, NULL, 0}
  330. };
  331. void
  332. cmd_stat(int argc, char **argv)
  333. {
  334. int ch;
  335. int wflag = 0, iflag = 0, seconds = 0;
  336. uint8_t (*hashes)[20] = NULL;
  337. char *endptr;
  338. while ((ch = getopt_long(argc, argv, "iw:", stat_opts, NULL)) != -1) {
  339. switch (ch) {
  340. case 'i':
  341. iflag = 1;
  342. break;
  343. case 'w':
  344. wflag = 1;
  345. seconds = strtol(optarg, &endptr, 10);
  346. if (strlen(optarg) > endptr - optarg || seconds < 1)
  347. usage_stat();
  348. break;
  349. default:
  350. usage_stat();
  351. }
  352. }
  353. argc -= optind;
  354. argv += optind;
  355. if (argc > 0) {
  356. hashes = malloc(argc * 20);
  357. for (int i = 0; i < argc; i++) {
  358. struct metainfo *mi;
  359. if ((errno = load_metainfo(argv[i], -1, 0, &mi)) != 0)
  360. err(1, "error loading torrent %s", argv[i]);
  361. bcopy(mi->info_hash, hashes[i], 20);
  362. clear_metainfo(mi);
  363. free(mi);
  364. }
  365. }
  366. btpd_connect();
  367. do_stat(iflag, seconds, argc, hashes);
  368. }
  369. struct {
  370. const char *name;
  371. void (*fun)(int, char **);
  372. void (*help)(void);
  373. } cmd_table[] = {
  374. { "add", cmd_add, usage_add },
  375. { "del", cmd_del, usage_del },
  376. { "kill", cmd_kill, usage_kill },
  377. { "list", cmd_list, usage_list },
  378. { "stat", cmd_stat, usage_stat }
  379. };
  380. int ncmds = sizeof(cmd_table) / sizeof(cmd_table[0]);
  381. void
  382. usage(void)
  383. {
  384. printf(
  385. "btcli is the btpd command line interface.\n"
  386. "\n"
  387. "Usage: btcli [main options] command [command options]\n"
  388. "\n"
  389. "Main options:\n"
  390. "-d dir\n"
  391. "\tThe btpd directory.\n"
  392. "\n"
  393. "--help [command]\n"
  394. "\tShow this text or help for the specified command.\n"
  395. "\n"
  396. "Commands:\n"
  397. "add\n"
  398. "del\n"
  399. "kill\n"
  400. "list\n"
  401. "stat\n"
  402. "\n");
  403. exit(1);
  404. }
  405. struct option base_opts [] = {
  406. { "help", no_argument, NULL, 'H' },
  407. {NULL, 0, NULL, 0}
  408. };
  409. int
  410. main(int argc, char **argv)
  411. {
  412. int ch, help = 0;
  413. if (argc < 2)
  414. usage();
  415. while ((ch = getopt_long(argc, argv, "+d:", base_opts, NULL)) != -1) {
  416. switch (ch) {
  417. case 'd':
  418. btpd_dir = optarg;
  419. break;
  420. case 'H':
  421. help = 1;
  422. break;
  423. default:
  424. usage();
  425. }
  426. }
  427. argc -= optind;
  428. argv += optind;
  429. if (argc == 0)
  430. usage();
  431. if (btpd_dir == NULL)
  432. if ((btpd_dir = find_btpd_dir()) == NULL)
  433. errx(1, "cannot find the btpd directory");
  434. optind = 0;
  435. int found = 0;
  436. for (int i = 0; !found && i < ncmds; i++) {
  437. if (strcmp(argv[0], cmd_table[i].name) == 0) {
  438. found = 1;
  439. if (help)
  440. cmd_table[i].help();
  441. else
  442. cmd_table[i].fun(argc, argv);
  443. }
  444. }
  445. if (!found)
  446. usage();
  447. return 0;
  448. }