about summary refs log tree commit diff
path: root/cli
diff options
context:
space:
mode:
Diffstat (limited to 'cli')
-rw-r--r--cli/btcli.c341
-rw-r--r--cli/btpd_if.c68
-rw-r--r--cli/btpd_if.h22
3 files changed, 229 insertions, 202 deletions
diff --git a/cli/btcli.c b/cli/btcli.c
index d5e20b6..3971d77 100644
--- a/cli/btcli.c
+++ b/cli/btcli.c
@@ -1,66 +1,179 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+
 #include <err.h>
 #include <errno.h>
+#include <fcntl.h>
 #include <getopt.h>
+#include <limits.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <unistd.h>
 
 #include "btpd_if.h"
+#include "metainfo.h"
+#include "subr.h"
 
-static const char *btpd_dir = "/usr/btpd";
-static struct ipc *ipc;
+const char *btpd_dir;
+struct ipc *ipc;
 
-static void
-handle_ipc_res(enum ipc_code code)
+void
+btpd_connect(void)
+{
+    if ((errno = ipc_open(btpd_dir, &ipc)) != 0)
+        err(1, "cannot open connection to btpd in %s", btpd_dir);
+}
+
+enum ipc_code
+handle_ipc_res(enum ipc_code code, const char *target)
 {
     switch (code) {
     case IPC_OK:
-        return;
+        break;
     case IPC_FAIL:
-        warnx("Ipc failed.\n");
+        warnx("btpd couldn't execute the requested operation for %s", target);
+        break;
+    case IPC_ERROR:
+        warnx("btpd encountered an error for %s", target);
         break;
-    case IPC_COMMERR:
-        errx(1, "Communication error.\n");
+    default:
+        errx(1, "fatal error in communication with btpd");
     }
+    return code;
 }
 
-static void
-btpd_connect(void)
+void
+print_state_name(struct tpstat *ts)
 {
-    if ((errno = ipc_open(btpd_dir, &ipc)) != 0)
-        errx(1, "Couldn't connect to btpd in %s (%s).\n",
-            btpd_dir, strerror(errno));
+    char statec[] = ">*<U";
+    int state = min(ts->state, 3);
+    printf("%c. %s", statec[state], ts->name);
+}
+
+void
+print_stat(struct tpstat *cur)
+{
+    printf("%5.1f%% %6.1fM %7.2fkB/s %6.1fM %7.2fkB/s %4u %5.1f%%",
+        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");
 }
 
 void
 usage_add(void)
 {
     printf(
-        "Add a torrent to btpd.\n"
+        "Add torrents 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"
+        "Usage: add [--topdir] -d dir file\n"
+        "       add file ...\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"
+        "Arguments:\n"
+        "file ...\n"
+        "\tOne or more torrents to add.\n"
         "\n"
-        "-f file\n"
-        "\tThe torrent to add.\n"
+        "Options:\n"
+        "-d dir\n"
+        "\tUse the dir for content.\n"
         "\n"
-        "-s\n"
-        "\tStart the torrent.\n"
+        "--topdir\n"
+        "\tAppend the torrent top directory (if any) to the content path.\n"
+        "\tThis option cannot be used without the '-d' option.\n"
         "\n"
         );
     exit(1);
 }
 
+struct option add_opts [] = {
+    { "help", no_argument, NULL, 'H' },
+    { "topdir", no_argument, NULL, 'T'},
+    {NULL, 0, NULL, 0}
+};
+
+int
+content_link(uint8_t *hash, char *buf)
+{
+    int n;
+    char relpath[41];
+    char path[PATH_MAX];
+    for (int i = 0; i < 20; i++)
+        snprintf(relpath + i * 2, 3, "%.2x", hash[i]);
+    snprintf(path, PATH_MAX, "%s/torrents/%s/content", btpd_dir, relpath);
+    if ((n = readlink(path, buf, PATH_MAX)) == -1)
+        return errno;
+    buf[min(n, PATH_MAX)] = '\0';
+    return 0;
+}
+
 void
 cmd_add(int argc, char **argv)
 {
+    int ch, topdir = 0;
+    char *dir = NULL, bdir[PATH_MAX];
+
+    while ((ch = getopt_long(argc, argv, "d:", add_opts, NULL)) != -1) {
+        switch (ch) {
+        case 'T':
+            topdir = 1;
+            break;
+        case 'd':
+            dir = optarg;
+            break;
+        default:
+            usage_add();
+        }
+    }
+    argc -= optind;
+    argv += optind;
+
+    if (argc < 1 || (topdir == 1 && dir == NULL) || (dir != NULL && argc > 1))
+        usage_add();
+
+    if (dir != NULL)
+        if (realpath(dir, bdir) == NULL)
+            err(1, "path error on %s", bdir);
+
+    btpd_connect();
+    for (int i = 0; i < argc; i++) {
+        struct metainfo *mi;
+        char dpath[PATH_MAX], fpath[PATH_MAX];
+
+        if ((errno = load_metainfo(argv[i], -1, 0, &mi)) != 0) {
+            warn("error loading torrent %s", argv[i]);
+            continue;
+        }
+
+        if ((topdir &&
+                !(mi->nfiles == 1
+                    && strcmp(mi->name, mi->files[0].path) == 0)))
+            snprintf(dpath, PATH_MAX, "%s/%s", bdir, mi->name);
+        else if (dir != NULL)
+            strlcpy(dpath, bdir, PATH_MAX);
+        else {
+            if (content_link(mi->info_hash, dpath) != 0) {
+                warnx("unknown content dir for %s", argv[i]);
+                errx(1, "use the '-d' option");
+            }
+        }
+
+        if (mkdir(dpath, 0777) != 0 && errno != EEXIST)
+            err(1, "couldn't create directory %s", dpath);
+
+        if (realpath(argv[i], fpath) == NULL)
+            err(1, "path error on %s", fpath);
+
+        handle_ipc_res(btpd_add(ipc, mi->info_hash, fpath, dpath), argv[1]);
+        clear_metainfo(mi);
+        free(mi);
+    }
 }
 
 void
@@ -69,11 +182,11 @@ usage_del(void)
     printf(
         "Remove torrents from btpd.\n"
         "\n"
-        "Usage: del num ...\n"
+        "Usage: del file ...\n"
         "\n"
         "Arguments:\n"
-        "num\n"
-        "\tThe number of the torrent to remove.\n"
+        "file ...\n"
+        "\tThe torrents to remove.\n"
         "\n");
     exit(1);
 }
