- #include <sys/types.h>
- #include <sys/stat.h>
-
- #include <err.h>
- #include <errno.h>
- #include <fcntl.h>
- #include <getopt.h>
- #include <inttypes.h>
- #include <limits.h>
- #include <math.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <unistd.h>
-
- #include "btpd_if.h"
- #include "metainfo.h"
- #include "subr.h"
-
- const char *btpd_dir;
- struct ipc *ipc;
-
- void
- btpd_connect(void)
- {
- if ((errno = ipc_open(btpd_dir, &ipc)) != 0)
- err(1, "cannot open connection to btpd in %s", btpd_dir);
- }
-
- enum ipc_code
- handle_ipc_res(enum ipc_code code, const char *target)
- {
- switch (code) {
- case IPC_OK:
- break;
- case IPC_FAIL:
- warnx("btpd couldn't execute the requested operation for %s", target);
- break;
- case IPC_ERROR:
- warnx("btpd encountered an error for %s", target);
- break;
- default:
- errx(1, "fatal error in communication with btpd");
- }
- return code;
- }
-
- char
- state_char(struct tpstat *ts)
- {
- switch (ts->state) {
- case T_STARTING:
- return '+';
- case T_ACTIVE:
- return ts->pieces_got == ts->torrent_pieces ? 'S' : 'L';
- case T_STOPPING:
- return '-';
- default:
- return ' ';
- }
- }
-
- void
- print_stat(struct tpstat *ts)
- {
- printf("%c %5.1f%% %6.1fM %7.2fkB/s %6.1fM %7.2fkB/s %4u %5.1f%%",
- state_char(ts),
- floor(1000.0 * ts->content_got / ts->content_size) / 10,
- (double)ts->downloaded / (1 << 20),
- (double)ts->rate_down / (20 << 10),
- (double)ts->uploaded / (1 << 20),
- (double)ts->rate_up / (20 << 10),
- ts->peers,
- floor(1000.0 * ts->pieces_seen / ts->torrent_pieces) / 10);
- if (ts->tr_errors > 0)
- printf(" E%u", ts->tr_errors);
- printf("\n");
- }
-
- void
- usage_add(void)
- {
- printf(
- "Add torrents to btpd.\n"
- "\n"
- "Usage: add [--topdir] -d dir file\n"
- " add file ...\n"
- "\n"
- "Arguments:\n"
- "file ...\n"
- "\tOne or more torrents to add.\n"
- "\n"
- "Options:\n"
- "-d dir\n"
- "\tUse the dir for content.\n"
- "\n"
- "--topdir\n"
- "\tAppend the torrent top directory (if any) to the content path.\n"
- "\tThis option cannot be used without the '-d' option.\n"
- "\n"
- );
- exit(1);
- }
-
- struct option add_opts [] = {
- { "help", no_argument, NULL, 'H' },
- { "topdir", no_argument, NULL, 'T'},
- {NULL, 0, NULL, 0}
- };
-
- int
- content_link(uint8_t *hash, char *buf)
- {
- int n;
- char relpath[41];
- char path[PATH_MAX];
- for (int i = 0; i < 20; i++)
- snprintf(relpath + i * 2, 3, "%.2x", hash[i]);
- snprintf(path, PATH_MAX, "%s/torrents/%s/content", btpd_dir, relpath);
- if ((n = readlink(path, buf, PATH_MAX)) == -1)
- return errno;
- buf[min(n, PATH_MAX)] = '\0';
- return 0;
- }
-
- void
- cmd_add(int argc, char **argv)
- {
- int ch, topdir = 0;
- char *dir = NULL;
-
- while ((ch = getopt_long(argc, argv, "d:", add_opts, NULL)) != -1) {
- switch (ch) {
- case 'T':
- topdir = 1;
- break;
- case 'd':
- dir = optarg;
- break;
- default:
- usage_add();
- }
- }
- argc -= optind;
- argv += optind;
-
- if (argc < 1 || (topdir == 1 && dir == NULL) || (dir != NULL && argc > 1))
- usage_add();
-
- btpd_connect();
- for (int i = 0; i < argc; i++) {
- struct metainfo *mi;
- char rdpath[PATH_MAX], dpath[PATH_MAX], fpath[PATH_MAX];
-
- if ((errno = load_metainfo(argv[i], -1, 0, &mi)) != 0) {
- warn("error loading torrent %s", argv[i]);
- continue;
- }
-
- if ((topdir &&
- !(mi->nfiles == 1
- && strcmp(mi->name, mi->files[0].path) == 0)))
- snprintf(dpath, PATH_MAX, "%s/%s", dir, mi->name);
- else if (dir != NULL)
- strncpy(dpath, dir, PATH_MAX);
- else {
- if (content_link(mi->info_hash, dpath) != 0) {
- warnx("unknown content dir for %s", argv[i]);
- errx(1, "use the '-d' option");
- }
- }
-
- if (mkdir(dpath, 0777) != 0 && errno != EEXIST)
- err(1, "couldn't create directory %s", dpath);
-
- if (realpath(dpath, rdpath) == NULL)
- err(1, "path error on %s", dpath);
-
- if (realpath(argv[i], fpath) == NULL)
- err(1, "path error on %s", fpath);
-
- handle_ipc_res(btpd_add(ipc, mi->info_hash, fpath, rdpath), argv[i]);
- clear_metainfo(mi);
- free(mi);
- }
- }
-
- void
- usage_del(void)
- {
- printf(
- "Remove torrents from btpd.\n"
- "\n"
- "Usage: del file ...\n"
- "\n"
- "Arguments:\n"
- "file ...\n"
- "\tThe torrents to remove.\n"
- "\n");
- exit(1);
- }
-
- void
- cmd_del(int argc, char **argv)
- {
- if (argc < 2)
- usage_del();
-
- btpd_connect();
- for (int i = 1; i < argc; i++) {
- struct metainfo *mi;
- if ((errno = load_metainfo(argv[i], -1, 0, &mi)) != 0) {
- warn("error loading torrent %s", argv[i]);
- continue;
- }
- handle_ipc_res(btpd_del(ipc, mi->info_hash), argv[i]);
- clear_metainfo(mi);
- free(mi);
- }
- }
-
- void
- usage_kill(void)
- {
- printf(
- "Shutdown btpd.\n"
- "\n"
- "Usage: kill [seconds]\n"
- "\n"
- "Arguments:\n"
- "seconds\n"
- "\tThe number of seconds btpd waits before giving up on unresponsive\n"
- "\ttrackers.\n"
- "\n"
- );
- exit(1);
- }
-
- void
- cmd_kill(int argc, char **argv)
- {
- int seconds = -1;
- char *endptr;
-
- if (argc == 2) {
- seconds = strtol(argv[1], &endptr, 10);
- if (strlen(argv[1]) > endptr - argv[1] || seconds < 0)
- usage_kill();
- } else if (argc > 2)
- usage_kill();
-
- btpd_connect();
- handle_ipc_res(btpd_die(ipc, seconds), "kill");
- }
-
- void
- usage_list(void)
- {
- printf(
- "List active torrents.\n"
- "\n"
- "Usage: list\n"
- "\n"
- );
- exit(1);
- }
-
- void
- cmd_list(int argc, char **argv)
- {
- struct btstat *st;
-
- if (argc > 1)
- usage_list();
-
- btpd_connect();
- if (handle_ipc_res(btpd_stat(ipc, &st), "list") != IPC_OK)
- exit(1);
- for (int i = 0; i < st->ntorrents; i++) {
- struct tpstat *ts = &st->torrents[i];
- printf("%c. %s\n", state_char(ts), ts->name);
- }
- printf("%u torrent%s.\n", st->ntorrents,
- st->ntorrents == 1 ? "" : "s");
- }
-
- void
- usage_stat(void)
- {
- printf(
- "Display stats for active torrents.\n"
- "The displayed stats are:\n"
- "%% got, MB down, rate down. MB up, rate up\n"
- "peer count, %% of pieces seen, tracker errors\n"
- "\n"
- "Usage: stat [-i] [-w seconds] [file ...]\n"
- "\n"
- "Arguments:\n"
- "file ...\n"
- "\tOnly display stats for the given torrent(s).\n"
- "\n"
- "Options:\n"
- "-i\n"
- "\tDisplay individual lines for each torrent.\n"
- "\n"
- "-w n\n"
- "\tDisplay stats every n seconds.\n"
- "\n");
- exit(1);
- }
-
- void
- do_stat(int individual, int seconds, int hash_count, uint8_t (*hashes)[20])
- {
- struct btstat *st;
- struct tpstat tot;
- again:
- bzero(&tot, sizeof(tot));
- tot.state = -1;
- if (handle_ipc_res(btpd_stat(ipc, &st), "stat") != IPC_OK)
- exit(1);
- for (int i = 0; i < st->ntorrents; i++) {
- struct tpstat *cur = &st->torrents[i];
- if (hash_count > 0) {
- int found = 0;
- for (int h = 0; !found && h < hash_count; h++)
- if (bcmp(cur->hash, hashes[h], 20) == 0)
- found = 1;
- if (!found)
- continue;
- }
- tot.uploaded += cur->uploaded;
- tot.downloaded += cur->downloaded;
- tot.rate_up += cur->rate_up;
- tot.rate_down += cur->rate_down;
- tot.peers += cur->peers;
- tot.pieces_seen += cur->pieces_seen;
- tot.torrent_pieces += cur->torrent_pieces;
- tot.content_got += cur->content_got;
- tot.content_size += cur->content_size;
- if (cur->tr_errors > 0)
- tot.tr_errors++;
- if (individual) {
- printf("%s:\n", cur->name);
- print_stat(cur);
- }
- }
- free_btstat(st);
- if (individual)
- printf("Total:\n");
- print_stat(&tot);
- if (seconds > 0) {
- sleep(seconds);
- goto again;
- }
- }
-
- struct option stat_opts [] = {
- { "help", no_argument, NULL, 'H' },
- {NULL, 0, NULL, 0}
- };
-
- void
- cmd_stat(int argc, char **argv)
- {
- int ch;
- int wflag = 0, iflag = 0, seconds = 0;
- uint8_t (*hashes)[20] = NULL;
- char *endptr;
- while ((ch = getopt_long(argc, argv, "iw:", stat_opts, NULL)) != -1) {
- switch (ch) {
- case 'i':
- iflag = 1;
- break;
- case 'w':
- wflag = 1;
- seconds = strtol(optarg, &endptr, 10);
- if (strlen(optarg) > endptr - optarg || seconds < 1)
- usage_stat();
- break;
- default:
- usage_stat();
- }
- }
- argc -= optind;
- argv += optind;
-
- if (argc > 0) {
- hashes = malloc(argc * 20);
- for (int i = 0; i < argc; i++) {
- struct metainfo *mi;
- if ((errno = load_metainfo(argv[i], -1, 0, &mi)) != 0)
- err(1, "error loading torrent %s", argv[i]);
- bcopy(mi->info_hash, hashes[i], 20);
- clear_metainfo(mi);
- free(mi);
- }
- }
- btpd_connect();
- do_stat(iflag, seconds, argc, hashes);
- }
-
- struct {
- const char *name;
- void (*fun)(int, char **);
- void (*help)(void);
- } cmd_table[] = {
- { "add", cmd_add, usage_add },
- { "del", cmd_del, usage_del },
- { "kill", cmd_kill, usage_kill },
- { "list", cmd_list, usage_list },
- { "stat", cmd_stat, usage_stat }
- };
-
- int ncmds = sizeof(cmd_table) / sizeof(cmd_table[0]);
-
- void
- usage(void)
- {
- printf(
- "btcli is the btpd command line interface.\n"
- "\n"
- "Usage: btcli [main options] command [command options]\n"
- "\n"
- "Main options:\n"
- "-d dir\n"
- "\tThe btpd directory.\n"
- "\n"
- "--help [command]\n"
- "\tShow this text or help for the specified command.\n"
- "\n"
- "Commands:\n"
- "add\n"
- "del\n"
- "kill\n"
- "list\n"
- "stat\n"
- "\n");
- exit(1);
- }
-
- struct option base_opts [] = {
- { "help", no_argument, NULL, 'H' },
- {NULL, 0, NULL, 0}
- };
-
- int
- main(int argc, char **argv)
- {
- int ch, help = 0;
-
- if (argc < 2)
- usage();
-
- while ((ch = getopt_long(argc, argv, "+d:", base_opts, NULL)) != -1) {
- switch (ch) {
- case 'd':
- btpd_dir = optarg;
- break;
- case 'H':
- help = 1;
- break;
- default:
- usage();
- }
- }
- argc -= optind;
- argv += optind;
-
- if (argc == 0)
- usage();
-
- if (btpd_dir == NULL)
- if ((btpd_dir = find_btpd_dir()) == NULL)
- errx(1, "cannot find the btpd directory");
-
- optind = 0;
- int found = 0;
- for (int i = 0; !found && i < ncmds; i++) {
- if (strcmp(argv[0], cmd_table[i].name) == 0) {
- found = 1;
- if (help)
- cmd_table[i].help();
- else
- cmd_table[i].fun(argc, argv);
- }
- }
-
- if (!found)
- usage();
-
- return 0;
- }
|