diff options
Diffstat (limited to 'cli')
| -rw-r--r-- | cli/btcli.c | 797 | ||||
| -rw-r--r-- | cli/btpd_if.c | 284 | ||||
| -rw-r--r-- | cli/btpd_if.h | 39 |
3 files changed, 491 insertions, 629 deletions
diff --git a/cli/btcli.c b/cli/btcli.c index 11329b6..660b9ad 100644 --- a/cli/btcli.c +++ b/cli/btcli.c @@ -1,587 +1,422 @@ -#include <sys/types.h> -#include <sys/time.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 const char *btpd_dir = "/usr/btpd"; +static struct ipc *ipc; static void -handle_error(int error) +handle_ipc_res(enum ipc_code code) { - switch (error) { - case 0: + switch (code) { + case IPC_OK: + return; + case IPC_FAIL: + warnx("Ipc failed.\n"); break; - case ENOENT: - case ECONNREFUSED: - errx(1, "Couldn't connect. Check that btpd is running."); - default: - errx(1, "%s", strerror(error)); + case IPC_COMMERR: + errx(1, "Communication error.\n"); } } 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) +btpd_connect(void) { - 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 + mi->npieces * - (off_t)ceil(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); + if ((errno = ipc_open(btpd_dir, &ipc)) != 0) + errx(1, "Couldn't connect to btpd in %s (%s).\n", + btpd_dir, strerror(errno)); } -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) +void +usage_add(void) { - struct ipc *ipc; - do_ipc_open(ipctok, &ipc); - handle_error(btpd_add(ipc, paths, npaths, out)); - ipc_close(ipc); + printf( + "Add a torrent to btpd.\n" + "\n" + "Usage: add [-a] [-s] [-c dir] -f file\n" + "\n" + "Options:\n" + "-a\n" + "\tAppend the torrent top directory (if any) to the content path.\n" + "\n" + "-c dir\n" + "\tThe directory where the content is (or will be downloaded to).\n" + "\tDefault is the directory containing the torrent file.\n" + "\n" + "-f file\n" + "\tThe torrent to add.\n" + "\n" + "-s\n" + "\tStart the torrent.\n" + "\n" + ); + exit(1); } -static void +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) +void +usage_del(void) { - struct ipc *ipc; - do_ipc_open(ipctok, &ipc); - handle_error(btpd_del(ipc, hashes, nhashes, out)); - ipc_close(ipc); + printf( + "Remove torrents from btpd.\n" + "\n" + "Usage: del num ...\n" + "\n" + "Arguments:\n" + "num\n" + "\tThe number of the torrent to remove.\n" + "\n"); + exit(1); } -static void +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); + if (argc < 2) + usage_del(); + + unsigned nums[argc - 1]; + char *endptr; + for (int i = 0; i < argc - 1; i++) { + nums[i] = strtoul(argv[i + 1], &endptr, 10); + if (strlen(argv[i + 1]) > endptr - argv[i + 1]) + usage_del(); } - free(res); + btpd_connect(); + for (int i = 0; i < argc -1; i++) + handle_ipc_res(btpd_del_num(ipc, nums[i])); } -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) +void +usage_kill(void) { - struct ipc *ipc; - do_ipc_open(ipctok, &ipc); - handle_error(btpd_die(ipc)); - ipc_close(ipc); + 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); } -static void -cmd_die(int argc, char **argv) +void +cmd_kill(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} -}; + int seconds = -1; + char *endptr; + if (argc == 1) + ; + else if (argc == 2) { + seconds = strtol(argv[1], &endptr, 10); + if (strlen(argv[1]) > endptr - argv[1] || seconds < 0) + usage_kill(); + } else + usage_kill(); -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); + btpd_connect(); + btpd_die(ipc, seconds); } -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) +void +usage_list(void) { - 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; + printf( + "List btpd's torrents.\n" + "\n" + "Usage: list\n" + "\n" + ); + exit(1); } -static void -free_tors(struct tor **tors) +void +cmd_list(int argc, char **argv) { - for (int i = 0; tors[i] != NULL; i++) { - free(tors[i]->path); - free(tors[i]); - } - free(tors); + struct btstat *st; + + if (argc > 1) + usage_list(); + + btpd_connect(); + if ((errno = btpd_stat(ipc, &st)) != 0) + err(1, "btpd_stat"); + for (int i = 0; i < st->ntorrents; i++) + printf("%u. %s (%c)\n", st->torrents[i].num, st->torrents[i].name, + st->torrents[i].state); + printf("Listed %u torrent%s.\n", st->ntorrents, + st->ntorrents == 1 ? "" : "s"); } -static void -print_stat(struct tor *cur, struct tor *old, double ds) +void +usage_stat(void) { - 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) / ds / (1 << 10), - cur->up / (double)(1 << 20), - (cur->up - old->up) / ds / (1 << 10), - (unsigned)cur->npeers - ); - } + printf( + "Display btpd stats.\n" + "\n" + "Usage: stat [-i] [-w seconds]\n" + "\n" + "Options:\n" + "-i\n" + "\tDisplay indivudal lines for each active torrent.\n" + "\n" + "-w n\n" + "\tDisplay stats every n seconds.\n" + "\n"); + exit(1); } -static void -grok_stat(char *ipctok, int iflag, int wait, - uint8_t (*hashes)[20], int nhashes) +void +do_stat(int individual, int seconds) { - int i, j; - char *res; - struct tor **cur, **old = NULL; - struct tor curtot, oldtot; - struct timeval tv_cur, tv_old; - double ds; + struct btstat *st; + struct tpstat tot; again: - do_stat(ipctok, &res); - gettimeofday(&tv_cur, NULL); - if (old == NULL) - ds = wait; - else { - struct timeval delta; - timersub(&tv_old, &tv_cur, &delta); - ds = delta.tv_sec + delta.tv_usec / 1000000.0; - if (ds < 0) - ds = wait; - } - tv_old = tv_cur; - 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, ds); - } 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], ds); - } + bzero(&tot, sizeof(tot)); + if ((errno = btpd_stat(ipc, &st)) != 0) + err(1, "btpd_stat"); + for (int i = 0; i < st->ntorrents; i++) { + struct tpstat *cur = &st->torrents[i]; + if (cur->state != 'A') + continue; + if (!individual) { + tot.uploaded += cur->uploaded; + tot.downloaded += cur->downloaded; + tot.rate_up += cur->rate_up; + tot.rate_down += cur->rate_down; + tot.npeers += cur->npeers; + continue; } + printf("%u. %5.1f%% %6.1fM %7.2fkB/s %6.1fM %7.2fkB/s %4u %5.1f%%", + cur->num, + 100.0 * cur->have / cur->total, + (double)cur->downloaded / (1 << 20), + (double)cur->rate_down / (20 << 10), + (double)cur->uploaded / (1 << 20), + (double)cur->rate_up / (20 << 10), + cur->npeers, + 100.0 * cur->nseen / cur->npieces + ); + if (cur->errors > 0) + printf(" E%u", cur->errors); + printf("\n"); } - - 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; + free_btstat(st); + if (!individual) { + printf("%6.1fM %7.2fkB/s %6.1fM %7.2fkB/s %4u\n", + (double)tot.downloaded / (1 << 20), + (double)tot.rate_down / (20 << 10), + (double)tot.uploaded / (1 << 20), + (double)tot.rate_up / (20 << 10), + tot.npeers); } - if (iflag) - printf("Total:\n"); - if (old != NULL) - print_stat(&curtot, &oldtot, ds); - else - print_stat(&curtot, NULL, ds); - - if (wait) { - if (old != NULL) - free_tors(old); - old = cur; - oldtot = curtot; - sleep(wait); + if (seconds > 0) { + sleep(seconds); goto again; } - free_tors(cur); } -static void +static struct option stat_opts [] = { + { "help", no_argument, NULL, 1 }, + {NULL, 0, NULL, 0} +}; + +void cmd_stat(int argc, char **argv) { int ch; - char *ipctok = NULL; - int wait = 0; - int iflag = 0; - + int wflag = 0, iflag = 0, seconds = 0; + char *endptr; 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; + wflag = 1; + seconds = strtol(optarg, &endptr, 10); + if (strlen(optarg) > endptr - optarg || seconds < 1) + usage_stat(); break; default: - usage(); + usage_stat(); } } argc -= optind; argv += optind; + if (argc > 0) + usage_stat(); - 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); + btpd_connect(); + do_stat(iflag, seconds); } -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) +void +usage_start(void) { - int ch; - char *ipctok = NULL; + printf( + "Activate torrents.\n" + "\n" + "Usage: start num ...\n" + "\n" + "Arguments:\n" + "num\n" + "\tThe number of the torrent to activate.\n" + "\n"); + exit(1); +} - while ((ch = getopt_long(argc, argv, "", list_opts, NULL)) != -1) { - switch (ch) { - case 1: - ipctok = optarg; - break; - default: - usage(); - } +void +cmd_start(int argc, char **argv) +{ + if (argc < 2) + usage_start(); + + unsigned nums[argc - 1]; + char *endptr; + for (int i = 0; i < argc - 1; i++) { + nums[i] = strtoul(argv[i + 1], &endptr, 10); + if (strlen(argv[i + 1]) > endptr - argv[i + 1]) + usage_start(); } - 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); + btpd_connect(); + for (int i = 0; i < argc -1; i++) + handle_ipc_res(btpd_start_num(ipc, nums[i])); +} + +void +usage_stop(void) +{ + printf( + "Deactivate torrents.\n" + "\n" + "Usage: stop num ...\n" + "\n" + "Arguments:\n" + "num\n" + "\tThe number of the torrent to deactivate.\n" + "\n"); + exit(1); +} + +void +cmd_stop(int argc, char **argv) +{ + if (argc < 2) + usage_stop(); + + unsigned nums[argc - 1]; + char *endptr; + for (int i = 0; i < argc - 1; i++) { + nums[i] = strtoul(argv[i + 1], &endptr, 10); + if (strlen(argv[i + 1]) > endptr - argv[i + 1]) + usage_stop(); } - printf("%d torrent%s.\n", count, count == 1 ? "" : "s"); + btpd_connect(); + for (int i = 0; i < argc -1; i++) + handle_ipc_res(btpd_stop_num(ipc, nums[i])); } static struct { const char *name; void (*fun)(int, char **); + void (*help)(void); } cmd_table[] = { - { "add", cmd_add }, - { "del", cmd_del }, - { "die", cmd_die }, - { "list", cmd_list}, - { "stat", cmd_stat } + { "add", cmd_add, usage_add }, + { "del", cmd_del, usage_del }, + { "kill", cmd_kill, usage_kill }, + { "list", cmd_list, usage_list }, + { "start", cmd_start, usage_start }, + { "stat", cmd_stat, usage_stat }, + { "stop", cmd_stop, usage_stop } }; static int ncmds = sizeof(cmd_table) / sizeof(cmd_table[0]); +void +usage(void) +{ + printf( + "btcli is the btpd command line interface. Use this tool to interact\n" + "with a btpd process.\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" + "start\n" + "stat\n" + "stop\n" + "\n"); + exit(1); +} + +static struct option base_opts [] = { + { "help", no_argument, NULL, 1 }, + {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 1: + help = 1; + break; + default: + usage(); + } + } + argc -= optind; + argv += optind; + + if (argc == 0) + usage(); + + optind = 0; int found = 0; for (int i = 0; !found && i < ncmds; i++) { - if (strcmp(argv[1], cmd_table[i].name) == 0) { + if (strcmp(argv[0], cmd_table[i].name) == 0) { found = 1; - cmd_table[i].fun(argc - 1, argv + 1); + if (help) + cmd_table[i].help(); + else + cmd_table[i].fun(argc, argv); } } - + if (!found) usage(); diff --git a/cli/btpd_if.c b/cli/btpd_if.c index 65502ac..a5fa40d 100644 --- a/cli/btpd_if.c +++ b/cli/btpd_if.c @@ -8,34 +8,42 @@ #include <unistd.h> #include "benc.h" -#include "iobuf.h" #include "btpd_if.h" +#include "iobuf.h" +#include "subr.h" + +struct ipc { + int sd; +}; int -ipc_open(const char *key, struct ipc **out) +ipc_open(const char *dir, struct ipc **out) { + int sd = -1, err = 0; size_t plen; - size_t keylen; struct ipc *res; + struct sockaddr_un addr; - if (key == NULL) - key = "default"; - keylen = strlen(key); - for (int i = 0; i < keylen; i++) - if (!isalnum(key[i])) - return EINVAL; + plen = sizeof(addr.sun_path); + if (snprintf(addr.sun_path, plen, "%s/sock", dir) >= plen) + return ENAMETOOLONG; + addr.sun_family = AF_UNIX; - res = malloc(sizeof(*res)); - if (res == NULL) - return ENOMEM; + if ((sd = socket(PF_UNIX, SOCK_STREAM, 0)) == -1) + return errno; - plen = sizeof(res->addr.sun_path); - if (snprintf(res->addr.sun_path, plen, - "/tmp/btpd_%u_%s", geteuid(), key) >= plen) { - free(res); - return ENAMETOOLONG; + if (connect(sd, (struct sockaddr *)&addr, sizeof(addr)) == -1) { + err = errno; + close(sd); + return err; } - res->addr.sun_family = AF_UNIX; + + if ((res = malloc(sizeof(*res))) == NULL) { + close(sd); + return ENOMEM; + } + + res->sd = sd; *out = res; return 0; } @@ -43,179 +51,175 @@ ipc_open(const char *key, struct ipc **out) int ipc_close(struct ipc *ipc) { + int err; + err = close(ipc->sd); 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; + return err; } static int -ipc_response(FILE *fp, char **out, uint32_t *len) +ipc_response(struct ipc *ipc, 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 ((errno = read_fully(ipc->sd, &size, sizeof(size))) != 0) + return errno; if (size == 0) - return EINVAL; + return ECONNRESET; if ((buf = malloc(size)) == NULL) return ENOMEM; - if (fread(buf, 1, size, fp) != size) { - if (ferror(fp)) - return errno; - else - return ECONNRESET; + if ((errno = read_fully(ipc->sd, buf, size)) != 0) { + free(buf); + return errno; } *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) +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) + if ((errno = write_fully(ipc->sd, &qlen, sizeof(qlen))) != 0) goto error; - if (fflush(fp) != 0) + if ((errno = write_fully(ipc->sd, req, qlen)) != 0) goto error; - if ((errno = ipc_response(fp, res, rlen)) != 0) + if ((errno = ipc_response(ipc, res, rlen)) != 0) goto error; if ((errno = benc_validate(*res, *rlen)) != 0) goto error; - - fclose(fp); - return 0; + if (!benc_isdct(*res)) + errno = EINVAL; error: - error = errno; - fclose(fp); - return error; + return errno; } -int -btpd_die(struct ipc *ipc) +static enum ipc_code +ipc_buf_req(struct ipc *ipc, struct io_buffer *iob) { - 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 err; + char *res; + size_t reslen; + + err = ipc_req_res(ipc, iob->buf, iob->buf_off, &res, &reslen); + free(iob->buf); + if (err != 0) + return IPC_COMMERR; + int code; + code = benc_dget_int(res, "code"); + free(res); + return code; } -int -btpd_add(struct ipc *ipc, char **paths, unsigned npaths, char **out) +enum ipc_code +btpd_die(struct ipc *ipc, int seconds) { - int error; struct io_buffer iob; - char *res = NULL; - uint32_t reslen; + buf_init(&iob, 16); + if (seconds >= 0) + buf_print(&iob, "l3:diei%dee", seconds); + else + buf_print(&iob, "l3:diee"); + return ipc_buf_req(ipc, &iob); +} - 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); +enum ipc_code +parse_btstat(const uint8_t *res, struct btstat **out) +{ + int code; + unsigned ntorrents; + const char *tlst; + + code = benc_dget_int(res, "code"); + if (code != IPC_OK) + return code; + + ntorrents = benc_dget_int(res, "ntorrents"); + tlst = benc_dget_lst(res, "torrents"); + + struct btstat *st = + malloc(sizeof(struct btstat) + sizeof(struct tpstat) * ntorrents); + + st->ntorrents = ntorrents; + int i = 0; + for (const char *tp = benc_first(tlst); tp != NULL; tp = benc_next(tp)) { + struct tpstat *ts = &st->torrents[i]; + ts->num = benc_dget_int(tp, "num"); + ts->name = benc_dget_str(tp, "path", NULL); + ts->state = *benc_dget_str(tp, "state", NULL); + if (ts->state == 'A') { + ts->errors = benc_dget_int(tp, "errors"); + ts->npieces = benc_dget_int(tp, "npieces"); + ts->nseen = benc_dget_int(tp, "seen npieces"); + ts->npeers = benc_dget_int(tp, "npeers"); + ts->downloaded = benc_dget_int(tp, "downloaded"); + ts->uploaded = benc_dget_int(tp, "uploaded"); + ts->rate_down = benc_dget_int(tp, "rd"); + ts->rate_up = benc_dget_int(tp, "ru"); + ts->have = benc_dget_int(tp, "have"); + ts->total = benc_dget_int(tp, "total"); + } + i++; } - buf_print(&iob, "e"); - - error = ipc_req_res(ipc, iob.buf, iob.buf_off, &res, &reslen); - free(iob.buf); - if (error == 0) - *out = res; + *out = st; + return IPC_OK; +} - return error; +void +free_btstat(struct btstat *st) +{ + for (unsigned i = 0; i < st->ntorrents; i++) + if (st->torrents[i].name != NULL) + free(st->torrents[i].name); + free(st); } -int -btpd_stat(struct ipc *ipc, char **out) +enum ipc_code +btpd_stat(struct ipc *ipc, struct btstat **out) { + int err; 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; + if ((err = ipc_req_res(ipc, cmd, cmdlen, &res, &reslen)) != 0) + return IPC_COMMERR; + + err = parse_btstat(res, out); + free(res); + return err; } -int -btpd_del(struct ipc *ipc, uint8_t (*hash)[20], unsigned nhashes, char **out) +static enum ipc_code +btpd_common_num(struct ipc *ipc, const char *cmd, unsigned num) { - int error; struct io_buffer iob; - char *res = NULL; - uint32_t reslen; + buf_init(&iob, 16); + buf_print(&iob, "l%d:%si%uee", (int)strlen(cmd), cmd, num); + return ipc_buf_req(ipc, &iob); +} - 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); +enum ipc_code +btpd_del_num(struct ipc *ipc, unsigned num) +{ + return btpd_common_num(ipc, "del", num); +} - error = ipc_req_res(ipc, iob.buf, iob.buf_off, &res, &reslen); - free(iob.buf); - if (error != 0) - return error; +enum ipc_code +btpd_start_num(struct ipc *ipc, unsigned num) +{ + return btpd_common_num(ipc, "start", num); +} - *out = res; - return 0; +enum ipc_code +btpd_stop_num(struct ipc *ipc, unsigned num) +{ + return btpd_common_num(ipc, "stop", num); } diff --git a/cli/btpd_if.h b/cli/btpd_if.h index e67770f..64cdb4b 100644 --- a/cli/btpd_if.h +++ b/cli/btpd_if.h @@ -5,17 +5,40 @@ #include <sys/socket.h> #include <sys/un.h> -struct ipc { - struct sockaddr_un addr; +struct ipc; + +enum ipc_code { + IPC_OK, + IPC_FAIL, + IPC_COMMERR +}; + +struct btstat { + unsigned ntorrents; + struct tpstat { + char *name; + unsigned num; + char state; + + unsigned errors; + unsigned npeers; + uint32_t npieces, nseen; + off_t have, total; + long long downloaded, uploaded; + unsigned long rate_up, rate_down; + } torrents[]; }; -int ipc_open(const char *key, struct ipc **out); +int ipc_open(const char *dir, 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); +enum ipc_code btpd_die(struct ipc *ipc, int seconds); +enum ipc_code btpd_stat(struct ipc *ipc, struct btstat **out); + +enum ipc_code btpd_del_num(struct ipc *ipc, unsigned num); +enum ipc_code btpd_start_num(struct ipc *ipc, unsigned num); +enum ipc_code btpd_stop_num(struct ipc *ipc, unsigned num); + +void free_btstat(struct btstat *stat); #endif |