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.

493 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. char
  42. state_char(struct tpstat *ts)
  43. {
  44. switch (ts->state) {
  45. case T_STARTING:
  46. return '+';
  47. case T_ACTIVE:
  48. return ts->pieces_got == ts->torrent_pieces ? 'S' : 'L';
  49. case T_STOPPING:
  50. return '-';
  51. default:
  52. return ' ';
  53. }
  54. }
  55. void
  56. print_stat(struct tpstat *ts)
  57. {
  58. printf("%c %5.1f%% %6.1fM %7.2fkB/s %6.1fM %7.2fkB/s %4u %5.1f%%",
  59. state_char(ts),
  60. 100.0 * ts->content_got / ts->content_size,
  61. (double)ts->downloaded / (1 << 20),
  62. (double)ts->rate_down / (20 << 10),
  63. (double)ts->uploaded / (1 << 20),
  64. (double)ts->rate_up / (20 << 10),
  65. ts->peers,
  66. 100.0 * ts->pieces_seen / ts->torrent_pieces);
  67. if (ts->tr_errors > 0)
  68. printf(" E%u", ts->tr_errors);
  69. printf("\n");
  70. }
  71. void
  72. usage_add(void)
  73. {
  74. printf(
  75. "Add torrents to btpd.\n"
  76. "\n"
  77. "Usage: add [--topdir] -d dir file\n"
  78. " add file ...\n"
  79. "\n"
  80. "Arguments:\n"
  81. "file ...\n"
  82. "\tOne or more torrents to add.\n"
  83. "\n"
  84. "Options:\n"
  85. "-d dir\n"
  86. "\tUse the dir for content.\n"
  87. "\n"
  88. "--topdir\n"
  89. "\tAppend the torrent top directory (if any) to the content path.\n"
  90. "\tThis option cannot be used without the '-d' option.\n"
  91. "\n"
  92. );
  93. exit(1);
  94. }
  95. struct option add_opts [] = {
  96. { "help", no_argument, NULL, 'H' },
  97. { "topdir", no_argument, NULL, 'T'},
  98. {NULL, 0, NULL, 0}
  99. };
  100. int
  101. content_link(uint8_t *hash, char *buf)
  102. {
  103. int n;
  104. char relpath[41];
  105. char path[PATH_MAX];
  106. for (int i = 0; i < 20; i++)
  107. snprintf(relpath + i * 2, 3, "%.2x", hash[i]);
  108. snprintf(path, PATH_MAX, "%s/torrents/%s/content", btpd_dir, relpath);
  109. if ((n = readlink(path, buf, PATH_MAX)) == -1)
  110. return errno;
  111. buf[min(n, PATH_MAX)] = '\0';
  112. return 0;
  113. }
  114. void
  115. cmd_add(int argc, char **argv)
  116. {
  117. int ch, topdir = 0;
  118. char *dir = NULL;
  119. while ((ch = getopt_long(argc, argv, "d:", add_opts, NULL)) != -1) {
  120. switch (ch) {
  121. case 'T':
  122. topdir = 1;
  123. break;
  124. case 'd':
  125. dir = optarg;
  126. break;
  127. default:
  128. usage_add();
  129. }
  130. }
  131. argc -= optind;
  132. argv += optind;
  133. if (argc < 1 || (topdir == 1 && dir == NULL) || (dir != NULL && argc > 1))
  134. usage_add();
  135. btpd_connect();
  136. for (int i = 0; i < argc; i++) {
  137. struct metainfo *mi;
  138. char rdpath[PATH_MAX], dpath[PATH_MAX], fpath[PATH_MAX];
  139. if ((errno = load_metainfo(argv[i], -1, 0, &mi)) != 0) {
  140. warn("error loading torrent %s", argv[i]);
  141. continue;
  142. }
  143. if ((topdir &&
  144. !(mi->nfiles == 1
  145. && strcmp(mi->name, mi->files[0].path) == 0)))
  146. snprintf(dpath, PATH_MAX, "%s/%s", dir, mi->name);
  147. else if (dir != NULL)
  148. strncpy(dpath, dir, PATH_MAX);
  149. else {
  150. if (content_link(mi->info_hash, dpath) != 0) {
  151. warnx("unknown content dir for %s", argv[i]);
  152. errx(1, "use the '-d' option");
  153. }
  154. }
  155. if (mkdir(dpath, 0777) != 0 && errno != EEXIST)
  156. err(1, "couldn't create directory %s", dpath);
  157. if (realpath(dpath, rdpath) == NULL)
  158. err(1, "path error on %s", dpath);
  159. if (realpath(argv[i], fpath) == NULL)
  160. err(1, "path error on %s", fpath);
  161. handle_ipc_res(btpd_add(ipc, mi->info_hash, fpath, rdpath), argv[i]);
  162. clear_metainfo(mi);
  163. free(mi);
  164. }
  165. }
  166. void
  167. usage_del(void)
  168. {
  169. printf(
  170. "Remove torrents from btpd.\n"
  171. "\n"
  172. "Usage: del file ...\n"
  173. "\n"
  174. "Arguments:\n"
  175. "file ...\n"
  176. "\tThe torrents to remove.\n"
  177. "\n");
  178. exit(1);
  179. }
  180. void
  181. cmd_del(int argc, char **argv)
  182. {
  183. if (argc < 2)
  184. usage_del();
  185. btpd_connect();
  186. for (int i = 1; i < argc; i++) {
  187. struct metainfo *mi;
  188. if ((errno = load_metainfo(argv[i], -1, 0, &mi)) != 0) {
  189. warn("error loading torrent %s", argv[i]);
  190. continue;
  191. }
  192. handle_ipc_res(btpd_del(ipc, mi->info_hash), argv[i]);
  193. clear_metainfo(mi);
  194. free(mi);
  195. }
  196. }
  197. void
  198. usage_kill(void)
  199. {
  200. printf(
  201. "Shutdown btpd.\n"
  202. "\n"
  203. "Usage: kill [seconds]\n"
  204. "\n"
  205. "Arguments:\n"
  206. "seconds\n"
  207. "\tThe number of seconds btpd waits before giving up on unresponsive\n"
  208. "\ttrackers.\n"
  209. "\n"
  210. );
  211. exit(1);
  212. }
  213. void
  214. cmd_kill(int argc, char **argv)
  215. {
  216. int seconds = -1;
  217. char *endptr;
  218. if (argc == 2) {
  219. seconds = strtol(argv[1], &endptr, 10);
  220. if (strlen(argv[1]) > endptr - argv[1] || seconds < 0)
  221. usage_kill();
  222. } else if (argc > 2)
  223. usage_kill();
  224. btpd_connect();
  225. handle_ipc_res(btpd_die(ipc, seconds), "kill");
  226. }
  227. void
  228. usage_list(void)
  229. {
  230. printf(
  231. "List active torrents.\n"
  232. "\n"
  233. "Usage: list\n"
  234. "\n"
  235. );
  236. exit(1);
  237. }
  238. void
  239. cmd_list(int argc, char **argv)
  240. {
  241. struct btstat *st;
  242. if (argc > 1)
  243. usage_list();
  244. btpd_connect();
  245. if (handle_ipc_res(btpd_stat(ipc, &st), "list") != IPC_OK)
  246. exit(1);
  247. for (int i = 0; i < st->ntorrents; i++) {
  248. struct tpstat *ts = &st->torrents[i];
  249. printf("%c. %s\n", state_char(ts), ts->name);
  250. }
  251. printf("%u torrent%s.\n", st->ntorrents,
  252. st->ntorrents == 1 ? "" : "s");
  253. }
  254. void
  255. usage_stat(void)
  256. {
  257. printf(
  258. "Display stats for active torrents.\n"
  259. "The displayed stats are:\n"
  260. "%% got, MB down, rate down. MB up, rate up\n"
  261. "peer count, %% of pieces seen, tracker errors\n"
  262. "\n"
  263. "Usage: stat [-i] [-w seconds] [file ...]\n"
  264. "\n"
  265. "Arguments:\n"
  266. "file ...\n"
  267. "\tOnly display stats for the given torrent(s).\n"
  268. "\n"
  269. "Options:\n"
  270. "-i\n"
  271. "\tDisplay individual lines for each torrent.\n"
  272. "\n"
  273. "-w n\n"
  274. "\tDisplay stats every n seconds.\n"
  275. "\n");
  276. exit(1);
  277. }
  278. void
  279. do_stat(int individual, int seconds, int hash_count, uint8_t (*hashes)[20])
  280. {
  281. struct btstat *st;
  282. struct tpstat tot;
  283. again:
  284. bzero(&tot, sizeof(tot));
  285. tot.state = -1;
  286. if (handle_ipc_res(btpd_stat(ipc, &st), "stat") != IPC_OK)
  287. exit(1);
  288. for (int i = 0; i < st->ntorrents; i++) {
  289. struct tpstat *cur = &st->torrents[i];
  290. if (hash_count > 0) {
  291. int found = 0;
  292. for (int h = 0; !found && h < hash_count; h++)
  293. if (bcmp(cur->hash, hashes[h], 20) == 0)
  294. found = 1;
  295. if (!found)
  296. continue;
  297. }
  298. tot.uploaded += cur->uploaded;
  299. tot.downloaded += cur->downloaded;
  300. tot.rate_up += cur->rate_up;
  301. tot.rate_down += cur->rate_down;
  302. tot.peers += cur->peers;
  303. tot.pieces_seen += cur->pieces_seen;
  304. tot.torrent_pieces += cur->torrent_pieces;
  305. tot.content_got += cur->content_got;
  306. tot.content_size += cur->content_size;
  307. if (cur->tr_errors > 0)
  308. tot.tr_errors++;
  309. if (individual) {
  310. printf("%s:\n", cur->name);
  311. print_stat(cur);
  312. }
  313. }
  314. free_btstat(st);
  315. if (individual)
  316. printf("Total:\n");
  317. print_stat(&tot);
  318. if (seconds > 0) {
  319. sleep(seconds);
  320. goto again;
  321. }
  322. }
  323. struct option stat_opts [] = {
  324. { "help", no_argument, NULL, 'H' },
  325. {NULL, 0, NULL, 0}
  326. };
  327. void
  328. cmd_stat(int argc, char **argv)
  329. {
  330. int ch;
  331. int wflag = 0, iflag = 0, seconds = 0;
  332. uint8_t (*hashes)[20] = NULL;
  333. char *endptr;
  334. while ((ch = getopt_long(argc, argv, "iw:", stat_opts, NULL)) != -1) {
  335. switch (ch) {
  336. case 'i':
  337. iflag = 1;
  338. break;
  339. case 'w':
  340. wflag = 1;
  341. seconds = strtol(optarg, &endptr, 10);
  342. if (strlen(optarg) > endptr - optarg || seconds < 1)
  343. usage_stat();
  344. break;
  345. default:
  346. usage_stat();
  347. }
  348. }
  349. argc -= optind;
  350. argv += optind;
  351. if (argc > 0) {
  352. hashes = malloc(argc * 20);
  353. for (int i = 0; i < argc; i++) {
  354. struct metainfo *mi;
  355. if ((errno = load_metainfo(argv[i], -1, 0, &mi)) != 0)
  356. err(1, "error loading torrent %s", argv[i]);
  357. bcopy(mi->info_hash, hashes[i], 20);
  358. clear_metainfo(mi);
  359. free(mi);
  360. }
  361. }
  362. btpd_connect();
  363. do_stat(iflag, seconds, argc, hashes);
  364. }
  365. struct {
  366. const char *name;
  367. void (*fun)(int, char **);
  368. void (*help)(void);
  369. } cmd_table[] = {
  370. { "add", cmd_add, usage_add },
  371. { "del", cmd_del, usage_del },
  372. { "kill", cmd_kill, usage_kill },
  373. { "list", cmd_list, usage_list },
  374. { "stat", cmd_stat, usage_stat }
  375. };
  376. int ncmds = sizeof(cmd_table) / sizeof(cmd_table[0]);
  377. void
  378. usage(void)
  379. {
  380. printf(
  381. "btcli is the btpd command line interface.\n"
  382. "\n"
  383. "Usage: btcli [main options] command [command options]\n"
  384. "\n"
  385. "Main options:\n"
  386. "-d dir\n"
  387. "\tThe btpd directory.\n"
  388. "\n"
  389. "--help [command]\n"
  390. "\tShow this text or help for the specified command.\n"
  391. "\n"
  392. "Commands:\n"
  393. "add\n"
  394. "del\n"
  395. "kill\n"
  396. "list\n"
  397. "stat\n"
  398. "\n");
  399. exit(1);
  400. }
  401. struct option base_opts [] = {
  402. { "help", no_argument, NULL, 'H' },
  403. {NULL, 0, NULL, 0}
  404. };
  405. int
  406. main(int argc, char **argv)
  407. {
  408. int ch, help = 0;
  409. if (argc < 2)
  410. usage();
  411. while ((ch = getopt_long(argc, argv, "+d:", base_opts, NULL)) != -1) {
  412. switch (ch) {
  413. case 'd':
  414. btpd_dir = optarg;
  415. break;
  416. case 'H':
  417. help = 1;
  418. break;
  419. default:
  420. usage();
  421. }
  422. }
  423. argc -= optind;
  424. argv += optind;
  425. if (argc == 0)
  426. usage();
  427. if (btpd_dir == NULL)
  428. if ((btpd_dir = find_btpd_dir()) == NULL)
  429. errx(1, "cannot find the btpd directory");
  430. optind = 0;
  431. int found = 0;
  432. for (int i = 0; !found && i < ncmds; i++) {
  433. if (strcmp(argv[0], cmd_table[i].name) == 0) {
  434. found = 1;
  435. if (help)
  436. cmd_table[i].help();
  437. else
  438. cmd_table[i].fun(argc, argv);
  439. }
  440. }
  441. if (!found)
  442. usage();
  443. return 0;
  444. }