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.

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