@@ -84,16 +197,17 @@ cmd_del(int argc, char **argv)
     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();
-    }
     btpd_connect();
-    for (int i = 0; i < argc -1; i++)
-        handle_ipc_res(btpd_del_num(ipc, nums[i]));
+    for (int i = 1; i < argc; i++) {
+        struct metainfo *mi;
+        if ((errno = load_metainfo(argv[i], -1, 0, &mi)) != 0) {
+            warn("error loading torrent %s", argv[i]);
+            continue;
+        }
+        handle_ipc_res(btpd_del(ipc, mi->info_hash), argv[i]);
+        clear_metainfo(mi);
+        free(mi);
+    }
 }
 
 void
@@ -118,24 +232,23 @@ cmd_kill(int argc, char **argv)
 {
     int seconds = -1;
     char *endptr;
-    if (argc == 1)
-        ;
-    else if (argc == 2) {
+
+    if (argc == 2) {
         seconds = strtol(argv[1], &endptr, 10);
         if (strlen(argv[1]) > endptr - argv[1] || seconds < 0)
             usage_kill();
-    } else
+    } else if (argc > 2)
         usage_kill();
 
     btpd_connect();
-    btpd_die(ipc, seconds);
+    handle_ipc_res(btpd_die(ipc, seconds), "kill");
 }
 
 void
 usage_list(void)
 {
     printf(
-        "List btpd's torrents.\n"
+        "List active torrents.\n"
         "\n"
         "Usage: list\n"
         "\n"
@@ -152,12 +265,13 @@ cmd_list(int argc, char **argv)
         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,
+    if (handle_ipc_res(btpd_stat(ipc, &st), "list") != IPC_OK)
+        exit(1);
+    for (int i = 0; i < st->ntorrents; i++) {
+        print_state_name(&st->torrents[i]);
+        putchar('\n');
+    }
+    printf("%u torrent%s.\n", st->ntorrents,
         st->ntorrents == 1 ? "" : "s");
 }
 
@@ -166,9 +280,9 @@ usage_stat(void)
 {
     printf(
         "Display stats for active torrents.\n"
-        "The stats displayed are:\n"
+        "The displayed stats are:\n"
         "%% got, MB down, rate down. MB up, rate up\n"
-        "peers, %% of pieces seen, tracker errors\n"
+        "peer count, %% of pieces seen, tracker errors\n"
         "\n"
         "Usage: stat [-i] [-w seconds]\n"
         "\n"
@@ -183,36 +297,17 @@ usage_stat(void)
 }
 
 void
-print_stat(struct tpstat *cur)
-{
-    printf("%5.1f%% %6.1fM %7.2fkB/s %6.1fM %7.2fkB/s %4u %5.1f%%",
-        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");
-}
-
-void
 do_stat(int individual, int seconds)
 {
     struct btstat *st;
     struct tpstat tot;
 again:
     bzero(&tot, sizeof(tot));
-    tot.num = -1;
-    if ((errno = btpd_stat(ipc, &st)) != 0)
-        err(1, "btpd_stat");
+    tot.state = T_ACTIVE;
+    if (handle_ipc_res(btpd_stat(ipc, &st), "stat") != IPC_OK)
+        exit(1);
     for (int i = 0; i < st->ntorrents; i++) {
         struct tpstat *cur = &st->torrents[i];
-        if (cur->state != 'A')
-            continue;
         tot.uploaded += cur->uploaded;
         tot.downloaded += cur->downloaded;
         tot.rate_up += cur->rate_up;
@@ -223,7 +318,8 @@ again:
         tot.have += cur->have;
         tot.total += cur->total;
         if (individual) {
-            printf("%u. %s:\n", cur->num, cur->name);
+            print_state_name(cur);
+            printf(":\n");
             print_stat(cur);
         }
     }
@@ -237,8 +333,8 @@ again:
     }
 }
 
-static struct option stat_opts [] = {
-    { "help", no_argument, NULL, 1 },
+struct option stat_opts [] = {
+    { "help", no_argument, NULL, 'H' },
     {NULL, 0, NULL, 0}
 };
 
@@ -272,73 +368,7 @@ cmd_stat(int argc, char **argv)
     do_stat(iflag, seconds);
 }
 
-void
-usage_start(void)
-{
-    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);
-}
-
-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();
-    }
-    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();
-    }
-    btpd_connect();
-    for (int i = 0; i < argc -1; i++)
-        handle_ipc_res(btpd_stop_num(ipc, nums[i]));
-}
-
-static struct {
+struct {
     const char *name;
     void (*fun)(int, char **);
     void (*help)(void);
@@ -347,19 +377,16 @@ static struct {
     { "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 }
+    { "stat", cmd_stat, usage_stat }
 };
 
-static int ncmds = sizeof(cmd_table) / sizeof(cmd_table[0]);
+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"
+        "btcli is the btpd command line interface.\n"
         "\n"
         "Usage: btcli [main options] command [command options]\n"
         "\n"
@@ -375,15 +402,13 @@ usage(void)
         "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 },
+struct option base_opts [] = {
+    { "help", no_argument, NULL, 'H' },
     {NULL, 0, NULL, 0}
 };
 
@@ -400,7 +425,7 @@ main(int argc, char **argv)
         case 'd':
             btpd_dir = optarg;
             break;
-        case 1:
+        case 'H':
             help = 1;
             break;
         default:
@@ -413,6 +438,10 @@ main(int argc, char **argv)
     if (argc == 0)
         usage();
 
+    if (btpd_dir == NULL)
+        if ((btpd_dir = find_btpd_dir()) == NULL)
+            errx(1, "cannot find the btpd directory");
+
     optind = 0;
     int found = 0;
     for (int i = 0; !found && i < ncmds; i++) {
@@ -424,7 +453,7 @@ main(int argc, char **argv)
                 cmd_table[i].fun(argc, argv);
         }
     }
-    
+
     if (!found)
         usage();
 
diff --git a/cli/btpd_if.c b/cli/btpd_if.c
index a5fa40d..c328fb6 100644
--- a/cli/btpd_if.c
+++ b/cli/btpd_if.c
@@ -1,3 +1,7 @@
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
 #include <ctype.h>
 #include <err.h>
 #include <errno.h>
@@ -81,7 +85,7 @@ ipc_response(struct ipc *ipc, char **out, uint32_t *len)
     *len = size;
     return 0;
 }
- 
+
 static int
 ipc_req_res(struct ipc *ipc, const char *req, uint32_t qlen, char **res,
     uint32_t *rlen)
@@ -125,7 +129,7 @@ btpd_die(struct ipc *ipc, int seconds)
     if (seconds >= 0)
         buf_print(&iob, "l3:diei%dee", seconds);
     else
-        buf_print(&iob, "l3:diee");
+        buf_swrite(&iob, "l3:diee");
     return ipc_buf_req(ipc, &iob);
 }
 
