summary refs log tree commit diff
path: root/cli
diff options
context:
space:
mode:
authorRichard Nyberg <rnyberg@murmeldjur.se>2006-02-05 17:08:39 +0000
committerRichard Nyberg <rnyberg@murmeldjur.se>2006-02-05 17:08:39 +0000
commit01c92051d5eda1c5fe44c7a84766afbd6774ff24 (patch)
treefcf31e1029b3e0590393ca32ed03a21d0df262e9 /cli
parent6559fcb9a42e241979e845bb574bdf3b38e03b90 (diff)
downloadbtpd-01c92051d5eda1c5fe44c7a84766afbd6774ff24.tar.gz
btpd-01c92051d5eda1c5fe44c7a84766afbd6774ff24.zip
o Changed the benc_ api to make it easier to use.
o Lot of work on the cli and its communication with btpd.

Diffstat (limited to 'cli')
-rw-r--r--cli/btcli.c797
-rw-r--r--cli/btpd_if.c284
-rw-r--r--cli/btpd_if.h39
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