A clone of btpd with my configuration changes.
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

497 lignes
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. }