diff options
Diffstat (limited to 'cli')
| -rw-r--r-- | cli/Makefile.am | 11 | ||||
| -rw-r--r-- | cli/btcli.c | 577 | ||||
| -rw-r--r-- | cli/btinfo.c | 53 | ||||
| -rw-r--r-- | cli/btpd_if.c | 221 | ||||
| -rw-r--r-- | cli/btpd_if.h | 21 |
5 files changed, 883 insertions, 0 deletions
diff --git a/cli/Makefile.am b/cli/Makefile.am new file mode 100644 index 0000000..84e18f9 --- /dev/null +++ b/cli/Makefile.am @@ -0,0 +1,11 @@ +bin_PROGRAMS=btinfo btcli + +btinfo_SOURCES=btinfo.c +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_LDADD=../misc/libmisc.a -lcrypto -lm +btcli_CPPFLAGS=-I$(top_srcdir)/misc @openssl_CPPFLAGS@ +btcli_LDFLAGS=@openssl_LDFLAGS@ 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; +} diff --git a/cli/btinfo.c b/cli/btinfo.c new file mode 100644 index 0000000..bd4a33b --- /dev/null +++ b/cli/btinfo.c @@ -0,0 +1,53 @@ +#include <sys/types.h> + +#include <err.h> +#include <errno.h> +#include <getopt.h> +#include <inttypes.h> +#include <stdio.h> +#include <stdlib.h> + +#include "metainfo.h" + +static void +usage() +{ + fprintf(stderr, "Usage: btinfo file ...\n\n"); + exit(1); +} + +static struct option longopts[] = { + { "help", no_argument, NULL, 1 }, + { NULL, 0, NULL, 0 } +}; + +int +main(int argc, char **argv) +{ + int ch; + + while ((ch = getopt_long(argc, argv, "", longopts, NULL)) != -1) + usage(); + + argc -= optind; + argv += optind; + + if (argc < 1) + usage(); + + while (argc > 0) { + struct metainfo *mi; + + if ((errno = load_metainfo(*argv, -1, 1, &mi)) != 0) + err(1, "load_metainfo: %s", *argv); + + print_metainfo(mi); + clear_metainfo(mi); + free(mi); + + argc--; + argv++; + } + + return 0; +} diff --git a/cli/btpd_if.c b/cli/btpd_if.c new file mode 100644 index 0000000..9990446 --- /dev/null +++ b/cli/btpd_if.c @@ -0,0 +1,221 @@ +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <inttypes.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "benc.h" +#include "iobuf.h" +#include "btpd_if.h" + +int +ipc_open(const char *key, struct ipc **out) +{ + size_t plen; + size_t keylen; + struct ipc *res; + + if (key == NULL) + key = "default"; + keylen = strlen(key); + for (int i = 0; i < keylen; i++) + if (!isalnum(key[i])) + return EINVAL; + + res = malloc(sizeof(*res)); + if (res == NULL) + return ENOMEM; + + plen = sizeof(res->addr.sun_path); + if (snprintf(res->addr.sun_path, plen, + "/tmp/btpd_%u_%s", geteuid(), key) >= plen) { + free(res); + return ENAMETOOLONG; + } + res->addr.sun_family = AF_UNIX; + *out = res; + return 0; +} + +int +ipc_close(struct ipc *ipc) +{ + free(ipc); + return 0; +} + +static int +ipc_connect(struct ipc *ipc, FILE **out) +{ + FILE *fp; + int sd; + int error; + + if ((sd = socket(PF_UNIX, SOCK_STREAM, 0)) == -1) + return errno; + + if (connect(sd, (struct sockaddr *)&ipc->addr, sizeof(ipc->addr)) == -1) + goto error; + + if ((fp = fdopen(sd, "r+")) == NULL) + goto error; + + *out = fp; + return 0; +error: + error = errno; + close(sd); + return error; +} + +static int +ipc_response(FILE *fp, char **out, uint32_t *len) +{ + uint32_t size; + char *buf; + + if (fread(&size, sizeof(size), 1, fp) != 1) { + if (ferror(fp)) + return errno; + else + return ECONNRESET; + } + + if (size == 0) + return EINVAL; + + if ((buf = malloc(size)) == NULL) + return ENOMEM; + + if (fread(buf, 1, size, fp) != size) { + if (ferror(fp)) + return errno; + else + return ECONNRESET; + } + + *out = buf; + *len = size; + return 0; +} + +static int +ipc_req_res(struct ipc *ipc, + const char *req, uint32_t qlen, + char **res, uint32_t *rlen) +{ + FILE *fp; + int error; + + if ((error = ipc_connect(ipc, &fp)) != 0) + return error; + + if (fwrite(&qlen, sizeof(qlen), 1, fp) != 1) + goto error; + if (fwrite(req, 1, qlen, fp) != qlen) + goto error; + if (fflush(fp) != 0) + goto error; + if ((errno = ipc_response(fp, res, rlen)) != 0) + goto error; + if ((errno = benc_validate(*res, *rlen)) != 0) + goto error; + + fclose(fp); + return 0; +error: + error = errno; + fclose(fp); + return error; +} + +int +btpd_die(struct ipc *ipc) +{ + int error; + char *response = NULL; + const char shutdown[] = "l3:diee"; + uint32_t size = sizeof(shutdown) - 1; + uint32_t rsiz; + + if ((error = ipc_req_res(ipc, shutdown, size, &response, &rsiz)) != 0) + return error; + + error = benc_validate(response, rsiz); + + if (error == 0) { + int64_t tmp; + benc_dget_int64(response, "code", &tmp); + error = tmp; + } + + free(response); + return error; +} + +int +btpd_add(struct ipc *ipc, char **paths, unsigned npaths, char **out) +{ + int error; + struct io_buffer iob; + char *res = NULL; + uint32_t reslen; + + buf_init(&iob, 1024); + buf_print(&iob, "l3:add"); + for (unsigned i = 0; i < npaths; i++) { + int plen = strlen(paths[i]); + buf_print(&iob, "%d:", plen); + buf_write(&iob, paths[i], plen); + } + buf_print(&iob, "e"); + + error = ipc_req_res(ipc, iob.buf, iob.buf_off, &res, &reslen); + free(iob.buf); + if (error == 0) + *out = res; + + return error; +} + +int +btpd_stat(struct ipc *ipc, char **out) +{ + const char cmd[] = "l4:state"; + uint32_t cmdlen = sizeof(cmd) - 1; + char *res; + uint32_t reslen; + + if ((errno = ipc_req_res(ipc, cmd, cmdlen, &res, &reslen)) != 0) + return errno; + *out = res; + return 0; +} + +int +btpd_del(struct ipc *ipc, uint8_t (*hash)[20], unsigned nhashes, char **out) +{ + int error; + struct io_buffer iob; + char *res = NULL; + uint32_t reslen; + + buf_init(&iob, 1024); + buf_write(&iob, "l3:del", 6); + for (unsigned i = 0; i < nhashes; i++) { + buf_write(&iob, "20:", 3); + buf_write(&iob, hash[i], 20); + } + buf_write(&iob, "e", 1); + + error = ipc_req_res(ipc, iob.buf, iob.buf_off, &res, &reslen); + free(iob.buf); + if (error != 0) + return error; + + *out = res; + return 0; +} diff --git a/cli/btpd_if.h b/cli/btpd_if.h new file mode 100644 index 0000000..bda9739 --- /dev/null +++ b/cli/btpd_if.h @@ -0,0 +1,21 @@ +#ifndef BTPD_IF_H +#define BTPD_IF_H + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> + +struct ipc { + struct sockaddr_un addr; +}; + +int ipc_open(const char *key, struct ipc **out); +int ipc_close(struct ipc *ipc); + +int btpd_add(struct ipc *ipc, char **path, unsigned npaths, char **out); +int btpd_del(struct ipc *ipc, uint8_t (*hash)[20], + unsigned nhashes, char **out); +int btpd_die(struct ipc *ipc); +int btpd_stat(struct ipc *ipc, char **out); + +#endif |