@@ -150,21 +154,18 @@ parse_btstat(const uint8_t *res, struct btstat **out)
     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");
-        }
+        ts->state = benc_dget_int(tp, "state");
+        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, "down");
+        ts->uploaded = benc_dget_int(tp, "up");
+        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++;
     }
     *out = st;
@@ -197,29 +198,26 @@ btpd_stat(struct ipc *ipc, struct btstat **out)
     return err;
 }
 
-static enum ipc_code
-btpd_common_num(struct ipc *ipc, const char *cmd, unsigned num)
-{
-    struct io_buffer iob;
-    buf_init(&iob, 16);
-    buf_print(&iob, "l%d:%si%uee", (int)strlen(cmd), cmd, num);
-    return ipc_buf_req(ipc, &iob);    
-}
-
 enum ipc_code
-btpd_del_num(struct ipc *ipc, unsigned num)
+btpd_add(struct ipc *ipc, const uint8_t *hash, const char *torrent,
+    const char *content)
 {
-    return btpd_common_num(ipc, "del", num);
-}
-
-enum ipc_code
-btpd_start_num(struct ipc *ipc, unsigned num)
-{
-    return btpd_common_num(ipc, "start", num);
+    struct io_buffer iob;
+    buf_init(&iob, (1 << 10));
+    buf_print(&iob, "l3:addd7:content%d:%s4:hash20:", (int)strlen(content),
+        content);
+    buf_write(&iob, hash, 20);
+    buf_print(&iob, "7:torrent%d:%see", (int)strlen(torrent), torrent);
+    return ipc_buf_req(ipc, &iob);
 }
 
 enum ipc_code
