From 01c92051d5eda1c5fe44c7a84766afbd6774ff24 Mon Sep 17 00:00:00 2001
From: Richard Nyberg <rnyberg@murmeldjur.se>
Date: Sun, 5 Feb 2006 17:08:39 +0000
Subject: [PATCH] o Changed the benc_ api to make it easier to use. o Lot of
 work on the cli and its communication with btpd.

---
 btpd/cli_if.c      | 260 ++++++++++-----
 btpd/tracker_req.c |  80 ++---
 cli/btcli.c        | 797 ++++++++++++++++++---------------------------
 cli/btpd_if.c      | 284 ++++++++--------
 cli/btpd_if.h      |  39 ++-
 misc/benc.c        | 323 +++++++++++-------
 misc/benc.h        |  50 +--
 misc/metainfo.c    |  64 ++--
 8 files changed, 949 insertions(+), 948 deletions(-)

diff --git a/btpd/cli_if.c b/btpd/cli_if.c
index 9b699f3..f2cdc0b 100644
--- a/btpd/cli_if.c
+++ b/btpd/cli_if.c
@@ -12,58 +12,80 @@
 #include <unistd.h>
 
 #include "btpd.h"
+#include "tracker_req.h"
+
+struct cli {
+    int sd;
+    struct event read;
+};
 
 #define buf_swrite(iob, s) buf_write(iob, s, sizeof(s) - 1)
 
 static struct event m_cli_incoming;
 
-static void
-errdie(int error)
-{
-    if (error != 0)
-        btpd_err("io_buf: %s.\n", strerror(error));
-}
-
-static void
-cmd_stat(int argc, const char *args, FILE *fp)
+static int
+cmd_stat(struct cli *cli, int argc, const char *args)
 {
     struct torrent *tp;
     struct io_buffer iob;
-    errdie(buf_init(&iob, (1 << 14)));
+    buf_init(&iob, (1 << 14));
 
-    errdie(buf_swrite(&iob, "d"));
-    errdie(buf_print(&iob, "6:npeersi%ue", net_npeers));
-    errdie(buf_print(&iob, "9:ntorrentsi%ue", btpd_get_ntorrents()));
-    errdie(buf_swrite(&iob, "8:torrentsl"));
+    buf_swrite(&iob, "d");
+    buf_swrite(&iob, "4:codei0e");
+    buf_print(&iob, "6:npeersi%ue", net_npeers);
+    buf_print(&iob, "9:ntorrentsi%ue", btpd_get_ntorrents());
+    buf_swrite(&iob, "8:torrentsl");
     BTPDQ_FOREACH(tp, btpd_get_torrents(), entry) {
-        if (tp->state != T_ACTIVE)
-            continue;
-
-        uint32_t seen_npieces = 0;
-        for (uint32_t i = 0; i < tp->meta.npieces; i++)
-            if (tp->net->piece_count[i] > 0)
-                seen_npieces++;
-        errdie(buf_print(&iob, "d4:downi%jue", (intmax_t)tp->net->downloaded));
-        errdie(buf_swrite(&iob, "4:hash20:"));
-        errdie(buf_write(&iob, tp->meta.info_hash, 20));
-        errdie(buf_print(&iob, "4:havei%jde", (intmax_t)cm_get_size(tp)));
-        errdie(buf_print(&iob, "6:npeersi%ue", tp->net->npeers));
-        errdie(buf_print(&iob, "7:npiecesi%ue", tp->meta.npieces));
-        errdie(buf_print(&iob, "4:path%d:%s",
-                         (int)strlen(tp->relpath), tp->relpath));
-        errdie(buf_print(&iob, "2:rdi%lue", tp->net->rate_dwn));
-        errdie(buf_print(&iob, "2:rui%lue", tp->net->rate_up));
-        errdie(buf_print(&iob, "12:seen npiecesi%ue", seen_npieces));
-        errdie(buf_print(&iob, "5:totali%jde",
-                   (intmax_t)tp->meta.total_length));
-        errdie(buf_print(&iob, "2:upi%juee", (intmax_t)tp->net->uploaded));
+        if (tp->state == T_ACTIVE) {
+            uint32_t seen_npieces = 0;
+            for (uint32_t i = 0; i < tp->meta.npieces; i++)
+                if (tp->net->piece_count[i] > 0)
+                    seen_npieces++;
+
+            buf_print(&iob, "d4:downi%jue", (intmax_t)tp->net->downloaded);
+            buf_print(&iob, "6:errorsi%ue", tr_errors(tp));
+            buf_swrite(&iob, "4:hash20:");
+            buf_write(&iob, tp->meta.info_hash, 20);
+            buf_print(&iob, "4:havei%jde", (intmax_t)cm_get_size(tp));
+            buf_print(&iob, "6:npeersi%ue", tp->net->npeers);
+            buf_print(&iob, "7:npiecesi%ue", tp->meta.npieces);
+            buf_print(&iob, "3:numi%ue", tp->num);
+            buf_print(&iob, "4:path%d:%s", (int)strlen(tp->meta.name),
+                tp->meta.name);
+            buf_print(&iob, "2:rdi%lue", tp->net->rate_dwn);
+            buf_print(&iob, "2:rui%lue", tp->net->rate_up);
+            buf_print(&iob, "12:seen npiecesi%ue", seen_npieces);
+            buf_swrite(&iob, "5:state1:A");
+            buf_print(&iob, "5:totali%jde", (intmax_t)tp->meta.total_length);
+            buf_print(&iob, "2:upi%juee", (intmax_t)tp->net->uploaded);
+        } else {
+            buf_swrite(&iob, "d4:hash20:");
+            buf_write(&iob, tp->meta.info_hash, 20);
+            buf_print(&iob, "3:numi%ue", tp->num);
+            buf_print(&iob, "4:path%d:%s", (int)strlen(tp->meta.name),
+                tp->meta.name);
+            switch (tp->state) {
+            case T_INACTIVE:
+                buf_swrite(&iob, "5:state1:Ie");
+                break;
+            case T_STARTING:
+                buf_swrite(&iob, "5:state1:Be");
+                break;
+            case T_STOPPING:
+                buf_swrite(&iob, "5:state1:Ee");
+                break;
+            case T_ACTIVE:
+                abort();
+            }
+        }
     }
-    errdie(buf_swrite(&iob, "ee"));
+    buf_swrite(&iob, "ee");
 
     uint32_t len = iob.buf_off;
-    fwrite(&len, sizeof(len), 1, fp);
-    fwrite(iob.buf, 1, iob.buf_off, fp);
+    write_fully(cli->sd, &len, sizeof(len));
+    write_fully(cli->sd, iob.buf, iob.buf_off);
     free(iob.buf);
+    return 0;
 }
 
 #if 0
