A clone of btpd with my configuration changes.
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

493 行
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. }