-btpd_stop_num(struct ipc *ipc, unsigned num)
+btpd_del(struct ipc *ipc, const uint8_t *hash)
 {
-    return btpd_common_num(ipc, "stop", num);
+    struct io_buffer iob;
+    buf_init(&iob, 32);
+    buf_swrite(&iob, "l3:del20:");
+    buf_write(&iob, hash, 20);
+    buf_write(&iob, "e", 1);
+    return ipc_buf_req(ipc, &iob);
 }
diff --git a/cli/btpd_if.h b/cli/btpd_if.h
index 64cdb4b..8c35cc7 100644
--- a/cli/btpd_if.h
+++ b/cli/btpd_if.h
@@ -1,15 +1,18 @@
 #ifndef BTPD_IF_H
 #define BTPD_IF_H
 
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <sys/un.h>
-
 struct ipc;
 
+enum torrent_state { //XXX: Same as in btpd/torrent.h
+    T_STARTING,
+    T_ACTIVE,
+    T_STOPPING
+};
+
 enum ipc_code {
     IPC_OK,
     IPC_FAIL,
+    IPC_ERROR,
     IPC_COMMERR
 };
 
@@ -17,8 +20,7 @@ struct btstat {
     unsigned ntorrents;
     struct tpstat {
         char *name;
-        unsigned num;
-        char state;
+        enum torrent_state state;
 
         unsigned errors;
         unsigned npeers;
@@ -32,13 +34,11 @@ struct btstat {
 int ipc_open(const char *dir, struct ipc **out);
 int ipc_close(struct ipc *ipc);
 
+enum ipc_code btpd_add(struct ipc *ipc, const uint8_t *hash,
+    const char *torrent, const char *content);
+enum ipc_code btpd_del(struct ipc *ipc, const uint8_t *hash);
 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