@@ -71,9 +93,9 @@ static void
 cmd_add(int argc, const char *args, FILE *fp)
 {
     struct io_buffer iob;
-    errdie(buf_init(&iob, (1 << 10)));
+    buf_init(&iob, (1 << 10));
 
-    errdie(buf_write(&iob, "l", 1));
+    buf_write(&iob, "l", 1);
     while (args != NULL) {
         size_t plen;
         char path[PATH_MAX];
@@ -87,16 +109,16 @@ cmd_add(int argc, const char *args, FILE *fp)
         benc_str(args, &pathp, &plen, &args);
 
         if (plen >= PATH_MAX) {
-            errdie(buf_print(&iob, "d4:codei%dee", ENAMETOOLONG));
+            buf_print(&iob, "d4:codei%dee", ENAMETOOLONG);
             continue;
         }
 
         bcopy(pathp, path, plen);
         path[plen] = '\0';
         btpd_log(BTPD_L_BTPD, "add request for %s.\n", path);
-        errdie(buf_print(&iob, "d4:codei%dee", torrent_load(path)));
+        buf_print(&iob, "d4:codei%dee", torrent_load(path));
     }
-    errdie(buf_write(&iob, "e", 1));
+    buf_write(&iob, "e", 1);
 
     uint32_t len = iob.buf_off;
     fwrite(&len, sizeof(len), 1, fp);
@@ -108,9 +130,9 @@ static void
 cmd_del(int argc, const char *args, FILE *fp)
 {
     struct io_buffer iob;
-    errdie(buf_init(&iob, (1 << 10)));
+    buf_init(&iob, (1 << 10));
 
-    errdie(buf_swrite(&iob, "l"));
+    buf_swrite(&iob, "l");
 
     while (args != NULL) {
         size_t len;
@@ -127,13 +149,13 @@ cmd_del(int argc, const char *args, FILE *fp)
         if (tp != NULL) {
             btpd_log(BTPD_L_BTPD, "del request for %s.\n", tp->relpath);
             torrent_unload(tp);
-            errdie(buf_swrite(&iob, "d4:codei0ee"));
+            buf_swrite(&iob, "d4:codei0ee");
         } else {
             btpd_log(BTPD_L_BTPD, "del request didn't match.\n");
-            errdie(buf_print(&iob, "d4:codei%dee", ENOENT));
+            buf_print(&iob, "d4:codei%dee", ENOENT);
         }
     }
-    errdie(buf_swrite(&iob, "e"));
+    buf_swrite(&iob, "e");
 
     uint32_t len = iob.buf_off;
     fwrite(&len, sizeof(len), 1, fp);
@@ -141,78 +163,144 @@ cmd_del(int argc, const char *args, FILE *fp)
     free(iob.buf);
 }
 
-static void
-cmd_die(int argc, const char *args, FILE *fp)
+#endif
+
+static int
+cmd_die(struct cli *cli, int argc, const char *args)
 {
     char res[] = "d4:codei0ee";
     uint32_t len = sizeof(res) - 1;
-    fwrite(&len, sizeof(len), 1, fp);
-    fwrite(res, 1, len, fp);
-    fflush(fp);
+    write_fully(cli->sd, &len, sizeof(len));
+    write_fully(cli->sd, res, len);
     btpd_log(BTPD_L_BTPD, "Someone wants me dead.\n");
-    btpd_shutdown();
+    btpd_shutdown((& (struct timeval) { 0, 0 }));
+    return 0;
+}
+
+static int
+cmd_start(struct cli *cli, int argc, const char *args)
+{
+    if (argc != 1 || !benc_isint(args))
+        return EINVAL;
+
+    int code;
+    unsigned num;
+    num = benc_int(args, NULL);
+    struct torrent *tp = btpd_get_torrent_num(num);
+    if (tp != NULL) {
+        torrent_activate(tp);
+        code = 0;
+    } else
+        code = 1;
+
+    struct io_buffer iob;
+    buf_init(&iob, 16);
+    buf_print(&iob, "d4:codei%dee", code);
+    uint32_t len = iob.buf_off;
+    write_fully(cli->sd, &len, sizeof(len));
+    write_fully(cli->sd, iob.buf, iob.buf_off);
+    return 0;
+}
+
+static int
+cmd_stop(struct cli *cli, int argc, const char *args)
+{
+    btpd_log(BTPD_L_BTPD, "%d\n", argc);
+    if (argc != 1 || !benc_isint(args))
+        return EINVAL;
+
+    int code;
+    unsigned num;
+    num = benc_int(args, NULL);
+    struct torrent *tp = btpd_get_torrent_num(num);
+    if (tp != NULL) {
+        torrent_deactivate(tp);
+        code = 0;
+    } else
+        code = 1;
+
+    struct io_buffer iob;
+    buf_init(&iob, 16);
+    buf_print(&iob, "d4:codei%dee", code);
+    uint32_t len = iob.buf_off;
+    write_fully(cli->sd, &len, sizeof(len));
+    write_fully(cli->sd, iob.buf, iob.buf_off);
+    return 0;
 }
-#endif
 
 static struct {
     const char *name;
     int nlen;
-    void (*fun)(int, const char *, FILE *);
+    int (*fun)(struct cli *cli, int, const char *);
 } cmd_table[] = {
 #if 0
     { "add",    3, cmd_add },
     { "del",    3, cmd_del },
-    { "die",    3, cmd_die },
 #endif
-    { "stat",   4, cmd_stat }
+    { "die",    3, cmd_die },
+    { "start",  5, cmd_start }, 
+    { "stat",   4, cmd_stat },
+    { "stop",   4, cmd_stop }
 };
 
 static int ncmds = sizeof(cmd_table) / sizeof(cmd_table[0]);
 
-static void
-cmd_dispatch(const char *buf, FILE *fp)
+static int
+cmd_dispatch(struct cli *cli, const char *buf)
 {
+    int err = 0;
     size_t cmdlen;
     const char *cmd;
     const char *args;
-    int found = 0;
 
-    benc_str(benc_first(buf), &cmd, &cmdlen, &args);
+    cmd = benc_mem(benc_first(buf), &cmdlen, &args);
 
-    for (int i = 0; !found && i < ncmds; i++) {
-        if (cmdlen == cmd_table[i].nlen &&
-            strncmp(cmd_table[i].name, cmd, cmdlen) == 0) {
-            cmd_table[i].fun(benc_nelems(buf) - 1, args, fp);
-            found = 1;
+    btpd_log(BTPD_L_BTPD, "%.*s\n", (int)cmdlen, cmd);
+    for (int i = 0; i < ncmds; i++) {
+        if ((cmdlen == cmd_table[i].nlen &&
+                strncmp(cmd_table[i].name, cmd, cmdlen) == 0)) {
+            err = cmd_table[i].fun(cli, benc_nelems(buf) - 1, args);
+            break;
         }
     }
+    return err;
 }
 
 static void
-do_ipc(FILE *fp)
+cli_read_cb(int sd, short type, void *arg)
 {
-    uint32_t cmdlen, nread;
-    char *buf;
+    struct cli *cli = arg;
+    uint32_t cmdlen;
+    uint8_t *msg = NULL;
 
-    if (fread(&cmdlen, sizeof(cmdlen), 1, fp) != 1)
-        return;
+    if (read_fully(sd, &cmdlen, sizeof(cmdlen)) != 0)
+        goto error;
 
-    buf = btpd_malloc(cmdlen);
+    msg = btpd_malloc(cmdlen);
+    if (read_fully(sd, msg, cmdlen) != 0)
+        goto error;
 
-    if ((nread = fread(buf, 1, cmdlen, fp)) == cmdlen) {
-        if (benc_validate(buf, cmdlen) == 0 && benc_islst(buf) &&
-            benc_first(buf) != NULL && benc_isstr(benc_first(buf)))
-            cmd_dispatch(buf, fp);
-    }
+    if (!(benc_validate(msg, cmdlen) == 0 && benc_islst(msg) &&
+            benc_first(msg) != NULL && benc_isstr(benc_first(msg))))
+        goto error;
 
-    free(buf);
+    if (cmd_dispatch(cli, msg) != 0)
+        goto error;
+
+    event_add(&cli->read, NULL);
+    return;
+
+error:
+    close(cli->sd);
+    free(cli);
+    if (msg != NULL)
+        free(msg);
 }
 
 void
 client_connection_cb(int sd, short type, void *arg)
 {
     int nsd;
-    FILE *fp;
 
     if ((nsd = accept(sd, NULL, NULL)) < 0) {
         if (errno == EWOULDBLOCK || errno == ECONNABORTED)
@@ -224,14 +312,10 @@ client_connection_cb(int sd, short type, void *arg)
     if ((errno = set_blocking(nsd)) != 0)
         btpd_err("set_blocking: %s.\n", strerror(errno));
 
-    if ((fp = fdopen(nsd, "r+")) == NULL) {
-        close(nsd);
-        return;
-    }
-
-    do_ipc(fp);
-
-    fclose(fp);
+    struct cli *cli = btpd_calloc(1, sizeof(*cli));
+    cli->sd = nsd;
+    event_set(&cli->read, cli->sd, EV_READ, cli_read_cb, cli);
+    event_add(&cli->read, NULL);
 }
 
 void
diff --git a/btpd/tracker_req.c b/btpd/tracker_req.c
index 74b16ee..dfb5ed6 100644
--- a/btpd/tracker_req.c
+++ b/btpd/tracker_req.c
@@ -40,15 +40,12 @@ static void tr_send(struct torrent *tp, enum tr_event event);
 void
 maybe_connect_to(struct torrent *tp, const char *pinfo)
 {
-    const char *pid = NULL;
-    char *ip = NULL;
-    int64_t port;
+    const char *pid;
+    char *ip;
+    int port;
     size_t len;
 
-    if (!benc_isdct(pinfo))
-        return;
-
-    if (benc_dget_str(pinfo, "peer id", &pid, &len) != 0 || len != 20)
+    if ((pid = benc_dget_mem(pinfo, "peer id", &len)) == NULL || len != 20)
         return;
 
     if (bcmp(btpd_get_peer_id(), pid, 20) == 0)
@@ -56,71 +53,60 @@ maybe_connect_to(struct torrent *tp, const char *pinfo)
 
     if (net_torrent_has_peer(tp->net, pid))
         return;
+    
+    if ((ip = benc_dget_str(pinfo, "ip", NULL)) == NULL)
+        return;
 
-    if (benc_dget_strz(pinfo, "ip", &ip, NULL) != 0)
-        goto out;
-
-    if (benc_dget_int64(pinfo, "port", &port) != 0)
-        goto out;
-
+    port = benc_dget_int(pinfo, "port");
     peer_create_out(tp->net, pid, ip, port);
 
-out:
     if (ip != NULL)
         free(ip);
 }
 
+
 static int
 parse_reply(struct torrent *tp, const char *content, size_t size)
 {
-    char *buf;
+    const char *buf;
+    size_t len;
     const char *peers;
-    uint32_t interval;
+    int interval;
 
-    if (benc_validate(content, size) != 0 || !benc_isdct(content)) {
-        btpd_log(BTPD_L_ERROR, "Bad data from tracker.\n");
+    if ((buf = benc_dget_mem(content, "failure reason", &len)) != NULL) {
+        btpd_log(BTPD_L_ERROR, "Tracker failure: %.*s.\n", (int)len, buf);
         return 1;
     }
 
-    if ((benc_dget_strz(content, "failure reason", &buf, NULL)) == 0) {
-        btpd_log(BTPD_L_ERROR, "Tracker failure: %s.\n", buf);
-        free(buf);
-        return 1;
-    }
+    if ((benc_validate(content, size) != 0 ||
+            !benc_dct_chk(content, 2, BE_INT, 1, "interval",
+                BE_ANY, 1, "peers")))
+        goto bad_data;
 
-    if ((benc_dget_uint32(content, "interval", &interval)) != 0) {
-        btpd_log(BTPD_L_ERROR, "Bad data from tracker.\n");
-        return 1;
-    }
+    interval = benc_dget_int(content, "interval");
+    if (interval < 1)
+        goto bad_data;
 
     tp->tr->interval = interval;
-    btpd_log(BTPD_L_BTPD, "Got interval %d.\n", interval);
-
-    int error = 0;
-    size_t length;
+    peers = benc_dget_any(content, "peers");
 
-    if ((error = benc_dget_lst(content, "peers", &peers)) == 0) {
+    if (benc_islst(peers)) {
         for (peers = benc_first(peers);
              peers != NULL && net_npeers < net_max_peers;
              peers = benc_next(peers))
             maybe_connect_to(tp, peers);
-    }
-
-    if (error == EINVAL) {
-        error = benc_dget_str(content, "peers", &peers, &length);
-        if (error == 0 && length % 6 == 0) {
-            size_t i;
-            for (i = 0; i < length && net_npeers < net_max_peers; i += 6)
-                peer_create_out_compact(tp->net, peers + i);
-        }
-    }
-
-    if (error != 0) {
-        btpd_log(BTPD_L_ERROR, "Bad data from tracker.\n");
-        return 1;
-    }
+    } else if (benc_isstr(peers)) {
+        peers = benc_dget_mem(content, "peers", &len);
+        for (size_t i = 0; i < len && net_npeers < net_max_peers; i += 6)
+            peer_create_out_compact(tp->net, peers + i);
+    } else
+        goto bad_data;
 
     return 0;
+
+bad_data:
+    btpd_log(BTPD_L_ERROR, "Bad data from tracker.\n");
+    return 1;
 }
 
 static void
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
diff --git a/misc/benc.c b/misc/benc.c
index 68f7f95..6a57e2e 100644
--- a/misc/benc.c
+++ b/misc/benc.c
@@ -2,6 +2,7 @@
 #include <ctype.h>
 #include <errno.h>
 #include <inttypes.h>
+#include <stdarg.h>
 #include <stdlib.h>
 #include <string.h>
 
@@ -99,7 +100,7 @@ benc_length(const char *p)
             ;
         return next - p + 1;
     default:
-        assert(benc_str(p, &next, &blen, NULL) == 0);
+        assert((next = benc_mem(p, &blen, NULL)) != NULL);
         return next - p + blen;
     }
 }
@@ -127,11 +128,12 @@ benc_next(const char *p)
     return *(p + blen) == 'e' ? NULL : p + blen;
 }
 
-int
-benc_str(const char *p, const char **out, size_t *len, const char**next)
+const char *
+benc_mem(const char *p, size_t *len, const char**next)
 {
+    if (!benc_isstr(p))
+        return NULL;
     size_t blen = 0;
-    assert(isdigit(*p));
     blen = *p - '0';
     p++;
     while (isdigit(*p)) {
@@ -141,53 +143,49 @@ benc_str(const char *p, const char **out, size_t *len, const char**next)
     }
     assert(*p == ':');
     benc_safeset(len, blen);
-    benc_safeset(out, p + 1);
     benc_safeset(next, *(p + blen + 1) == 'e' ? NULL : p + blen + 1);
-    return 0;
+    return p + 1;
 }
 
-int
-benc_strz(const char *p, char **out, size_t *len, const char **next)
+char *
+benc_str(const char *p, size_t *len, const char **next)
 {
-    int err;
     size_t blen;
     const char *bstr;
-
-    if ((err = benc_str(p, &bstr, &blen, next)) == 0) {
-        if ((*out = malloc(blen + 1)) != NULL) {
-            memcpy(*out, bstr, blen);
-            (*out)[blen] = '\0';
-            benc_safeset(len, blen);
-        } else
-            err = ENOMEM;
-    }
-    return err;
+    char *ret;
+    if ((bstr = benc_mem(p, &blen, next)) == NULL)
+        return NULL;
+    if ((ret = malloc(blen + 1)) == NULL)
+        return NULL;
+    bcopy(bstr, ret, blen);
+    ret[blen] = '\0';
+    benc_safeset(len, blen);
+    return ret;
 }
 
-int
-benc_stra(const char *p, char **out, size_t *len, const char **next)
+char *
+benc_mema(const char *p, size_t *len, const char **next)
 {
-    int err;
     size_t blen;
     const char *bstr;
-
-    if ((err = benc_str(p, &bstr, &blen, next)) == 0) {
-        if ((*out = malloc(blen)) != NULL) {
-            memcpy(*out, bstr, blen);
-            benc_safeset(len, blen);
-        } else
-            err = ENOMEM;
-    }
-    return err;
+    char *ret;
+    if ((bstr = benc_mem(p, &blen, next)) == NULL)
+        return NULL;
+    if ((ret = malloc(blen)) == NULL)
+        return NULL;
+    bcopy(bstr, ret, blen);
+    benc_safeset(len, blen);
+    return ret;
 }
 
-int
-benc_int64(const char *p, int64_t *out, const char **next)
+long long
+benc_int(const char *p, const char **next)
 {
     int sign = 1;
-    int64_t res = 0;
+    long long res = 0;
 
-    assert(*p == 'i');
+    if (!benc_isint(p))
+        return 0;
     p++;
     if (*p == '-') {
         sign = -1;
@@ -202,122 +200,79 @@ benc_int64(const char *p, int64_t *out, const char **next)
         p++;
     }
     assert(*p == 'e');
-    benc_safeset(out, res);
-    benc_safeset(next, *(p + 1) == 'e' ? NULL : p + 1);
-
-    return 0;
-}
-
-int
-benc_uint32(const char *p, uint32_t *out, const char **next)
-{
-    int err;
-    int64_t res;
-    if ((err = benc_int64(p, &res, next)) == 0) {
-        if (res >= 0 && res <= 0xffffffffUL)
-            *out = (uint32_t)res;
-        else
-            err = EINVAL;
-    }
-    return err;
+    benc_safeset(next, *(p + 1) == 'e' ? NULL : p + 1);    
+    return res;
 }
 
-int
-benc_dget_any(const char *p, const char *key, const char **val)
+const char *
+benc_dget_any(const char *p, const char *key)
 {
-    int res;
+    int cmp;
     size_t len, blen;
     const char *bstr;
 
-    assert(benc_isdct(p));
+    if (!benc_isdct(p))
+        return NULL;
 
     len = strlen(key);
 
     p = benc_first(p);
     while (p != NULL) {
-        if ((res = benc_str(p, &bstr, &blen, &p)) != 0)
-            return res;
-
-        res = strncmp(bstr, key, blen);
-        if (res == 0 && len == blen) {
-            *val = p;
-            return 0;
-        } else if (res <= 0) {
+        if (!benc_isstr(p))
+            return NULL;
+        bstr = benc_mem(p, &blen, &p);
+
+        cmp = strncmp(bstr, key, blen);
+        if (cmp == 0 && len == blen)
+            return p;
+        else if (cmp <= 0)
             p = benc_next(p);
-        } else
-            return ENOENT;
+        else
+            return NULL;
     }
-    return ENOENT;
+    return NULL;
 }
 
-int
-benc_dget_lst(const char *p, const char *key, const char **val)
-{
-    int err;
-    if ((err = benc_dget_any(p, key, val)) == 0)
-        if (!benc_islst(*val))
-            err = EINVAL;
-    return err;
-}
-
-int
-benc_dget_dct(const char *p, const char *key, const char **val)
+const char *
+benc_dget_lst(const char *p, const char *key)
 {
-    int err;
-    if ((err = benc_dget_any(p, key, val)) == 0)
-        if (!benc_isdct(*val))
-            err = EINVAL;
-    return err;
+    const char *ret = benc_dget_any(p, key);
+    return ret != NULL && benc_islst(ret) ? ret : NULL;
 }
 
-int
-benc_dget_str(const char *p, const char *key, const char **val, size_t *len)
+const char *
+benc_dget_dct(const char *p, const char *key)
 {
-    int err;
-    const char *sp;
-    if ((err = benc_dget_any(p, key, &sp)) == 0)
-        err = benc_isstr(sp) ? benc_str(sp, val, len, NULL) : EINVAL;
-    return err;
+    const char *ret = benc_dget_any(p, key);
+    return ret != NULL && benc_isdct(ret) ? ret : NULL;
 }
 
-int
-benc_dget_stra(const char *p, const char *key, char **val, size_t *len)
+const char *
+benc_dget_mem(const char *p, const char *key, size_t *len)
 {
-    int err;
-    const char *sp;
-    if ((err = benc_dget_any(p, key, &sp)) == 0)
-        err = benc_isstr(sp) ? benc_stra(sp, val, len, NULL) : EINVAL;
-    return err;
+    const char *str = benc_dget_any(p, key);
+    return str != NULL && benc_isstr(str) ? benc_mem(str, len, NULL) : NULL;
 }
 
-int
-benc_dget_strz(const char *p, const char *key, char **val, size_t *len)
+char *
+benc_dget_mema(const char *p, const char *key, size_t *len)
 {
-    int err;
-    const char *sp;
-    if ((err = benc_dget_any(p, key, &sp)) == 0)
-        err = benc_isstr(sp) ? benc_strz(sp, val, len, NULL) : EINVAL;
-    return err;
+    const char *str = benc_dget_any(p, key);
+    return str != NULL && benc_isstr(str) ? benc_mema(str, len, NULL) : NULL;
 }
 
-int
-benc_dget_int64(const char *p, const char *key, int64_t *val)
+char *
+benc_dget_str(const char *p, const char *key, size_t *len)
 {
-    int err;
-    const char *ip;
-    if ((err = benc_dget_any(p, key, &ip)) == 0)
-        err = benc_isint(ip) ? benc_int64(ip, val, NULL) : EINVAL;
-    return err;
+    const char *str = benc_dget_any(p, key);
+    return str != NULL && benc_isstr(str) ? benc_str(str, len, NULL) : NULL;
 }
 
-int
-benc_dget_uint32(const char *p, const char *key, uint32_t *val)
+long long
+benc_dget_int(const char *p, const char *key)
 {
-    int err;
-    const char *ip;
-    if ((err = benc_dget_any(p, key, &ip)) == 0)
-        err = benc_isint(ip) ? benc_uint32(ip, val, NULL) : EINVAL;
-    return err;
+    const char *intp = benc_dget_any(p, key);
+    return intp != NULL && benc_isint(intp) ? benc_int(intp, NULL) : 0;
 }
 
 int
@@ -343,3 +298,131 @@ benc_isstr(const char *p)
 {
     return isdigit(*p);
 }
+
+int
+benc_istype(const char *p, enum be_type type)
+{
+    switch (type) {
+    case BE_ANY:
+        return benc_isdct(p) || benc_isint(p) ||
+            benc_islst(p) || benc_isstr(p);
+    case BE_DCT:
+        return benc_isdct(p);
+    case BE_INT:
+        return benc_isint(p);
+    case BE_LST:
+        return benc_islst(p);
+    case BE_STR:
+        return benc_isstr(p);
+    default:
+        abort();
+    }
+}
+
+int
+benc_dct_chk(const char *p, int count, ...)
+{
+    int i, ok = 1;
+    va_list ap;
+
+    if (!benc_isdct(p))
+        ok = 0;
+
+    va_start(ap, count);
+    for (i = 0; ok && i < count; i++) {
+        enum be_type type = va_arg(ap, enum be_type);
+        int level = va_arg(ap, int);
+        const char *dct = p;
+        const char *key = va_arg(ap, const char *);
+        while (ok && level > 1) {            
+            if ((dct = benc_dget_dct(dct, key)) != NULL) {
+                level--;
+                key = va_arg(ap, const char *);
+            } else
+                ok = 0;
+        }
+        if (ok) {
+            const char *val = benc_dget_any(dct, key);
+            if (val == NULL || !benc_istype(val, type))
+                ok = 0;
+        }
+    }
+    va_end(ap);
+    return ok;
+}
+
+#if 0
+int
+benc_dct_type_check(const char *p, int count, ...)
+{
+    int i;
+    va_list ap;
+
+    benc_validate_dct(p, 2, BE_INT, "code", BE_STR, "hash", 
+
+    if (!benc_isdct(p))
+        return EINVAL;
+
+    va_start(ap, count);
+    for (i = 0; i < count; i++) {
+
+
+    }
+}
+
+int
+benc_dget_many(const char *p, int count, ...)
+{
+    int i;
+    va_list ap;
+    if (!benc_isdct(p))
+        return 0;
+    va_start(ap, count);
+    for (i = 0; i < count; i++) {
+        const char *name = va_arg(ap, const char *);
+        enum be_type type = va_arg(ap, enum be_type);
+        int64_t *iret;
+        size_t *lret;
+        const char **mret;
+        char **aret;
+
+        switch (type) {
+        case BE_INT:
+            iret = va_arg(ap, int64_t *);
+            if (benc_dget_int64(p, name, iret) != 0)
+                goto out;
+            break;
+        case BE_LST:
+            mret = va_arg(ap, const char **);
+            lret = va_arg(ap, size_t *);
+            if (benc_dget_lst(p, name, mret) != 0)
+                goto out;
+            if (lret != NULL)
+                *lret = benc_nelems(*mret);
+            break;
+        case BE_DCT:
+            mret = va_arg(ap, const char **);
+            if (benc_dget_dct(p, name, mret) != 0)
+                goto out;
+            break;
+        case BE_MEM:
+            mret = va_arg(ap, const char **);
+            lret = va_arg(ap, size_t *);
+            if (benc_dget_str(p, name, mret, lret) != 0)
+                goto out;
+            break;
+        case BE_STRZ:
+            aret = va_arg(ap, char **);
+            lret = va_arg(ap, size_t *);
+            if (benc_dget_strz(p, name, aret, lret) != 0)
+                goto out;
+            break;
+        default:
+            abort();
+        }
+    }
+out:
+    va_end(ap);
+    return i;
+}
+#endif
diff --git a/misc/benc.h b/misc/benc.h
index 8088190..69c5f57 100644
--- a/misc/benc.h
+++ b/misc/benc.h
@@ -1,7 +1,21 @@
 #ifndef BTPD_BENC_H
 #define BTPD_BENC_H
 
+enum be_type {
+    BE_ANY,
+    BE_DCT,
+    BE_INT,
+    BE_LST,
+    BE_STR
+};
+
 int benc_validate(const char *p, size_t len);
+int benc_dct_chk(const char *p, int count, ...);
+
+int benc_islst(const char *p);
+int benc_isdct(const char *p);
+int benc_isint(const char *p);
+int benc_isstr(const char *p);
 
 size_t benc_length(const char *p);
 size_t benc_nelems(const char *p);
@@ -9,29 +23,17 @@ size_t benc_nelems(const char *p);
 const char *benc_first(const char *p);
 const char *benc_next(const char *p);
 
-int benc_str(const char *p, const char **mem, size_t *len, const char**next);
-int benc_stra(const char *p, char **out, size_t *len, const char **next);
-int benc_strz(const char *p, char **out, size_t *len, const char **next);
-int benc_int64(const char *p, int64_t *out, const char **next);
-int benc_uint32(const char *p, uint32_t *out, const char **next);
-
-#define benc_off benc_int64
-
-int benc_dget_any(const char *p, const char *key, const char **val);
-int benc_dget_lst(const char *p, const char *key, const char **val);
-int benc_dget_dct(const char *p, const char *key, const char **val);
-int benc_dget_str(const char *p, const char *key,
-                  const char **val, size_t *len);
-int benc_dget_stra(const char *p, const char *key, char **val, size_t *len);
-int benc_dget_strz(const char *p, const char *key, char **val, size_t *len);
-int benc_dget_int64(const char *p, const char *key, int64_t *val);
-int benc_dget_uint32(const char *p, const char *key, uint32_t *val);
-
-#define benc_dget_off benc_dget_int64
-
-int benc_islst(const char *p);
-int benc_isdct(const char *p);
-int benc_isint(const char *p);
-int benc_isstr(const char *p);
+long long benc_int(const char *p, const char **next);
+const char *benc_mem(const char *p, size_t *len, const char **next);
+char *benc_mema(const char *p, size_t *len, const char **next);
+char *benc_str(const char *p, size_t *len, const char **next);
+
+const char *benc_dget_any(const char *p, const char *key);
+const char *benc_dget_lst(const char *p, const char *key);
+const char *benc_dget_dct(const char *p, const char *key);
+long long benc_dget_int(const char *p, const char *key);
+const char *benc_dget_mem(const char *p, const char *key, size_t *len);
+char *benc_dget_mema(const char *p, const char *key, size_t *len);
+char *benc_dget_str(const char *p, const char *key, size_t *len);
 
 #endif
diff --git a/misc/metainfo.c b/misc/metainfo.c
index 1b90247..d8f3a7c 100644
--- a/misc/metainfo.c
+++ b/misc/metainfo.c
@@ -69,22 +69,22 @@ check_path(const char *path, size_t len)
 int
 fill_fileinfo(const char *fdct, struct fileinfo *tfp)
 {
-    int err;
+    //int err;
     size_t npath, plen, len;
     const char *plst, *iter, *str;
 
-    if ((err = benc_dget_off(fdct, "length", &tfp->length)) != 0)
-        return err;
+    if (!benc_dct_chk(fdct, 2, BE_INT, 1, "length", BE_LST, 1, "path"))
+        return EINVAL;
 
-    if ((err = benc_dget_lst(fdct, "path", &plst)) != 0)
-        return err;
+    tfp->length = benc_dget_int(fdct, "length");
+    plst = benc_dget_lst(fdct, "path");
 
     npath = plen = 0;
     iter = benc_first(plst);
     while (iter != NULL) {
         if (!benc_isstr(iter))
             return EINVAL;
-        benc_str(iter, &str, &len, &iter);
+        str = benc_mem(iter, &len, &iter);
         if (!check_path(str, len))
             return EINVAL;
         npath++;
@@ -97,13 +97,13 @@ fill_fileinfo(const char *fdct, struct fileinfo *tfp)
         return ENOMEM;
 
     iter = benc_first(plst);
-    benc_str(iter, &str, &len, &iter);
+    str = benc_mem(iter, &len, &iter);
     memcpy(tfp->path, str, len);
     plen = len;
     npath--;
     while (npath > 0) {
         tfp->path[plen++] = '/';
-        benc_str(iter, &str, &len, &iter);
+        str = benc_mem(iter, &len, &iter);
         memcpy(tfp->path + plen, str, len);
         plen += len;
         npath--;
@@ -135,35 +135,25 @@ int
 fill_metainfo(const char *bep, struct metainfo *tp, int mem_hashes)
 {
     size_t len;
-    int err;
+    int err = 0;
     const char *base_addr = bep;
     const char *hash_addr;
 
-    if (!benc_isdct(bep))
+    if (!benc_dct_chk(bep, 5,
+            BE_STR, 1, "announce",
+            BE_DCT, 1, "info",
+            BE_INT, 2, "info", "piece length",
+            BE_STR, 2, "info", "pieces",
+            BE_STR, 2, "info", "name"))
         return EINVAL;
 
-    if ((err = benc_dget_strz(bep, "announce", &tp->announce, NULL)) != 0)
-        goto out;
-
-    if ((err = benc_dget_dct(bep, "info", &bep)) != 0)
-        goto out;
-
+    tp->announce = benc_dget_str(bep, "announce", NULL);
+    bep = benc_dget_dct(bep, "info");
     SHA1(bep, benc_length(bep), tp->info_hash);
-
-    if ((err = benc_dget_off(bep, "piece length", &tp->piece_length)) != 0)
-        goto out;
-
-    if ((err = benc_dget_str(bep, "pieces", &hash_addr, &len)) != 0)
-        goto out;
-
-    if (len % 20 != 0) {
-        err = EINVAL;
-        goto out;
-    }
+    tp->piece_length = benc_dget_int(bep, "piece length");
+    hash_addr = benc_dget_mem(bep, "pieces", &len);
     tp->npieces = len / 20;
-
     tp->pieces_off = hash_addr - base_addr;
-
     if (mem_hashes) {
         if ((tp->piece_hash = malloc(len)) == NULL) {
             err = ENOMEM;
@@ -171,12 +161,10 @@ fill_metainfo(const char *bep, struct metainfo *tp, int mem_hashes)
         }
         bcopy(hash_addr, tp->piece_hash, len);
     }
+    tp->name = benc_dget_str(bep, "name", NULL);
 
-    if ((err = benc_dget_strz(bep, "name", &tp->name, NULL)) != 0)
-        goto out;
-
-    err = benc_dget_off(bep, "length", &tp->total_length);
-    if (err == 0) {
+    if (benc_dct_chk(bep, 1, BE_INT, 1, "length")) {
+        tp->total_length = benc_dget_int(bep, "length");
         tp->nfiles = 1;
         tp->files = calloc(1, sizeof(struct fileinfo));
         if (tp->files != NULL) {
@@ -190,14 +178,11 @@ fill_metainfo(const char *bep, struct metainfo *tp, int mem_hashes)
             err = ENOMEM;
             goto out;
         }
-    }
-    else if (err == ENOENT) {
+    } else if (benc_dct_chk(bep, 1, BE_LST, 1, "files")) {
         int i;
         const char *flst, *fdct;
 
-        if ((err = benc_dget_lst(bep, "files", &flst)) != 0)
-            goto out;
-
+        flst = benc_dget_lst(bep, "files");
         tp->nfiles = benc_nelems(flst);
         if (tp->nfiles < 1) {
             err = EINVAL;
@@ -258,7 +243,6 @@ load_metainfo(const char *path, off_t size, int mem_hashes,
     if (err == 0)
         if ((*res = calloc(1, sizeof(**res))) == NULL)
             err = ENOMEM;
-
     if (err == 0)
         if ((err = fill_metainfo(buf, *res, mem_hashes)) != 0)
             free(*res);