diff options
Diffstat (limited to 'cli/btcli.c')
| -rw-r--r-- | cli/btcli.c | 403 |
1 files changed, 34 insertions, 369 deletions
diff --git a/cli/btcli.c b/cli/btcli.c index 95ae487..f8b6a81 100644 --- a/cli/btcli.c +++ b/cli/btcli.c @@ -1,21 +1,4 @@ -#include <sys/types.h> -#include <sys/stat.h> - -#include <err.h> -#include <errno.h> -#include <fcntl.h> -#include <getopt.h> -#include <inttypes.h> -#include <limits.h> -#include <math.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> - -#include "btpd_if.h" -#include "metainfo.h" -#include "subr.h" +#include "btcli.h" const char *btpd_dir; struct ipc *ipc; @@ -27,377 +10,55 @@ btpd_connect(void) err(1, "cannot open connection to btpd in %s", btpd_dir); } -enum ipc_code -handle_ipc_res(enum ipc_code code, const char *target) +enum ipc_err +handle_ipc_res(enum ipc_err code, const char *target) { switch (code) { case IPC_OK: break; - case IPC_FAIL: - warnx("btpd couldn't execute the requested operation for %s", target); - break; - case IPC_ERROR: - warnx("btpd encountered an error for %s", target); - break; - default: + case IPC_COMMERR: errx(1, "fatal error in communication with btpd"); + default: + warnx("btpd response for '%s': %s", target, ipc_strerror(code)); } return code; } char -state_char(struct tpstat *ts) +tstate_char(enum ipc_tstate ts) { - switch (ts->state) { - case T_STARTING: + switch (ts) { + case IPC_TSTATE_INACTIVE: + return 'I'; + case IPC_TSTATE_START: return '+'; - case T_ACTIVE: - return ts->pieces_got == ts->torrent_pieces ? 'S' : 'L'; - case T_STOPPING: + case IPC_TSTATE_STOP: return '-'; - default: - return ' '; + case IPC_TSTATE_LEECH: + return 'L'; + case IPC_TSTATE_SEED: + return 'S'; } + errx(1, "bad state"); } -void -print_stat(struct tpstat *ts) -{ - printf("%c %5.1f%% %6.1fM %7.2fkB/s %6.1fM %7.2fkB/s %4u %5.1f%%", - state_char(ts), - floor(1000.0 * ts->content_got / ts->content_size) / 10, - (double)ts->downloaded / (1 << 20), - (double)ts->rate_down / (20 << 10), - (double)ts->uploaded / (1 << 20), - (double)ts->rate_up / (20 << 10), - ts->peers, - floor(1000.0 * ts->pieces_seen / ts->torrent_pieces) / 10); - if (ts->tr_errors > 0) - printf(" E%u", ts->tr_errors); - printf("\n"); -} - -void -usage_add(void) -{ - printf( - "Add torrents to btpd.\n" - "\n" - "Usage: add [--topdir] -d dir file\n" - " add file ...\n" - "\n" - "Arguments:\n" - "file ...\n" - "\tOne or more torrents to add.\n" - "\n" - "Options:\n" - "-d dir\n" - "\tUse the dir for content.\n" - "\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; - - 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(); - - btpd_connect(); - for (int i = 0; i < argc; i++) { - struct metainfo *mi; - char rdpath[PATH_MAX], 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", dir, mi->name); - else if (dir != NULL) - strncpy(dpath, dir, 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(dpath, rdpath) == NULL) - err(1, "path error on %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, rdpath), argv[i]); - clear_metainfo(mi); - free(mi); - } -} - -void -usage_del(void) -{ - printf( - "Remove torrents from btpd.\n" - "\n" - "Usage: del file ...\n" - "\n" - "Arguments:\n" - "file ...\n" - "\tThe torrents to remove.\n" - "\n"); - exit(1); -} - -void -cmd_del(int argc, char **argv) -{ - if (argc < 2) - usage_del(); - - btpd_connect(); - 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 -usage_kill(void) -{ - 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); -} - -void -cmd_kill(int argc, char **argv) -{ - int seconds = -1; - char *endptr; - - if (argc == 2) { - seconds = strtol(argv[1], &endptr, 10); - if (strlen(argv[1]) > endptr - argv[1] || seconds < 0) - usage_kill(); - } else if (argc > 2) - usage_kill(); - - btpd_connect(); - handle_ipc_res(btpd_die(ipc, seconds), "kill"); -} - -void -usage_list(void) -{ - printf( - "List active torrents.\n" - "\n" - "Usage: list\n" - "\n" - ); - exit(1); -} - -void -cmd_list(int argc, char **argv) -{ - struct btstat *st; - - if (argc > 1) - usage_list(); - - btpd_connect(); - if (handle_ipc_res(btpd_stat(ipc, &st), "list") != IPC_OK) - exit(1); - for (int i = 0; i < st->ntorrents; i++) { - struct tpstat *ts = &st->torrents[i]; - printf("%c. %s\n", state_char(ts), ts->name); - } - printf("%u torrent%s.\n", st->ntorrents, - st->ntorrents == 1 ? "" : "s"); -} - -void -usage_stat(void) -{ - printf( - "Display stats for active torrents.\n" - "The displayed stats are:\n" - "%% got, MB down, rate down. MB up, rate up\n" - "peer count, %% of pieces seen, tracker errors\n" - "\n" - "Usage: stat [-i] [-w seconds] [file ...]\n" - "\n" - "Arguments:\n" - "file ...\n" - "\tOnly display stats for the given torrent(s).\n" - "\n" - "Options:\n" - "-i\n" - "\tDisplay individual lines for each torrent.\n" - "\n" - "-w n\n" - "\tDisplay stats every n seconds.\n" - "\n"); - exit(1); -} - -void -do_stat(int individual, int seconds, int hash_count, uint8_t (*hashes)[20]) -{ - struct btstat *st; - struct tpstat tot; -again: - bzero(&tot, sizeof(tot)); - tot.state = -1; - 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 (hash_count > 0) { - int found = 0; - for (int h = 0; !found && h < hash_count; h++) - if (bcmp(cur->hash, hashes[h], 20) == 0) - found = 1; - if (!found) - continue; - } - tot.uploaded += cur->uploaded; - tot.downloaded += cur->downloaded; - tot.rate_up += cur->rate_up; - tot.rate_down += cur->rate_down; - tot.peers += cur->peers; - tot.pieces_seen += cur->pieces_seen; - tot.torrent_pieces += cur->torrent_pieces; - tot.content_got += cur->content_got; - tot.content_size += cur->content_size; - if (cur->tr_errors > 0) - tot.tr_errors++; - if (individual) { - printf("%s:\n", cur->name); - print_stat(cur); - } - } - free_btstat(st); - if (individual) - printf("Total:\n"); - print_stat(&tot); - if (seconds > 0) { - sleep(seconds); - goto again; - } -} - -struct option stat_opts [] = { - { "help", no_argument, NULL, 'H' }, - {NULL, 0, NULL, 0} -}; - -void -cmd_stat(int argc, char **argv) +torrent_spec(char *arg, struct ipc_torrent *tp) { - int ch; - int wflag = 0, iflag = 0, seconds = 0; - uint8_t (*hashes)[20] = NULL; - char *endptr; - while ((ch = getopt_long(argc, argv, "iw:", stat_opts, NULL)) != -1) { - switch (ch) { - case 'i': - iflag = 1; - break; - case 'w': - wflag = 1; - seconds = strtol(optarg, &endptr, 10); - if (strlen(optarg) > endptr - optarg || seconds < 1) - usage_stat(); - break; - default: - usage_stat(); - } + char *p; + tp->u.num = strtoul(arg, &p, 10); + if (*p == '\0') { + tp->by_hash = 0; + return 1; } - argc -= optind; - argv += optind; - - if (argc > 0) { - hashes = malloc(argc * 20); - for (int i = 0; i < argc; i++) { - struct metainfo *mi; - if ((errno = load_metainfo(argv[i], -1, 0, &mi)) != 0) - err(1, "error loading torrent %s", argv[i]); - bcopy(mi->info_hash, hashes[i], 20); - clear_metainfo(mi); - free(mi); - } + if ((p = mi_load(arg, NULL)) == NULL) { + warnx("bad torrent '%s' (%s)", arg, strerror(errno)); + return 0; } - btpd_connect(); - do_stat(iflag, seconds, argc, hashes); + tp->by_hash = 1; + mi_info_hash(p, tp->u.hash); + free(p); + return 1; } struct { @@ -409,6 +70,8 @@ struct { { "del", cmd_del, usage_del }, { "kill", cmd_kill, usage_kill }, { "list", cmd_list, usage_list }, + { "start", cmd_start, usage_start }, + { "stop", cmd_stop, usage_stop }, { "stat", cmd_stat, usage_stat } }; @@ -434,7 +97,9 @@ usage(void) "del\n" "kill\n" "list\n" + "start\n" "stat\n" + "stop\n" "\n"); exit(1); } |