diff options
Diffstat (limited to 'cli')
| -rw-r--r-- | cli/Makefile.am | 2 | ||||
| -rw-r--r-- | cli/add.c | 87 | ||||
| -rw-r--r-- | cli/btcli.c | 403 | ||||
| -rw-r--r-- | cli/btcli.h | 44 | ||||
| -rw-r--r-- | cli/btinfo.c | 35 | ||||
| -rw-r--r-- | cli/del.c | 30 | ||||
| -rw-r--r-- | cli/kill.c | 35 | ||||
| -rw-r--r-- | cli/list.c | 124 | ||||
| -rw-r--r-- | cli/start.c | 27 | ||||
| -rw-r--r-- | cli/stat.c | 193 | ||||
| -rw-r--r-- | cli/stop.c | 27 |
11 files changed, 633 insertions, 374 deletions
diff --git a/cli/Makefile.am b/cli/Makefile.am index 84e18f9..3222e95 100644 --- a/cli/Makefile.am +++ b/cli/Makefile.am @@ -5,7 +5,7 @@ btinfo_LDADD=../misc/libmisc.a -lcrypto -lm btinfo_CPPFLAGS=-I$(top_srcdir)/misc @openssl_CPPFLAGS@ btinfo_LDFLAGS=@openssl_LDFLAGS@ -btcli_SOURCES=btcli.c btpd_if.c btpd_if.h +btcli_SOURCES=btcli.c btcli.h add.c del.c list.c kill.c start.c stop.c stat.c btcli_LDADD=../misc/libmisc.a -lcrypto -lm btcli_CPPFLAGS=-I$(top_srcdir)/misc @openssl_CPPFLAGS@ btcli_LDFLAGS=@openssl_LDFLAGS@ diff --git a/cli/add.c b/cli/add.c new file mode 100644 index 0000000..34edb1c --- /dev/null +++ b/cli/add.c @@ -0,0 +1,87 @@ +#include "btcli.h" + +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); +} + +static struct option add_opts [] = { + { "help", no_argument, NULL, 'H' }, + { "topdir", no_argument, NULL, 'T'}, + {NULL, 0, NULL, 0} +}; + +void +cmd_add(int argc, char **argv) +{ + int ch, topdir = 0; + size_t dirlen = 0; + char *dir = NULL, *name = NULL; + + while ((ch = getopt_long(argc, argv, "d:n:", add_opts, NULL)) != -1) { + switch (ch) { + case 'T': + topdir = 1; + break; + case 'd': + dir = optarg; + if ((dirlen = strlen(dir)) == 0) + errx(1, "bad option value for -d"); + break; + case 'n': + name = optarg; + break; + default: + usage_add(); + } + } + argc -= optind; + argv += optind; + + if (argc != 1 || dir == NULL) + usage_add(); + + btpd_connect(); + char *mi; + size_t mi_size; + char dpath[PATH_MAX]; + struct io_buffer iob; + + if ((mi = mi_load(argv[0], &mi_size)) == NULL) + err(1, "error loading '%s'", argv[0]); + + buf_init(&iob, PATH_MAX); + buf_write(&iob, dir, dirlen); + if (topdir) { + size_t tdlen; + const char *td = + benc_dget_mem(benc_dget_dct(mi, "info"), "name", &tdlen); + buf_swrite(&iob, "/"); + buf_write(&iob, td, tdlen); + } + buf_swrite(&iob, ""); + if (realpath(iob.buf, dpath) == NULL) + err(1, "realpath '%s'", dpath); + handle_ipc_res(btpd_add(ipc, mi, mi_size, dpath, name), argv[0]); + return; +} diff --git a/cli/btcli.c b/cli/btcli.c index 95ae487..f8b6a81 100644 --- a/cli/btcli.c +++ b/cli/btcli.c @@ -1,21 +1,4 @@ -#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" +#include "btcli.h" const char *btpd_dir; struct ipc *ipc; @@ -27,377 +10,55 @@ btpd_connect(void) err(1, "cannot open connection to btpd in %s", btpd_dir); } -enum ipc_code -handle_ipc_res(enum ipc_code code, const char *target) +enum ipc_err +handle_ipc_res(enum ipc_err 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: + case IPC_COMMERR: errx(1, "fatal error in communication with btpd"); + default: + warnx("btpd response for '%s': %s", target, ipc_strerror(code)); } return code; } char -state_char(struct tpstat *ts) +tstate_char(enum ipc_tstate ts) { - switch (ts->state) { - case T_STARTING: + switch (ts) { + case IPC_TSTATE_INACTIVE: + return 'I'; + case IPC_TSTATE_START: return '+'; - case T_ACTIVE: - return ts->pieces_got == ts->torrent_pieces ? 'S' : 'L'; - case T_STOPPING: + case IPC_TSTATE_STOP: return '-'; - default: - return ' '; + case IPC_TSTATE_LEECH: + return 'L'; + case IPC_TSTATE_SEED: + return 'S'; } + errx(1, "bad state"); } -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) +torrent_spec(char *arg, struct ipc_torrent *tp) { - 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(); - } + char *p; + tp->u.num = strtoul(arg, &p, 10); + if (*p == '\0') { + tp->by_hash = 0; + return 1; } - 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); - } + if ((p = mi_load(arg, NULL)) == NULL) { + warnx("bad torrent '%s' (%s)", arg, strerror(errno)); + return 0; } - btpd_connect(); - do_stat(iflag, seconds, argc, hashes); + tp->by_hash = 1; + mi_info_hash(p, tp->u.hash); + free(p); + return 1; } struct { @@ -409,6 +70,8 @@ struct { { "del", cmd_del, usage_del }, { "kill", cmd_kill, usage_kill }, { "list", cmd_list, usage_list }, + { "start", cmd_start, usage_start }, + { "stop", cmd_stop, usage_stop }, { "stat", cmd_stat, usage_stat } }; @@ -434,7 +97,9 @@ usage(void) "del\n" "kill\n" "list\n" + "start\n" "stat\n" + "stop\n" "\n"); exit(1); } diff --git a/cli/btcli.h b/cli/btcli.h new file mode 100644 index 0000000..5b64662 --- /dev/null +++ b/cli/btcli.h @@ -0,0 +1,44 @@ +#ifndef BTCLI_H +#define BTCLI_H + +#include <err.h> +#include <errno.h> +#include <getopt.h> +#include <inttypes.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "btpd_if.h" +#include "metainfo.h" +#include "subr.h" +#include "benc.h" +#include "iobuf.h" +#include "queue.h" + +extern const char *btpd_dir; +extern struct ipc *ipc; + +void btpd_connect(void); +enum ipc_err handle_ipc_res(enum ipc_err err, const char *target); + +char tstate_char(enum ipc_tstate ts); +int torrent_spec(char *arg, struct ipc_torrent *tp); + +void usage_add(void); +void cmd_add(int argc, char **argv); +void usage_del(void); +void cmd_del(int argc, char **argv); +void usage_list(void); +void cmd_list(int argc, char **argv); +void usage_stat(void); +void cmd_stat(int argc, char **argv); +void usage_kill(void); +void cmd_kill(int argc, char **argv); +void usage_start(void); +void cmd_start(int argc, char **argv); +void usage_stop(void); +void cmd_stop(int argc, char **argv); + +#endif diff --git a/cli/btinfo.c b/cli/btinfo.c index f451c85..cfd1991 100644 --- a/cli/btinfo.c +++ b/cli/btinfo.c @@ -8,6 +8,7 @@ #include <stdlib.h> #include "metainfo.h" +#include "subr.h" static void usage() @@ -21,11 +22,38 @@ static struct option longopts[] = { { NULL, 0, NULL, 0 } }; +static void +print_metainfo(const char *mi) +{ + uint8_t hash[20]; + char hex[SHAHEXSIZE]; + char *name = mi_name(mi); + unsigned nfiles = mi_nfiles(mi); + struct mi_file *files = mi_files(mi); + struct mi_announce *ann = mi_announce(mi); + for (int i = 0; i < ann->ntiers; i++) + for (int j = 0; j < ann->tiers[i].nurls; j++) + printf("%d: %s\n", i, ann->tiers[i].urls[j]); + printf("\n"); + mi_free_announce(ann); + mi_info_hash(mi, hash); + bin2hex(hash, hex, 20); + printf("name: %s\n", name); + printf("info hash: %s\n", hex); + printf("length: %jd\n", (intmax_t)mi_total_length(mi)); + printf("piece length: %jd\n", (intmax_t)mi_piece_length(mi)); + printf("files: %u\n", nfiles); + for (unsigned i = 0; i < nfiles; i++) + printf("%s(%jd)\n", files[i].path, (intmax_t)files[i].length); + free(name); +} + int main(int argc, char **argv) { int ch; + srandom(time(NULL)); while ((ch = getopt_long(argc, argv, "", longopts, NULL)) != -1) usage(); @@ -36,13 +64,12 @@ main(int argc, char **argv) usage(); while (argc > 0) { - struct metainfo *mi; + char *mi = NULL; - if ((errno = load_metainfo(*argv, -1, 1, &mi)) != 0) - err(1, "load_metainfo: %s", *argv); + if ((mi = mi_load(*argv, NULL)) == NULL) + err(1, "mi_load: %s", *argv); print_metainfo(mi); - clear_metainfo(mi); free(mi); argc--; diff --git a/cli/del.c b/cli/del.c new file mode 100644 index 0000000..eaa0a1e --- /dev/null +++ b/cli/del.c @@ -0,0 +1,30 @@ +#include "btcli.h" + +void +usage_del(void) +{ + printf( + "Remove torrents from btpd.\n" + "\n" + "Usage: del torrent ...\n" + "\n" + "Arguments:\n" + "file ...\n" + "\tThe torrents to remove.\n" + "\n"); + exit(1); +} + +void +cmd_del(int argc, char **argv) +{ + struct ipc_torrent t; + + if (argc < 2) + usage_del(); + + btpd_connect(); + for (int i = 1; i < argc; i++) + if (torrent_spec(argv[i], &t)) + handle_ipc_res(btpd_del(ipc, &t), argv[i]); +} diff --git a/cli/kill.c b/cli/kill.c new file mode 100644 index 0000000..b2c6862 --- /dev/null +++ b/cli/kill.c @@ -0,0 +1,35 @@ +#include "btcli.h" + +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"); +} diff --git a/cli/list.c b/cli/list.c new file mode 100644 index 0000000..8bdf851 --- /dev/null +++ b/cli/list.c @@ -0,0 +1,124 @@ +#include "btcli.h" + +void +usage_list(void) +{ + printf( + "List torrents.\n" + "\n" + "Usage: list [-a] [-i]\n" + "\n" + ); + exit(1); +} + +struct item { + unsigned num; + char *name; + char st; + BTPDQ_ENTRY(item) entry; +}; + +struct items { + int count; + BTPDQ_HEAD(item_tq, item) hd; +}; + +void +itm_insert(struct items *itms, struct item *itm) +{ + struct item *p; + BTPDQ_FOREACH(p, &itms->hd, entry) + if (itm->num < p->num) +#if 0 + if (strcmp(itm->name, p->name) < 0) +#endif + break; + if (p != NULL) + BTPDQ_INSERT_BEFORE(p, itm, entry); + else + BTPDQ_INSERT_TAIL(&itms->hd, itm, entry); +} + +static void +list_cb(int obji, enum ipc_err objerr, struct ipc_get_res *res, void *arg) +{ + struct items *itms = arg; + struct item *itm = calloc(1, sizeof(*itm)); + itms->count++; + itm->num = (unsigned)res[IPC_TVAL_NUM].v.num; + itm->st = tstate_char(res[IPC_TVAL_STATE].v.num); + if (res[IPC_TVAL_NAME].type == IPC_TYPE_ERR) + asprintf(&itm->name, "%s", ipc_strerror(res[IPC_TVAL_NAME].v.num)); + else + asprintf(&itm->name, "%.*s", (int)res[IPC_TVAL_NAME].v.str.l, + res[IPC_TVAL_NAME].v.str.p); + itm_insert(itms, itm); +#if 0 + int *count = arg; + (*count)++; + printf("%4u %c.", (unsigned)res[IPC_TVAL_NUM].v.num, + tstate_char(res[IPC_TVAL_STATE].v.num)); + if (res[IPC_TVAL_NAME].type == IPC_TYPE_ERR) + printf(" %s\n", ipc_strerror(res[IPC_TVAL_NAME].v.num)); + else + printf(" %.*s\n", (int)res[IPC_TVAL_NAME].v.str.l, + res[IPC_TVAL_NAME].v.str.p); +#endif +} + +void +print_items(struct items* itms) +{ + int n; + struct item *p; + BTPDQ_FOREACH(p, &itms->hd, entry) { + n = printf("%u: ", p->num); + while (n < 7) { + putchar(' '); + n++; + } + printf("%c. %s\n", p->st, p->name); + } +} + +static struct option list_opts [] = { + { "help", no_argument, NULL, 'H' }, + {NULL, 0, NULL, 0} +}; + +void +cmd_list(int argc, char **argv) +{ + int ch, /*count = 0,*/ inactive = 0, active = 0; + enum ipc_twc twc; + enum ipc_tval keys[] = { IPC_TVAL_NUM, IPC_TVAL_STATE, IPC_TVAL_NAME }; + struct items itms; + while ((ch = getopt_long(argc, argv, "ai", list_opts, NULL)) != -1) { + switch (ch) { + case 'a': + active = 1; + break; + case 'i': + inactive = 1; + break; + default: + usage_list(); + } + } + + if (inactive == active) + twc = IPC_TWC_ALL; + else if (inactive) + twc = IPC_TWC_INACTIVE; + else + twc = IPC_TWC_ACTIVE; + + btpd_connect(); + printf("NUM ST NAME\n"); + itms.count = 0; + BTPDQ_INIT(&itms.hd); + handle_ipc_res(btpd_tget_wc(ipc, twc, keys, 3, list_cb, &itms), "tget"); + print_items(&itms); + printf("Listed %d torrent%s.\n", itms.count, itms.count == 1 ? "" : "s"); +} diff --git a/cli/start.c b/cli/start.c new file mode 100644 index 0000000..4772bb6 --- /dev/null +++ b/cli/start.c @@ -0,0 +1,27 @@ +#include "btcli.h" + +void +usage_start(void) +{ + printf( + "Start torrents.\n" + "\n" + "Usage: start torrent\n" + "\n" + ); + exit(1); +} + +void +cmd_start(int argc, char **argv) +{ + struct ipc_torrent t; + + if (argc < 2) + usage_start(); + + btpd_connect(); + for (int i = 1; i < argc; i++) + if (torrent_spec(argv[i], &t)) + handle_ipc_res(btpd_start(ipc, &t), argv[i]); +} diff --git a/cli/stat.c b/cli/stat.c new file mode 100644 index 0000000..2af1210 --- /dev/null +++ b/cli/stat.c @@ -0,0 +1,193 @@ +#include <math.h> + +#include "btcli.h" + +void +usage_stat(void) +{ + printf( + "Display stats for active torrents.\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" + "-n\n" + "\tDisplay the name of each torrent. Implies '-i'.\n" + "\n" + "-w n\n" + "\tDisplay stats every n seconds.\n" + "\n"); + exit(1); +} + +struct btstat { + unsigned num; + enum ipc_tstate state; + unsigned peers, tr_errors; + long long content_got, content_size, downloaded, uploaded, rate_up, + rate_down; + uint32_t pieces_seen, torrent_pieces; +}; + +struct cbarg { + int individual, names; + struct btstat tot; +}; + +static enum ipc_tval stkeys[] = { + IPC_TVAL_STATE, + IPC_TVAL_NUM, + IPC_TVAL_NAME, + IPC_TVAL_PCOUNT, + IPC_TVAL_TRERR, + IPC_TVAL_PCCOUNT, + IPC_TVAL_PCSEEN, + IPC_TVAL_SESSUP, + IPC_TVAL_SESSDWN, + IPC_TVAL_RATEUP, + IPC_TVAL_RATEDWN, + IPC_TVAL_CGOT, + IPC_TVAL_CSIZE +}; + +static size_t nstkeys = sizeof(stkeys) / sizeof(stkeys[0]); + +static void +print_stat(struct btstat *st) +{ + printf("%5.1f%% %6.1fM %7.2fkB/s %6.1fM %7.2fkB/s %5u %5.1f%%", + floor(1000.0 * st->content_got / st->content_size) / 10, + (double)st->downloaded / (1 << 20), + (double)st->rate_down / (20 << 10), + (double)st->uploaded / (1 << 20), + (double)st->rate_up / (20 << 10), + st->peers, + floor(1000.0 * st->pieces_seen / st->torrent_pieces) / 10); + if (st->tr_errors > 0) + printf(" E%u", st->tr_errors); + printf("\n"); +} + +void +stat_cb(int obji, enum ipc_err objerr, struct ipc_get_res *res, void *arg) +{ + struct cbarg *cba = arg; + struct btstat st, *tot = &cba->tot; + if (objerr != IPC_OK || res[IPC_TVAL_STATE].v.num == IPC_TSTATE_INACTIVE) + return; + bzero(&st, sizeof(st)); + st.state = res[IPC_TVAL_STATE].v.num; + st.num = res[IPC_TVAL_NUM].v.num; + tot->torrent_pieces += (st.torrent_pieces = res[IPC_TVAL_PCCOUNT].v.num); + tot->pieces_seen += (st.pieces_seen = res[IPC_TVAL_PCSEEN].v.num); + tot->content_got += (st.content_got = res[IPC_TVAL_CGOT].v.num); + tot->content_size += (st.content_size = res[IPC_TVAL_CSIZE].v.num); + tot->downloaded += (st.downloaded = res[IPC_TVAL_SESSDWN].v.num); + tot->uploaded += (st.uploaded = res[IPC_TVAL_SESSUP].v.num); + tot->rate_down += (st.rate_down = res[IPC_TVAL_RATEDWN].v.num); + tot->rate_up += (st.rate_up = res[IPC_TVAL_RATEUP].v.num); + tot->peers += (st.peers = res[IPC_TVAL_PCOUNT].v.num); + if ((st.tr_errors = res[IPC_TVAL_TRERR].v.num) > 0) + tot->tr_errors++; + if (cba->individual) { + if (cba->names) + printf("%.*s\n", (int)res[IPC_TVAL_NAME].v.str.l, + res[IPC_TVAL_NAME].v.str.p); + int n = printf("%u:", st.num); + while (n < 7) { + putchar(' '); + n++; + } + printf("%c. ", tstate_char(st.state)); + print_stat(&st); + } +} + +static void +do_stat(int individual, int names, int seconds, struct ipc_torrent *tps, + int ntps) +{ + enum ipc_err err; + struct cbarg cba; + if (names) + individual = 1; + if (individual) + printf("NUM ST "); + printf(" HAVE DLOAD RTDWN ULOAD RTUP PEERS AVAIL\n"); + cba.individual = individual; + cba.names = names; +again: + bzero(&cba.tot, sizeof(cba.tot)); + cba.tot.state = IPC_TSTATE_INACTIVE; + if (tps == NULL) + err = btpd_tget_wc(ipc, IPC_TWC_ACTIVE, stkeys, nstkeys, + stat_cb, &cba); + else + err = btpd_tget(ipc, tps, ntps, stkeys, nstkeys, stat_cb, &cba); + if (handle_ipc_res(err, "stat") != IPC_OK) + exit(1); + if (names) + printf("-----\n"); + if (individual) + printf("Total: "); + print_stat(&cba.tot); + if (seconds > 0) { + sleep(seconds); + goto again; + } +} + +static 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, nflag = 0, seconds = 0; + struct ipc_torrent *tps = NULL; + int ntps = 0; + char *endptr; + while ((ch = getopt_long(argc, argv, "inw:", stat_opts, NULL)) != -1) { + switch (ch) { + case 'i': + iflag = 1; + break; + case 'n': + nflag = 1; + break; + case 'w': + wflag = 1; + seconds = strtol(optarg, &endptr, 10); + if (*endptr != '\0' || seconds < 1) + usage_stat(); + break; + default: + usage_stat(); + } + } + argc -= optind; + argv += optind; + + if (argc > 0) { + tps = malloc(argc * sizeof(*tps)); + for (int i = 0; i < argc; i++) { + if (torrent_spec(argv[i], &tps[ntps])) + ntps++; + else + exit(1); + + } + } + btpd_connect(); + do_stat(iflag, nflag, seconds, tps, ntps); +} diff --git a/cli/stop.c b/cli/stop.c new file mode 100644 index 0000000..caf68f4 --- /dev/null +++ b/cli/stop.c @@ -0,0 +1,27 @@ +#include "btcli.h" + +void +usage_stop(void) +{ + printf( + "Stop torrents.\n" + "\n" + "Usage: stop torrent ...\n" + "\n" + ); + exit(1); +} + +void +cmd_stop(int argc, char **argv) +{ + struct ipc_torrent t; + + if (argc < 2) + usage_stop(); + + btpd_connect(); + for (int i = 1; i < argc; i++) + if (torrent_spec(argv[i], &t)) + handle_ipc_res(btpd_stop(ipc, &t), argv[i]); +} |