diff options
| author | Richard Nyberg <rnyberg@murmeldjur.se> | 2005-06-24 09:51:38 +0000 |
|---|---|---|
| committer | Richard Nyberg <rnyberg@murmeldjur.se> | 2005-06-24 09:51:38 +0000 |
| commit | dd0d462afae75ff243f8cd1528963f9ad489706d (patch) | |
| tree | 2ef874a1fe5212245814d16f4c9b389524aed9d1 /cli/btcli.c | |
| download | btpd-dd0d462afae75ff243f8cd1528963f9ad489706d.tar.gz btpd-dd0d462afae75ff243f8cd1528963f9ad489706d.zip | |
Import btpd-0.1.
git-svn-id: file:///home/rnyberg/svngit/btpd/releases/0.1@1 76a1f634-46fa-0310-9943-bd1476092a85
Diffstat (limited to 'cli/btcli.c')
| -rw-r--r-- | cli/btcli.c | 577 |
1 files changed, 577 insertions, 0 deletions
diff --git a/cli/btcli.c b/cli/btcli.c new file mode 100644 index 0000000..2e266a1 --- /dev/null +++ b/cli/btcli.c @@ -0,0 +1,577 @@ +#include <sys/types.h> + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <inttypes.h> +#include <math.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <openssl/sha.h> + +#include "benc.h" +#include "metainfo.h" +#include "stream.h" +#include "subr.h" +#include "btpd_if.h" + +static void +usage() +{ + printf("Usage: btcli command [options] [files]\n" + "Commands:\n" + "add <file_1> ... [file_n]\n" + "\tAdd the given torrents to btpd.\n" + "\n" + "del <file_1> ... [file_n]\n" + "\tRemove the given torrents from btpd.\n" + "\n" + "die\n" + "\tShut down btpd.\n" + "\n" + "list\n" + "\tList active torrents.\n" + "\n" + "stat [-i] [-w n] [file_1] ... [file_n]\n" + "\tShow stats for either all active or the given torrents.\n" + "\tThe stats displayed are:\n" + "\t%% of pieces seen, %% of pieces verified, \n" + "\tMB down, rate down, MB up, rate up, no peers\n" + "-i\n" + "\tShow stats per torrent in addition to total stats.\n" + "-w n\n" + "\tRepeat every n seconds.\n" + "\n" + "Common options:\n" + "--ipc key\n" + "\tTalk to the btpd started with the same key.\n" + "\n" + "--help\n" + "\tShow this help.\n" + "\n"); + exit(1); +} + +static void +handle_error(int error) +{ + switch (error) { + case 0: + break; + case ENOENT: + case ECONNREFUSED: + errx(1, "Couldn't connect. Check that btpd is running."); + default: + errx(1, "%s", strerror(error)); + } +} + +static void +do_ipc_open(char *ipctok, struct ipc **ipc) +{ + switch (ipc_open(ipctok, ipc)) { + case 0: + break; + case EINVAL: + errx(1, "--ipc argument only takes letters and digits."); + case ENAMETOOLONG: + errx(1, "--ipc argument is too long."); + } +} + +struct cb { + char *path; + uint8_t *piece_field; + uint32_t have; + struct metainfo *meta; +}; + +static void +hash_cb(uint32_t index, uint8_t *hash, void *arg) +{ + struct cb *cb = arg; + if (hash != NULL) + if (bcmp(hash, cb->meta->piece_hash[index], SHA_DIGEST_LENGTH) == 0) { + set_bit(cb->piece_field, index); + cb->have++; + } + printf("\rTested: %5.1f%%", 100.0 * (index + 1) / cb->meta->npieces); + fflush(stdout); +} + +static int +fd_cb(const char *path, int *fd, void *arg) +{ + struct cb *fp = arg; + return vopen(fd, O_RDONLY, "%s.d/%s", fp->path, path); +} + +static void +gen_ifile(char *path) +{ + int fd; + struct cb cb; + struct metainfo *mi; + size_t field_len; + + if ((errno = load_metainfo(path, -1, 1, &mi)) != 0) + err(1, "load_metainfo: %s", path); + + field_len = ceil(mi->npieces / 8.0); + cb.path = path; + cb.piece_field = calloc(1, field_len); + cb.have = 0; + cb.meta = mi; + + if (cb.piece_field == NULL) + errx(1, "Out of memory.\n"); + + if ((errno = bts_hashes(mi, fd_cb, hash_cb, &cb)) != 0) + err(1, "bts_hashes"); + printf("\nHave: %5.1f%%\n", 100.0 * cb.have / cb.meta->npieces); + + if ((errno = vopen(&fd, O_WRONLY|O_CREAT, "%s.i", path)) != 0) + err(1, "opening %s.i", path); + + if (ftruncate(fd, + field_len + + (off_t)ceil(mi->npieces * mi->piece_length / (double)(1<<17))) < 0) + err(1, "ftruncate: %s", path); + + if (write(fd, cb.piece_field, field_len) != field_len) + err(1, "write %s.i", path); + + if (close(fd) < 0) + err(1, "close %s.i", path); + + clear_metainfo(mi); + free(mi); +} + +static struct option add_opts[] = { + { "ipc", required_argument, NULL, 1 }, + { "help", required_argument, NULL, 2}, + {NULL, 0, NULL, 0} +}; + +static void +do_add(char *ipctok, char **paths, int npaths, char **out) +{ + struct ipc *ipc; + do_ipc_open(ipctok, &ipc); + handle_error(btpd_add(ipc, paths, npaths, out)); + ipc_close(ipc); +} + +static void +cmd_add(int argc, char **argv) +{ + int ch; + char *ipctok = NULL; + while ((ch = getopt_long(argc, argv, "", add_opts, NULL)) != -1) { + switch(ch) { + case 1: + ipctok = optarg; + break; + default: + usage(); + } + } + argc -= optind; + argv += optind; + + if (argc < 1) + usage(); + + for (int i = 0; i < argc; i++) { + int64_t code; + char *res; + int fd; + char *path; + errno = vopen(&fd, O_RDONLY, "%s.i", argv[i]); + if (errno == ENOENT) { + printf("Testing %s for content.\n", argv[i]); + gen_ifile(argv[i]); + } else if (errno != 0) + err(1, "open %s.i", argv[i]); + else + close(fd); + + if ((errno = canon_path(argv[i], &path)) != 0) + err(1, "canon_path"); + do_add(ipctok, &path, 1, &res); + free(path); + benc_dget_int64(benc_first(res), "code", &code); + if (code == EEXIST) + printf("btpd already had %s.\n", argv[i]); + else if (code != 0) { + printf("btpd indicates error: %s for %s.\n", + strerror(code), argv[i]); + } + free(res); + } +} + +static struct option del_opts[] = { + { "ipc", required_argument, NULL, 1 }, + { "help", required_argument, NULL, 2}, + {NULL, 0, NULL, 0} +}; + +static void +do_del(char *ipctok, uint8_t (*hashes)[20], int nhashes, char **out) +{ + struct ipc *ipc; + do_ipc_open(ipctok, &ipc); + handle_error(btpd_del(ipc, hashes, nhashes, out)); + ipc_close(ipc); +} + +static void +cmd_del(int argc, char **argv) +{ + int ch; + char *ipctok = NULL; + while ((ch = getopt_long(argc, argv, "", del_opts, NULL)) != -1) { + switch(ch) { + case 1: + ipctok = optarg; + break; + default: + usage(); + } + } + argc -= optind; + argv += optind; + + if (argc < 1) + usage(); + + uint8_t hashes[argc][20]; + char *res; + const char *d; + + for (int i = 0; i < argc; i++) { + struct metainfo *mi; + if ((errno = load_metainfo(argv[i], -1, 0, &mi)) != 0) + err(1, "load_metainfo: %s", argv[i]); + bcopy(mi->info_hash, hashes[i], 20); + clear_metainfo(mi); + free(mi); + } + + do_del(ipctok, hashes, argc, &res); + d = benc_first(res); + for (int i = 0; i < argc; i++) { + int64_t code; + benc_dget_int64(d, "code", &code); + if (code == ENOENT) + printf("btpd didn't have %s.\n", argv[i]); + else if (code != 0) { + printf("btpd indicates error: %s for %s.\n", + strerror(code), argv[i]); + } + d = benc_next(d); + } + free(res); +} + +static struct option die_opts[] = { + { "ipc", required_argument, NULL, 1 }, + { "help", no_argument, NULL, 2 }, + {NULL, 0, NULL, 0} +}; + +static void +do_die(char *ipctok) +{ + struct ipc *ipc; + do_ipc_open(ipctok, &ipc); + handle_error(btpd_die(ipc)); + ipc_close(ipc); +} + +static void +cmd_die(int argc, char **argv) +{ + int ch; + char *ipctok = NULL; + + while ((ch = getopt_long(argc, argv, "", die_opts, NULL)) != -1) { + switch (ch) { + case 1: + ipctok = optarg; + break; + default: + usage(); + } + } + do_die(ipctok); +} + +static struct option stat_opts[] = { + { "ipc", required_argument, NULL, 1 }, + { "help", no_argument, NULL, 2 }, + {NULL, 0, NULL, 0} +}; + +static void +do_stat(char *ipctok, char **out) +{ + struct ipc *ipc; + do_ipc_open(ipctok, &ipc); + handle_error(btpd_stat(ipc, out)); + ipc_close(ipc); +} + +struct tor { + char *path; + uint8_t hash[20]; + uint64_t down; + uint64_t up; + uint64_t npeers; + uint64_t npieces; + uint64_t have_npieces; + uint64_t seen_npieces; +}; + +struct tor **parse_tors(char *res, uint8_t (*hashes)[20], int nhashes) +{ + struct tor **tors; + int64_t num; + const char *p; + benc_dget_int64(res, "ntorrents", &num); + benc_dget_lst(res, "torrents", &p); + + tors = calloc(sizeof(*tors), num + 1); + int i = 0; + for (p = benc_first(p); p; p = benc_next(p)) { + int j; + const char *hash; + benc_dget_str(p, "hash", &hash, NULL); + + for (j = 0; j < nhashes; j++) { + if (bcmp(hashes[i], hash, 20) == 0) + break; + } + if (j < nhashes || nhashes == 0) { + tors[i] = calloc(sizeof(*tors[i]), 1); + bcopy(hash, tors[i]->hash, 20); + benc_dget_int64(p, "down", &tors[i]->down); + benc_dget_int64(p, "up", &tors[i]->up); + benc_dget_int64(p, "npeers", &tors[i]->npeers); + benc_dget_int64(p, "npieces", &tors[i]->npieces); + benc_dget_int64(p, "have npieces", &tors[i]->have_npieces); + benc_dget_int64(p, "seen npieces", &tors[i]->seen_npieces); + benc_dget_strz(p, "path", &tors[i]->path, NULL); + i++; + } + } + return tors; +} + +static void +free_tors(struct tor **tors) +{ + for (int i = 0; tors[i] != NULL; i++) { + free(tors[i]->path); + free(tors[i]); + } + free(tors); +} + +static void +print_stat(struct tor *cur, struct tor *old, int wait) +{ + if (old == NULL) { + printf("%5.1f%% %5.1f%% %6.1fM - kB/s %6.1fM - kB/s %4u\n", + 100 * cur->seen_npieces / (double)cur->npieces, + 100 * cur->have_npieces / (double)cur->npieces, + cur->down / (double)(1 << 20), + cur->up / (double)(1 << 20), + (unsigned)cur->npeers); + } else { + printf("%5.1f%% %5.1f%% %6.1fM %7.2fkB/s %6.1fM %7.2fkB/s %4u\n", + 100 * cur->seen_npieces / (double)cur->npieces, + 100 * cur->have_npieces / (double)cur->npieces, + cur->down / (double)(1 << 20), + (cur->down - old->down) / (double)wait / (double)(1 << 10), + cur->up / (double)(1 << 20), + (cur->up - old->up) / (double)wait / (double)(1 << 10), + (unsigned)cur->npeers + ); + } +} + +static void +grok_stat(char *ipctok, int iflag, int wait, + uint8_t (*hashes)[20], int nhashes) +{ + int i, j; + char *res; + struct tor **cur, **old = NULL; + struct tor curtot, oldtot; + +again: + do_stat(ipctok, &res); + cur = parse_tors(res, hashes, nhashes); + free(res); + + if (iflag) { + for (i = 0; cur[i] != NULL; i++) { + if (old == NULL) { + printf("%s:\n", rindex(cur[i]->path, '/') + 1); + print_stat(cur[i], NULL, wait); + } else { + for (j = 0; old[j] != NULL; j++) + if (bcmp(cur[i]->hash, old[j]->hash, 20) == 0) + break; + printf("%s:\n", rindex(cur[i]->path, '/') + 1); + print_stat(cur[i], old[j], wait); + } + } + } + + bzero(&curtot, sizeof(curtot)); + for (i = 0; cur[i] != NULL; i++) { + curtot.down += cur[i]->down; + curtot.up += cur[i]->up; + curtot.npeers += cur[i]->npeers; + curtot.npieces += cur[i]->npieces; + curtot.have_npieces += cur[i]->have_npieces; + curtot.seen_npieces += cur[i]->seen_npieces; + } + if (iflag) + printf("Total:\n"); + if (old != NULL) + print_stat(&curtot, &oldtot, wait); + else + print_stat(&curtot, NULL, wait); + + if (wait) { + if (old != NULL) + free_tors(old); + old = cur; + oldtot = curtot; + sleep(wait); + goto again; + } + free_tors(cur); +} + +static void +cmd_stat(int argc, char **argv) +{ + int ch; + char *ipctok = NULL; + int wait = 0; + int iflag = 0; + + while ((ch = getopt_long(argc, argv, "iw:", stat_opts, NULL)) != -1) { + switch (ch) { + case 'i': + iflag = 1; + break; + case 'w': + wait = atoi(optarg); + if (wait <= 0) + errx(1, "-w argument must be an integer > 0."); + break; + case 1: + ipctok = optarg; + break; + default: + usage(); + } + } + argc -= optind; + argv += optind; + + if (argc > 0) { + uint8_t hashes[argc][20]; + for (int i = 0; i < argc; i++) { + struct metainfo *mi; + if ((errno = load_metainfo(argv[i], -1, 0, &mi)) != 0) + err(1, "load_metainfo: %s", argv[i]); + bcopy(mi->info_hash, hashes[i], 20); + clear_metainfo(mi); + free(mi); + } + grok_stat(ipctok, iflag, wait, hashes, argc); + } else + grok_stat(ipctok, iflag, wait, NULL, 0); +} + +static struct option list_opts[] = { + { "ipc", required_argument, NULL, 1 }, + { "help", no_argument, NULL, 2 }, + {NULL, 0, NULL, 0} +}; + +static void +cmd_list(int argc, char **argv) +{ + int ch; + char *ipctok = NULL; + + while ((ch = getopt_long(argc, argv, "", list_opts, NULL)) != -1) { + switch (ch) { + case 1: + ipctok = optarg; + break; + default: + usage(); + } + } + char *res; + const char *p; + char *path; + do_stat(ipctok, &res); + + benc_dget_lst(res, "torrents", &p); + int count = 0; + for (p = benc_first(p); p; p = benc_next(p)) { + count++; + benc_dget_strz(p, "path", &path, NULL); + printf("%s\n", path); + free(path); + } + printf("%d torrents.\n", count); +} + +static struct { + const char *name; + void (*fun)(int, char **); +} cmd_table[] = { + { "add", cmd_add }, + { "del", cmd_del }, + { "die", cmd_die }, + { "list", cmd_list}, + { "stat", cmd_stat } +}; + +static int ncmds = sizeof(cmd_table) / sizeof(cmd_table[0]); + +int +main(int argc, char **argv) +{ + if (argc < 2) + usage(); + + int found = 0; + for (int i = 0; !found && i < ncmds; i++) { + if (strcmp(argv[1], cmd_table[i].name) == 0) { + found = 1; + cmd_table[i].fun(argc - 1, argv + 1); + } + } + + if (!found) + usage(); + + return 0; +} |