From 86754d7d53b59ce52de51a32f865d4916c119bdb Mon Sep 17 00:00:00 2001 From: Richard Nyberg Date: Wed, 13 Sep 2006 07:02:07 +0000 Subject: [PATCH] btpd now has a library of torrents indexed by number and info hash. The add and del commands adds or removes torrents from this library. The start and stop commands are used to active or deactivate torrents. Also, a mechanism for qeurying data on torrents has been added. It's only used by the btcli list and stat commands yet though. btcli has been split into different files for each command. Both btpd and btcli now use misc/btpd_if.h for all ipc definitions. Misc changes: - struct metainfo is gone. Use the new mi_* functions. - Add printf format type checking where appropriate. --- btpd/Makefile.am | 3 +- btpd/active.c | 5 +- btpd/btpd.c | 1 + btpd/btpd.h | 12 +- btpd/cli_if.c | 386 +++++++++++++++++++++++++++++++---------- btpd/content.c | 125 +++++++------- btpd/download.c | 2 +- btpd/download_subr.c | 10 +- btpd/net.c | 19 +- btpd/net_buf.c | 6 +- btpd/peer.c | 10 +- btpd/tlib.c | 246 ++++++++++++++++++++++++++ btpd/tlib.h | 30 ++++ btpd/torrent.c | 118 +++++++------ btpd/torrent.h | 20 ++- btpd/tracker_req.c | 23 ++- btpd/tracker_req.h | 2 +- cli/Makefile.am | 2 +- cli/add.c | 87 ++++++++++ cli/btcli.c | 403 ++++--------------------------------------- cli/btcli.h | 44 +++++ cli/btinfo.c | 35 +++- cli/del.c | 30 ++++ cli/kill.c | 35 ++++ cli/list.c | 124 +++++++++++++ cli/start.c | 27 +++ cli/stat.c | 193 +++++++++++++++++++++ cli/stop.c | 27 +++ 28 files changed, 1388 insertions(+), 637 deletions(-) create mode 100644 btpd/tlib.c create mode 100644 btpd/tlib.h create mode 100644 cli/add.c create mode 100644 cli/btcli.h create mode 100644 cli/del.c create mode 100644 cli/kill.c create mode 100644 cli/list.c create mode 100644 cli/start.c create mode 100644 cli/stat.c create mode 100644 cli/stop.c diff --git a/btpd/Makefile.am b/btpd/Makefile.am index cb72911..a77def4 100644 --- a/btpd/Makefile.am +++ b/btpd/Makefile.am @@ -10,8 +10,7 @@ btpd_SOURCES=\ net_buf.c net_buf.h\ opts.c opts.h\ peer.c peer.h\ - queue.h\ - torrent.c torrent.h\ + tlib.c tlib.h torrent.c torrent.h\ tracker_req.c tracker_req.h\ upload.c upload.h\ util.c diff --git a/btpd/active.c b/btpd/active.c index 8a2b0aa..9db9a04 100644 --- a/btpd/active.c +++ b/btpd/active.c @@ -83,8 +83,9 @@ active_start(void) pos = 0; while (fread(hash, sizeof(hash), 1, fp) == 1) { - if (torrent_get(hash) == NULL) - if (torrent_start(hash) != 0) { + struct tlib *tl = tlib_by_hash(hash); + if (tl != NULL && tl->tp == NULL) + if (torrent_start(tl) != 0) { active_del_pos(fp, pos, &sb.st_size); fseek(fp, pos, SEEK_SET); } diff --git a/btpd/btpd.c b/btpd/btpd.c index 8716001..f2b9c00 100644 --- a/btpd/btpd.c +++ b/btpd/btpd.c @@ -201,6 +201,7 @@ btpd_init(void) ipc_init(); ul_init(); cm_init(); + tlib_init(); signal(SIGPIPE, SIG_IGN); diff --git a/btpd/btpd.h b/btpd/btpd.h index 55f235f..481c365 100644 --- a/btpd/btpd.h +++ b/btpd/btpd.h @@ -17,17 +17,22 @@ #include "benc.h" #include "metainfo.h" +#include "subr.h" #include "iobuf.h" +#include "hashtable.h" #include "net_buf.h" #include "net_types.h" #include "net.h" #include "peer.h" +#include "tlib.h" #include "torrent.h" #include "download.h" #include "upload.h" -#include "subr.h" #include "content.h" #include "opts.h" +#define DAEMON +#include "btpd_if.h" +#undef DAEMON #define BTPD_VERSION (PACKAGE_NAME "/" PACKAGE_VERSION) @@ -43,10 +48,15 @@ extern long btpd_seconds; void btpd_init(void); +__attribute__((format (printf, 2, 3))) void btpd_log(uint32_t type, const char *fmt, ...); + +__attribute__((format (printf, 1, 2), noreturn)) void btpd_err(const char *fmt, ...); +__attribute__((malloc)) void *btpd_malloc(size_t size); +__attribute__((malloc)) void *btpd_calloc(size_t nmemb, size_t size); void btpd_ev_add(struct event *ev, struct timeval *tv); diff --git a/btpd/cli_if.c b/btpd/cli_if.c index a6bde4f..3e71239 100644 --- a/btpd/cli_if.c +++ b/btpd/cli_if.c @@ -22,13 +22,6 @@ struct cli { static struct event m_cli_incoming; -enum ipc_code { // XXX: Same as in cli/btpd_if.h - IPC_OK, - IPC_FAIL, - IPC_ERROR, - IPC_COMMERR -}; - static int write_buffer(struct cli *cli, struct io_buffer *iob) { @@ -45,7 +38,7 @@ write_buffer(struct cli *cli, struct io_buffer *iob) } static int -write_code_buffer(struct cli *cli, enum ipc_code code) +write_code_buffer(struct cli *cli, enum ipc_err code) { struct io_buffer iob; buf_init(&iob, 16); @@ -54,111 +47,314 @@ write_code_buffer(struct cli *cli, enum ipc_code code) } static int -cmd_stat(struct cli *cli, int argc, const char *args) +write_add_buffer(struct cli *cli, unsigned num) +{ + struct io_buffer iob; + buf_init(&iob, 32); + buf_print(&iob, "d4:codei%ue3:numi%uee", IPC_OK, num); + return write_buffer(cli, &iob); +} + +static void +write_ans(struct io_buffer *iob, struct tlib *tl, enum ipc_tval val) +{ + enum ipc_tstate ts = IPC_TSTATE_INACTIVE; + switch (val) { + case IPC_TVAL_CGOT: + if (tl->tp == NULL) + buf_print(iob, "i%dei%de", IPC_TYPE_ERR, IPC_ETINACTIVE); + else + buf_print(iob, "i%dei%llde", IPC_TYPE_NUM, + (long long)cm_content(tl->tp)); + return; + case IPC_TVAL_CSIZE: + if (tl->tp == NULL) + buf_print(iob, "i%dei%de", IPC_TYPE_ERR, IPC_ETINACTIVE); + else + buf_print(iob, "i%dei%llde", IPC_TYPE_NUM, + (long long)tl->tp->total_length); + return; + case IPC_TVAL_PCCOUNT: + if (tl->tp == NULL) + buf_print(iob, "i%dei%de", IPC_TYPE_ERR, IPC_ETINACTIVE); + else + buf_print(iob, "i%dei%lue", IPC_TYPE_NUM, + (unsigned long)tl->tp->npieces); + return; + case IPC_TVAL_PCGOT: + if (tl->tp == NULL) + buf_print(iob, "i%dei%de", IPC_TYPE_ERR, IPC_ETINACTIVE); + else + buf_print(iob, "i%dei%lue", IPC_TYPE_NUM, + (unsigned long)cm_pieces(tl->tp)); + return; + case IPC_TVAL_PCSEEN: + if (tl->tp == NULL) + buf_print(iob, "i%dei%de", IPC_TYPE_ERR, IPC_ETINACTIVE); + else { + unsigned long pcseen = 0; + for (unsigned long i = 0; i < tl->tp->npieces; i++) + if (tl->tp->net->piece_count[i] > 0) + pcseen++; + buf_print(iob, "i%dei%lue", IPC_TYPE_NUM, pcseen); + } + return; + case IPC_TVAL_RATEDWN: + if (tl->tp == NULL) + buf_print(iob, "i%dei%de", IPC_TYPE_ERR, IPC_ETINACTIVE); + else + buf_print(iob, "i%dei%lue", IPC_TYPE_NUM, tl->tp->net->rate_dwn); + return; + case IPC_TVAL_RATEUP: + if (tl->tp == NULL) + buf_print(iob, "i%dei%de", IPC_TYPE_ERR, IPC_ETINACTIVE); + else + buf_print(iob, "i%dei%lue", IPC_TYPE_NUM, tl->tp->net->rate_up); + return; + case IPC_TVAL_SESSDWN: + if (tl->tp == NULL) + buf_print(iob, "i%dei%de", IPC_TYPE_ERR, IPC_ETINACTIVE); + else + buf_print(iob, "i%dei%llde", IPC_TYPE_NUM, + tl->tp->net->downloaded); + return; + case IPC_TVAL_SESSUP: + if (tl->tp == NULL) + buf_print(iob, "i%dei%de", IPC_TYPE_ERR, IPC_ETINACTIVE); + else + buf_print(iob, "i%dei%llde", IPC_TYPE_NUM, tl->tp->net->uploaded); + return; + case IPC_TVAL_DIR: + if (tl->dir != NULL) + buf_print(iob, "i%de%d:%s", IPC_TYPE_STR, (int)strlen(tl->dir), + tl->dir); + else + buf_print(iob, "i%dei%de", IPC_TYPE_ERR, IPC_EBADTENT); + return; + case IPC_TVAL_NAME: + if (tl->name != NULL) + buf_print(iob, "i%de%d:%s", IPC_TYPE_STR, (int)strlen(tl->name), + tl->name); + else + buf_print(iob, "i%dei%de", IPC_TYPE_ERR, IPC_EBADTENT); + return; + case IPC_TVAL_IHASH: + buf_print(iob, "i%de20:", IPC_TYPE_BIN); + buf_write(iob, tl->hash, 20); + return; + case IPC_TVAL_NUM: + buf_print(iob, "i%dei%ue", IPC_TYPE_NUM, tl->num); + return; + case IPC_TVAL_PCOUNT: + buf_print(iob, "i%dei%ue", IPC_TYPE_NUM, + tl->tp == NULL ? 0 : tl->tp->net->npeers); + return; + case IPC_TVAL_STATE: + buf_print(iob, "i%de", IPC_TYPE_NUM); + if (tl->tp != NULL) { + switch (tl->tp->state) { + case T_STARTING: + ts = IPC_TSTATE_START; + break; + case T_STOPPING: + ts= IPC_TSTATE_STOP; + break; + case T_ACTIVE: + if (cm_full(tl->tp)) + ts = IPC_TSTATE_SEED; + else + ts = IPC_TSTATE_LEECH; + break; + } + } + buf_print(iob, "i%de", ts); + return; + case IPC_TVAL_TRERR: + buf_print(iob, "i%dei%ue", IPC_TYPE_NUM, + tl->tp == NULL ? 0 : tr_errors(tl->tp)); + return; + case IPC_TVALCOUNT: + break; + } + buf_print(iob, "i%dei%de", IPC_TYPE_ERR, IPC_ENOKEY); +} + +static int +cmd_tget(struct cli *cli, int argc, const char *args) { - struct torrent *tp; + if (argc != 1 || !benc_isdct(args)) + return IPC_COMMERR; + + size_t nkeys; + const char *keys, *p; + enum ipc_tval *opts; struct io_buffer iob; - buf_init(&iob, (1 << 14)); - - buf_swrite(&iob, "d"); - buf_swrite(&iob, "4:codei0e"); - buf_print(&iob, "6:npeersi%ue", net_npeers); - buf_print(&iob, "9:ntorrentsi%ue", torrent_count()); - buf_swrite(&iob, "8:torrentsl"); - BTPDQ_FOREACH(tp, torrent_get_all(), entry) { - const char *name = torrent_name(tp); - 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_swrite(&iob, "d"); - buf_print(&iob, "11:content goti%llde", (long long)cm_content(tp)); - buf_print(&iob, "12:content sizei%llde", - (long long)tp->meta.total_length); - buf_print(&iob, "10:downloadedi%llde", tp->net->downloaded); - buf_swrite(&iob, "9:info hash20:"); - buf_write(&iob, tp->meta.info_hash, 20); - buf_print(&iob, "4:name%d:%s", (int)strlen(name), name); - buf_print(&iob, "5:peersi%ue", tp->net->npeers); - buf_print(&iob, "10:pieces goti%ue", cm_pieces(tp)); - buf_print(&iob, "11:pieces seeni%ue", seen_npieces); - buf_print(&iob, "9:rate downi%lue", tp->net->rate_dwn); - buf_print(&iob, "7:rate upi%lue", tp->net->rate_up); - buf_print(&iob, "5:statei%ue", tp->state); - buf_print(&iob, "14:torrent piecesi%ue", tp->meta.npieces); - buf_print(&iob, "14:tracker errorsi%ue", tr_errors(tp)); - buf_print(&iob, "8:uploadedi%llde", tp->net->uploaded); - buf_swrite(&iob, "e"); + + if ((keys = benc_dget_lst(args, "keys")) == NULL) + return IPC_COMMERR; + + nkeys = benc_nelems(keys); + opts = btpd_calloc(nkeys, sizeof(*opts)); + + p = benc_first(keys); + for (int i = 0; i < nkeys; i++) + opts[i] = benc_int(p, &p); + + buf_init(&iob, (1 << 15)); + buf_swrite(&iob, "d4:codei0e6:resultl"); + p = benc_dget_any(args, "from"); + if (benc_isint(p)) { + enum ipc_twc from = benc_int(p, NULL); + struct tlib *tlv[tlib_count()]; + tlib_put_all(tlv); + for (int i = 0; i < sizeof(tlv) / sizeof(tlv[0]); i++) { + if ((from == IPC_TWC_ALL || + (tlv[i]->tp == NULL && from == IPC_TWC_INACTIVE) || + (tlv[i]->tp != NULL && from == IPC_TWC_ACTIVE))) { + buf_swrite(&iob, "l"); + for (int k = 0; k < nkeys; k++) + write_ans(&iob, tlv[i], opts[k]); + buf_swrite(&iob, "e"); + } + } + } else if (benc_islst(p)) { + for (p = benc_first(p); p != NULL; p = benc_next(p)) { + struct tlib *tl = NULL; + if (benc_isint(p)) + tl = tlib_by_num(benc_int(p, NULL)); + else if (benc_isstr(p) && benc_strlen(p) == 20) + tl = tlib_by_hash(benc_mem(p, NULL, NULL)); + else { + free(iob.buf); + free(opts); + return IPC_COMMERR; + } + if (tl != NULL) { + buf_swrite(&iob, "l"); + for (int i = 0; i < nkeys; i++) + write_ans(&iob, tl, opts[i]); + buf_swrite(&iob, "e"); + } else + buf_print(&iob, "i%de", IPC_ENOTENT); + } } buf_swrite(&iob, "ee"); + free(opts); return write_buffer(cli, &iob); } static int cmd_add(struct cli *cli, int argc, const char *args) +{ + if (argc != 1 || !benc_isdct(args)) + return IPC_COMMERR; + + struct tlib *tl; + size_t mi_size = 0, csize = 0; + const char *mi, *cp; + char content[PATH_MAX]; + uint8_t hash[20]; + + if ((mi = benc_dget_mem(args, "torrent", &mi_size)) == NULL) + return IPC_COMMERR; + + if (!mi_test(mi, mi_size)) + return write_code_buffer(cli, IPC_EBADT); + + if ((cp = benc_dget_mem(args, "content", &csize)) == NULL || + csize >= PATH_MAX || csize == 0) + return write_code_buffer(cli, IPC_EBADCDIR); + + if (cp[0] != '/') + return write_code_buffer(cli, IPC_EBADCDIR); + bcopy(cp, content, csize); + content[csize] = '\0'; + + tl = tlib_by_hash(mi_info_hash(mi, hash)); + if (tl != NULL) + return write_code_buffer(cli, IPC_ETENTEXIST); + tl = tlib_add(hash, mi, mi_size, content, + benc_dget_str(args, "name", NULL)); + return write_add_buffer(cli, tl->num); +} + +static int +cmd_del(struct cli *cli, int argc, const char *args) { if (argc != 1) - return EINVAL; - if (btpd_is_stopping()) - return write_code_buffer(cli, IPC_FAIL); - - size_t hlen; - struct torrent *tp; - enum ipc_code code = IPC_OK; - const uint8_t *hash = benc_dget_mem(args, "hash", &hlen); - char *content = benc_dget_str(args, "content", NULL); - char *torrent = benc_dget_str(args, "torrent", NULL); - - if (!(hlen == 20 && content != NULL && torrent != NULL)) { - code = IPC_COMMERR; - goto out; - } - if ((tp = torrent_get(hash)) != NULL) { - code = tp->state == T_STOPPING ? IPC_FAIL : IPC_OK; - goto out; - } - if (torrent_set_links(hash, torrent, content) != 0) { - code = IPC_ERROR; - goto out; - } - if (torrent_start(hash) != 0) { - code = IPC_ERROR; - goto out; - } + return IPC_COMMERR; + + struct tlib *tl; + enum ipc_err code = IPC_OK; + if (benc_isstr(args) && benc_strlen(args) == 20) + tl = tlib_by_hash(benc_mem(args, NULL, NULL)); + else if (benc_isint(args)) + tl = tlib_by_num(benc_int(args, NULL)); + else + return IPC_COMMERR; - active_add(hash); + if (tl == NULL) + code = IPC_ENOTENT; + else if (tl->tp != NULL) + code = IPC_ETACTIVE; + else + tlib_del(tl); -out: - if (content != NULL) - free(content); - if (torrent != NULL) - free(torrent); + return write_code_buffer(cli, code); +} - if (code == IPC_COMMERR) - return EINVAL; +static int +cmd_start(struct cli *cli, int argc, const char *args) +{ + if (argc != 1) + return IPC_COMMERR; + if (btpd_is_stopping()) + return write_code_buffer(cli, IPC_ESHUTDOWN); + + struct tlib *tl; + enum ipc_err code = IPC_OK; + if (benc_isstr(args) && benc_strlen(args) == 20) + tl = tlib_by_hash(benc_mem(args, NULL, NULL)); + else if (benc_isint(args)) + tl = tlib_by_num(benc_int(args, NULL)); else - return write_code_buffer(cli, code); + return IPC_COMMERR; + + if (tl == NULL) + code = IPC_ENOTENT; + else if (tl->tp != NULL) + code = IPC_ETACTIVE; + else + if ((code = torrent_start(tl)) == IPC_OK) + active_add(tl->hash); + return write_code_buffer(cli, code); } static int -cmd_del(struct cli *cli, int argc, const char *args) +cmd_stop(struct cli *cli, int argc, const char *args) { - if (argc != 1 || !benc_isstr(args)) - return EINVAL; - - size_t hlen; - uint8_t *hash = (uint8_t *)benc_mem(args, &hlen, NULL); - if (hlen != 20) - return EINVAL; - // Stopping a torrent may trigger exit so we need to reply before. - int ret = write_code_buffer(cli, IPC_OK); - struct torrent *tp = torrent_get(hash); - if (tp != NULL) { - torrent_stop(tp); - active_del(hash); + if (argc != 1) + return IPC_COMMERR; + + struct tlib *tl; + if (benc_isstr(args) && benc_strlen(args) == 20) + tl = tlib_by_hash(benc_mem(args, NULL, NULL)); + else if (benc_isint(args)) + tl = tlib_by_num(benc_int(args, NULL)); + else + return IPC_COMMERR; + + if (tl == NULL) + return write_code_buffer(cli, IPC_ENOTENT); + else if (tl->tp == NULL) + return write_code_buffer(cli, IPC_ETINACTIVE); + else { + // Stopping a torrent may trigger exit so we need to reply before. + int ret = write_code_buffer(cli, IPC_OK); + active_del(tl->hash); + torrent_stop(tl->tp); + return ret; } - return ret; } static int @@ -183,7 +379,9 @@ static struct { { "add", 3, cmd_add }, { "del", 3, cmd_del }, { "die", 3, cmd_die }, - { "stat", 4, cmd_stat } + { "start", 5, cmd_start }, + { "stop", 4, cmd_stop }, + { "tget", 4, cmd_tget } }; static int ncmds = sizeof(cmd_table) / sizeof(cmd_table[0]); diff --git a/btpd/content.c b/btpd/content.c index 31c526b..e69216f 100644 --- a/btpd/content.c +++ b/btpd/content.c @@ -99,15 +99,14 @@ static int fd_cb_rd(const char *path, int *fd, void *arg) { struct torrent *tp = arg; - return vopen(fd, O_RDONLY, "torrents/%s/content/%s", tp->relpath, path); + return vopen(fd, O_RDONLY, "%s/%s", tp->tl->dir, path); } static int fd_cb_wr(const char *path, int *fd, void *arg) { struct torrent *tp = arg; - return vopen(fd, O_RDWR|O_CREAT, "torrents/%s/content/%s", tp->relpath, - path); + return vopen(fd, O_RDWR|O_CREAT, "%s/%s", tp->tl->dir, path); } static void @@ -255,7 +254,8 @@ cm_td_cb(void *arg) if (cm->active) { assert(!op->u.start.cancel); if (!cm_full(tp)) { - if ((err = bts_open(&cm->wrs, &tp->meta, fd_cb_wr, tp)) != 0) + if ((err = bts_open(&cm->wrs, tp->nfiles, tp->files, + fd_cb_wr, tp)) != 0) btpd_err("Couldn't open write stream for '%s' (%s).\n", torrent_name(tp), strerror(err)); btpd_ev_add(&cm->save_timer, SAVE_INTERVAL); @@ -265,7 +265,7 @@ cm_td_cb(void *arg) break; case CM_TEST: if (op->u.test.ok) { - assert(cm->npieces_got < tp->meta.npieces); + assert(cm->npieces_got < tp->npieces); cm->npieces_got++; set_bit(cm->piece_field, op->u.test.piece); if (net_active(tp)) @@ -294,13 +294,13 @@ cm_td_cb(void *arg) void cm_create(struct torrent *tp) { - size_t pfield_size = ceil(tp->meta.npieces / 8.0); + size_t pfield_size = ceil(tp->npieces / 8.0); struct content *cm = btpd_calloc(1, sizeof(*cm)); - cm->bppbf = ceil((double)tp->meta.piece_length / (1 << 17)); + cm->bppbf = ceil((double)tp->piece_length / (1 << 17)); cm->piece_field = btpd_calloc(pfield_size, 1); cm->hold_field = btpd_calloc(pfield_size, 1); cm->pos_field = btpd_calloc(pfield_size, 1); - cm->block_field = btpd_calloc(tp->meta.npieces * cm->bppbf, 1); + cm->block_field = btpd_calloc(tp->npieces * cm->bppbf, 1); BTPDQ_INIT(&cm->todoq); evtimer_set(&cm->save_timer, save_timer_cb, tp); @@ -313,7 +313,7 @@ cm_start(struct torrent *tp) { struct content *cm = tp->cm; - if ((errno = bts_open(&cm->rds, &tp->meta, fd_cb_rd, tp)) != 0) + if ((errno = bts_open(&cm->rds, tp->nfiles, tp->files, fd_cb_rd, tp)) != 0) btpd_err("Error opening stream (%s).\n", strerror(errno)); cm->active = 1; @@ -329,7 +329,7 @@ cm_get_bytes(struct torrent *tp, uint32_t piece, uint32_t begin, size_t len, { *buf = btpd_malloc(len); int err = - bts_get(tp->cm->rds, piece * tp->meta.piece_length + begin, *buf, len); + bts_get(tp->cm->rds, piece * tp->piece_length + begin, *buf, len); if (err != 0) btpd_err("Io error (%s)\n", strerror(err)); return 0; @@ -365,9 +365,9 @@ cm_prealloc(struct torrent *tp, uint32_t piece) if (cm_alloc_size <= 0) set_bit(cm->pos_field, piece); else { - unsigned npieces = ceil((double)cm_alloc_size / tp->meta.piece_length); + unsigned npieces = ceil((double)cm_alloc_size / tp->piece_length); uint32_t start = piece - piece % npieces; - uint32_t end = min(start + npieces, tp->meta.npieces); + uint32_t end = min(start + npieces, tp->npieces); while (start < end) { if ((!has_bit(cm->pos_field, start) @@ -416,7 +416,7 @@ cm_put_bytes(struct torrent *tp, uint32_t piece, uint32_t begin, if (it == NULL) BTPDQ_INSERT_TAIL(&op->u.write.q, d, entry); } else { - err = bts_put(cm->wrs, piece * tp->meta.piece_length + begin, buf, + err = bts_put(cm->wrs, piece * tp->piece_length + begin, buf, len); if (err != 0) btpd_err("Io error (%s)\n", strerror(err)); @@ -432,7 +432,7 @@ cm_put_bytes(struct torrent *tp, uint32_t piece, uint32_t begin, int cm_full(struct torrent *tp) { - return tp->cm->npieces_got == tp->meta.npieces; + return tp->cm->npieces_got == tp->npieces; } off_t @@ -468,23 +468,19 @@ cm_has_piece(struct torrent *tp, uint32_t piece) static int test_hash(struct torrent *tp, uint8_t *hash, uint32_t piece) { - if (tp->meta.piece_hash != NULL) - return bcmp(hash, tp->meta.piece_hash[piece], SHA_DIGEST_LENGTH); - else { - char piece_hash[SHA_DIGEST_LENGTH]; - int fd; - int err; + char piece_hash[SHA_DIGEST_LENGTH]; + int fd; + int err; - err = vopen(&fd, O_RDONLY, "torrents/%s/torrent", tp->relpath); - if (err != 0) - btpd_err("test_hash: %s\n", strerror(err)); + err = vopen(&fd, O_RDONLY, "torrents/%s/torrent", tp->relpath); + if (err != 0) + btpd_err("test_hash: %s\n", strerror(err)); - lseek(fd, tp->meta.pieces_off + piece * SHA_DIGEST_LENGTH, SEEK_SET); - read(fd, piece_hash, SHA_DIGEST_LENGTH); - close(fd); + lseek(fd, tp->pieces_off + piece * SHA_DIGEST_LENGTH, SEEK_SET); + read(fd, piece_hash, SHA_DIGEST_LENGTH); + close(fd); - return bcmp(hash, piece_hash, SHA_DIGEST_LENGTH); - } + return bcmp(hash, piece_hash, SHA_DIGEST_LENGTH); } static int @@ -493,9 +489,9 @@ test_piece(struct torrent *tp, uint32_t pos, uint32_t piece, int *ok) int err; uint8_t hash[SHA_DIGEST_LENGTH]; struct bt_stream *bts; - if ((err = bts_open(&bts, &tp->meta, fd_cb_rd, tp)) != 0) + if ((err = bts_open(&bts, tp->nfiles, tp->files, fd_cb_rd, tp)) != 0) return err; - if ((err = bts_sha(bts, pos * tp->meta.piece_length, + if ((err = bts_sha(bts, pos * tp->piece_length, torrent_piece_size(tp, piece), hash)) != 0) return err;; bts_close(bts); @@ -514,11 +510,11 @@ cm_td_alloc(struct cm_op *op) assert(!has_bit(cm->pos_field, pos)); - if ((err = bts_open(&bts, &tp->meta, fd_cb_wr, tp)) != 0) + if ((err = bts_open(&bts, tp->nfiles, tp->files, fd_cb_wr, tp)) != 0) goto out; off_t len = torrent_piece_size(tp, pos); - off_t off = tp->meta.piece_length * pos; + off_t off = tp->piece_length * pos; while (len > 0) { size_t wlen = min(ZEROBUFLEN, len); if ((err = bts_put(bts, off, m_zerobuf, wlen)) != 0) { @@ -545,22 +541,20 @@ test_torrent(struct torrent *tp, volatile sig_atomic_t *cancel) if ((err = vfopen(&fp, "r", "torrents/%s/torrent", tp->relpath)) != 0) return err; - hashes = btpd_malloc(tp->meta.npieces * SHA_DIGEST_LENGTH); - fseek(fp, tp->meta.pieces_off, SEEK_SET); - fread(hashes, SHA_DIGEST_LENGTH, tp->meta.npieces, fp); + hashes = btpd_malloc(tp->npieces * SHA_DIGEST_LENGTH); + fseek(fp, tp->pieces_off, SEEK_SET); + fread(hashes, SHA_DIGEST_LENGTH, tp->npieces, fp); fclose(fp); - tp->meta.piece_hash = hashes; - struct content *cm = tp->cm; - for (uint32_t piece = 0; piece < tp->meta.npieces; piece++) { + for (uint32_t piece = 0; piece < tp->npieces; piece++) { if (!has_bit(cm->pos_field, piece)) continue; - err = bts_sha(cm->rds, piece * tp->meta.piece_length, + err = bts_sha(cm->rds, piece * tp->piece_length, torrent_piece_size(tp, piece), hash); if (err != 0) break; - if (test_hash(tp, hash, piece) == 0) + if (bcmp(hashes[piece], hash, SHA_DIGEST_LENGTH) == 0) set_bit(tp->cm->piece_field, piece); else clear_bit(tp->cm->piece_field, piece); @@ -570,7 +564,6 @@ test_torrent(struct torrent *tp, volatile sig_atomic_t *cancel) } } - tp->meta.piece_hash = NULL; free(hashes); return err; } @@ -585,9 +578,8 @@ stat_and_adjust(struct torrent *tp, struct rstat ret[]) { char path[PATH_MAX]; struct stat sb; - for (int i = 0; i < tp->meta.nfiles; i++) { - snprintf(path, PATH_MAX, "torrents/%s/content/%s", tp->relpath, - tp->meta.files[i].path); + for (int i = 0; i < tp->nfiles; i++) { + snprintf(path, PATH_MAX, "%s/%s", tp->tl->dir, tp->files[i].path); again: if (stat(path, &sb) == -1) { if (errno == ENOENT) { @@ -599,8 +591,8 @@ again: ret[i].mtime = sb.st_mtime; ret[i].size = sb.st_size; } - if (ret[i].size > tp->meta.files[i].length) { - if (truncate(path, tp->meta.files[i].length) != 0) + if (ret[i].size > tp->files[i].length) { + if (truncate(path, tp->files[i].length) != 0) return errno; goto again; } @@ -613,8 +605,8 @@ load_resume(struct torrent *tp, struct rstat sbs[]) { int err, ver; FILE *fp; - size_t pfsiz = ceil(tp->meta.npieces / 8.0); - size_t bfsiz = tp->meta.npieces * tp->cm->bppbf; + size_t pfsiz = ceil(tp->npieces / 8.0); + size_t bfsiz = tp->npieces * tp->cm->bppbf; if ((err = vfopen(&fp, "r" , "torrents/%s/resume", tp->relpath)) != 0) return err; @@ -623,7 +615,7 @@ load_resume(struct torrent *tp, struct rstat sbs[]) goto invalid; if (ver != 1) goto invalid; - for (int i = 0; i < tp->meta.nfiles; i++) { + for (int i = 0; i < tp->nfiles; i++) { quad_t size; long time; if (fscanf(fp, "%qd %ld\n", &size, &time) != 2) @@ -652,10 +644,10 @@ save_resume(struct torrent *tp, struct rstat sbs[]) if ((err = vfopen(&fp, "wb", "torrents/%s/resume", tp->relpath)) != 0) return err; fprintf(fp, "%d\n", 1); - for (int i = 0; i < tp->meta.nfiles; i++) + for (int i = 0; i < tp->nfiles; i++) fprintf(fp, "%lld %ld\n", (long long)sbs[i].size, (long)sbs[i].mtime); - fwrite(tp->cm->piece_field, 1, ceil(tp->meta.npieces / 8.0), fp); - fwrite(tp->cm->block_field, 1, tp->meta.npieces * tp->cm->bppbf, fp); + fwrite(tp->cm->piece_field, 1, ceil(tp->npieces / 8.0), fp); + fwrite(tp->cm->block_field, 1, tp->npieces * tp->cm->bppbf, fp); if (fclose(fp) != 0) err = errno; return err; @@ -665,7 +657,7 @@ static void cm_td_save(struct cm_op *op) { struct torrent *tp = op->tp; - struct rstat sbs[tp->meta.nfiles]; + struct rstat sbs[tp->nfiles]; if (stat_and_adjust(tp, sbs) == 0) save_resume(tp, sbs); } @@ -674,7 +666,7 @@ static void cm_td_start(struct cm_op *op) { int err, resume_clean = 0, tested_torrent = 0; - struct rstat sbs[op->tp->meta.nfiles]; + struct rstat sbs[op->tp->nfiles]; struct torrent *tp = op->tp; struct content *cm = tp->cm; @@ -683,17 +675,17 @@ cm_td_start(struct cm_op *op) resume_clean = load_resume(tp, sbs) == 0; if (!resume_clean) { - memset(cm->pos_field, 0xff, ceil(tp->meta.npieces / 8.0)); + memset(cm->pos_field, 0xff, ceil(tp->npieces / 8.0)); off_t off = 0; - for (int i = 0; i < tp->meta.nfiles; i++) { - if (sbs[i].size != tp->meta.files[i].length) { + for (int i = 0; i < tp->nfiles; i++) { + if (sbs[i].size != tp->files[i].length) { uint32_t start, end; - end = (off + tp->meta.files[i].length - 1) - / tp->meta.piece_length; + end = (off + tp->files[i].length - 1) + / tp->piece_length; if (sbs[i].size == -1) - start = off / tp->meta.piece_length; + start = off / tp->piece_length; else - start = (off + sbs[i].size) / tp->meta.piece_length; + start = (off + sbs[i].size) / tp->piece_length; while (start <= end) { clear_bit(cm->pos_field, start); clear_bit(cm->piece_field, start); @@ -701,7 +693,7 @@ cm_td_start(struct cm_op *op) start++; } } - off += tp->meta.files[i].length; + off += tp->files[i].length; } if (op->u.start.cancel) goto out; @@ -710,8 +702,8 @@ cm_td_start(struct cm_op *op) tested_torrent = 1; } - bzero(cm->pos_field, ceil(tp->meta.npieces / 8.0)); - for (uint32_t piece = 0; piece < tp->meta.npieces; piece++) { + bzero(cm->pos_field, ceil(tp->npieces / 8.0)); + for (uint32_t piece = 0; piece < tp->npieces; piece++) { if (cm_has_piece(tp, piece)) { cm->ncontent_bytes += torrent_piece_size(tp, piece); cm->npieces_got++; @@ -768,9 +760,10 @@ cm_td_write(struct cm_op *op) { int err; struct cm_write_data *d, *next; - off_t base = op->tp->meta.piece_length * op->u.write.pos; + off_t base = op->tp->piece_length * op->u.write.pos; struct bt_stream *bts; - if ((err = bts_open(&bts, &op->tp->meta, fd_cb_wr, op->tp)) != 0) + if ((err = bts_open(&bts, op->tp->nfiles, op->tp->files, + fd_cb_wr, op->tp)) != 0) goto out; BTPDQ_FOREACH(d, &op->u.write.q, entry) if ((err = bts_put(bts, base + d->begin, d->buf, d->len)) != 0) { diff --git a/btpd/download.c b/btpd/download.c index 9ef54be..a836ac9 100644 --- a/btpd/download.c +++ b/btpd/download.c @@ -148,7 +148,7 @@ dl_on_lost_peer(struct peer *p) { struct net *n = p->n; - for (uint32_t i = 0; i < n->tp->meta.npieces; i++) + for (uint32_t i = 0; i < n->tp->npieces; i++) if (peer_has(p, i)) n->piece_count[i]--; diff --git a/btpd/download_subr.c b/btpd/download_subr.c index a38e553..d12efa9 100644 --- a/btpd/download_subr.c +++ b/btpd/download_subr.c @@ -33,7 +33,7 @@ static struct piece * piece_alloc(struct net *n, uint32_t index) { assert(!has_bit(n->busy_field, index) - && n->npcs_busy < n->tp->meta.npieces); + && n->npcs_busy < n->tp->npieces); struct piece *pc; size_t mem, field; unsigned nblocks; @@ -98,7 +98,7 @@ static int dl_should_enter_endgame(struct net *n) { int should; - if (cm_pieces(n->tp) + n->npcs_busy == n->tp->meta.npieces) { + if (cm_pieces(n->tp) + n->npcs_busy == n->tp->npieces) { should = 1; struct piece *pc; BTPDQ_FOREACH(pc, &n->getlst, entry) { @@ -216,15 +216,15 @@ dl_choose_rarest(struct peer *p, uint32_t *res) assert(n->endgame == 0); - for (i = 0; i < n->tp->meta.npieces && !dl_piece_startable(p, i); i++) + for (i = 0; i < n->tp->npieces && !dl_piece_startable(p, i); i++) ; - if (i == n->tp->meta.npieces) + if (i == n->tp->npieces) return ENOENT; uint32_t min_i = i; uint32_t min_c = 1; - for(i++; i < n->tp->meta.npieces; i++) { + for(i++; i < n->tp->npieces; i++) { if (dl_piece_startable(p, i)) { if (n->piece_count[i] == n->piece_count[min_i]) min_c++; diff --git a/btpd/net.c b/btpd/net.c index 37307d6..02171ed 100644 --- a/btpd/net.c +++ b/btpd/net.c @@ -53,9 +53,9 @@ net_torrent_has_peer(struct net *n, const uint8_t *id) void net_create(struct torrent *tp) { - size_t field_size = ceil(tp->meta.npieces / 8.0); + size_t field_size = ceil(tp->npieces / 8.0); size_t mem = sizeof(*(tp->net)) + field_size + - tp->meta.npieces * sizeof(*(tp->net->piece_count)); + tp->npieces * sizeof(*(tp->net->piece_count)); struct net *n = btpd_calloc(1, mem); n->tp = tp; @@ -260,7 +260,7 @@ net_dispatch_msg(struct peer *p, const char *buf) begin = net_read32(buf + 4); length = net_read32(buf + 8); if ((length > PIECE_BLOCKLEN - || index >= p->n->tp->meta.npieces + || index >= p->n->tp->npieces || !cm_has_piece(p->n->tp, index) || begin + length > torrent_piece_size(p->n->tp, index))) { btpd_log(BTPD_L_MSG, "bad request: (%u, %u, %u) from %p\n", @@ -300,7 +300,7 @@ net_mh_ok(struct peer *p) case MSG_HAVE: return mlen == 5; case MSG_BITFIELD: - return mlen == (uint32_t)ceil(p->n->tp->meta.npieces / 8.0) + 1; + return mlen == (uint32_t)ceil(p->n->tp->npieces / 8.0) + 1; case MSG_REQUEST: case MSG_CANCEL: return mlen == 13; @@ -331,15 +331,12 @@ net_state(struct peer *p, const char *buf) break; case SHAKE_INFO: if (p->flags & PF_INCOMING) { - struct net *n; - BTPDQ_FOREACH(n, &m_torrents, entry) - if (bcmp(buf, n->tp->meta.info_hash, 20) == 0) - break; - if (n == NULL) + struct torrent *tp = torrent_by_hash(buf); + if (tp == NULL || tp->net == NULL) goto bad; - p->n = n; + p->n = tp->net; peer_send(p, nb_create_shake(p->n->tp)); - } else if (bcmp(buf, p->n->tp->meta.info_hash, 20) != 0) + } else if (bcmp(buf, p->n->tp->tl->hash, 20) != 0) goto bad; peer_set_in_state(p, SHAKE_ID, 20); break; diff --git a/btpd/net_buf.c b/btpd/net_buf.c index 5b09ddb..6827c9c 100644 --- a/btpd/net_buf.c +++ b/btpd/net_buf.c @@ -184,7 +184,7 @@ nb_create_interest(void) struct net_buf * nb_create_bitfield(struct torrent *tp) { - uint32_t plen = ceil(tp->meta.npieces / 8.0); + uint32_t plen = ceil(tp->npieces / 8.0); struct net_buf *out = nb_create_alloc(NB_BITFIELD, 5); net_write32(out->buf, plen + 1); @@ -195,7 +195,7 @@ nb_create_bitfield(struct torrent *tp) struct net_buf * nb_create_bitdata(struct torrent *tp) { - uint32_t plen = ceil(tp->meta.npieces / 8.0); + uint32_t plen = ceil(tp->npieces / 8.0); struct net_buf *out = nb_create_set(NB_BITDATA, cm_get_piece_field(tp), plen, kill_buf_no); return out; @@ -206,7 +206,7 @@ nb_create_shake(struct torrent *tp) { struct net_buf *out = nb_create_alloc(NB_SHAKE, 68); bcopy("\x13""BitTorrent protocol\0\0\0\0\0\0\0\0", out->buf, 28); - bcopy(tp->meta.info_hash, out->buf + 28, 20); + bcopy(tp->tl->hash, out->buf + 28, 20); bcopy(btpd_get_peer_id(), out->buf + 48, 20); return out; } diff --git a/btpd/peer.c b/btpd/peer.c index 714489d..5731e05 100644 --- a/btpd/peer.c +++ b/btpd/peer.c @@ -357,10 +357,10 @@ peer_on_shake(struct peer *p) printid[i] = p->id[i]; printid[i] = '\0'; btpd_log(BTPD_L_MSG, "received shake(%s) from %p\n", printid, p); - p->piece_field = btpd_calloc(1, (int)ceil(p->n->tp->meta.npieces / 8.0)); + p->piece_field = btpd_calloc(1, (int)ceil(p->n->tp->npieces / 8.0)); if (cm_pieces(p->n->tp) > 0) { if ((cm_pieces(p->n->tp) * 9 < 5 + - ceil(p->n->tp->meta.npieces / 8.0))) + ceil(p->n->tp->npieces / 8.0))) peer_send(p, nb_create_multihave(p->n->tp)); else { peer_send(p, nb_create_bitfield(p->n->tp)); @@ -449,8 +449,8 @@ peer_on_bitfield(struct peer *p, const uint8_t *field) { btpd_log(BTPD_L_MSG, "received bitfield from %p\n", p); assert(p->npieces == 0); - bcopy(field, p->piece_field, (size_t)ceil(p->n->tp->meta.npieces / 8.0)); - for (uint32_t i = 0; i < p->n->tp->meta.npieces; i++) { + bcopy(field, p->piece_field, (size_t)ceil(p->n->tp->npieces / 8.0)); + for (uint32_t i = 0; i < p->n->tp->npieces; i++) { if (has_bit(p->piece_field, i)) { p->npieces++; dl_on_piece_ann(p, i); @@ -593,5 +593,5 @@ peer_active_up(struct peer *p) int peer_full(struct peer *p) { - return p->npieces == p->n->tp->meta.npieces; + return p->npieces == p->n->tp->npieces; } diff --git a/btpd/tlib.c b/btpd/tlib.c new file mode 100644 index 0000000..812a141 --- /dev/null +++ b/btpd/tlib.c @@ -0,0 +1,246 @@ +#include +#include + +#include +#include +#include + +#include "btpd.h" + +HTBL_TYPE(numtbl, tlib, unsigned, num, nchain); +HTBL_TYPE(hashtbl, tlib, uint8_t, hash, hchain); + +static unsigned m_nextnum; +static unsigned m_ntlibs; +static struct numtbl *m_numtbl; +static struct hashtbl *m_hashtbl; + +unsigned +tlib_count(void) +{ + return m_ntlibs; +} + +struct tlib * +tlib_by_num(unsigned num) +{ + return numtbl_find(m_numtbl, &num); +} + +struct tlib * +tlib_by_hash(const uint8_t *hash) +{ + return hashtbl_find(m_hashtbl, hash); +} + +void +tlib_kill(struct tlib *tl) +{ + numtbl_remove(m_numtbl, &tl->num); + hashtbl_remove(m_hashtbl, tl->hash); + free(tl); + m_ntlibs--; +} + +struct tlib * +tlib_create(const uint8_t *hash) +{ + struct tlib *tl = btpd_calloc(1, sizeof(*tl)); + char hex[SHAHEXSIZE]; + bin2hex(hash, hex, 20); + tl->num = m_nextnum; + bcopy(hash, tl->hash, 20); + m_nextnum++; + m_ntlibs++; + numtbl_insert(m_numtbl, tl); + hashtbl_insert(m_hashtbl, tl); + return tl; +} + +int +tlib_del(struct tlib *tl) +{ + char relpath[RELPATH_SIZE]; + char cmd[PATH_MAX]; + assert(tl->tp == NULL); + snprintf(cmd, PATH_MAX, "rm -r torrents/%s", + bin2hex(tl->hash, relpath, 20)); + system(cmd); + tlib_kill(tl); + return 0; +} + +static int +valid_info(char *buf, size_t len) +{ + size_t slen; + if (benc_validate(buf, len) != 0) + return 0; + if (benc_dget_mem(buf, "name", &slen) == NULL || slen == 0) + return 0; + if ((benc_dget_mem(buf, "dir", &slen) == NULL || + (slen == 0 || slen >= PATH_MAX))) + return 0; + return 1; +} + +static void +load_info(struct tlib *tl, const char *path) +{ + size_t size = 1 << 14; + char buf[size], *p = buf; + + if ((errno = read_whole_file((void **)&p, &size, path)) != 0) { + btpd_log(BTPD_L_ERROR, "couldn't load '%s' (%s).\n", path, + strerror(errno)); + return; + } + + if (!valid_info(buf, size)) { + btpd_log(BTPD_L_ERROR, "bad info file '%s'.\n", path); + return ; + } + + tl->name = benc_dget_str(buf, "name", NULL); + tl->dir = benc_dget_str(buf, "dir", NULL); +#if 0 + tl->t_added = benc_dget_int(buf, "time added"); + tl->t_active = benc_dget_int(buf, "time active"); + tl->tot_up = benc_dget_int(buf, "total upload"); + tl->tot_down = benc_dget_int(buf, "total download"); +#endif + if (tl->name == NULL || tl->dir == NULL) + btpd_err("out of memory.\n"); +} + +static void +save_info(struct tlib *tl, const char *path) +{ + FILE *fp; + char wpath[PATH_MAX]; + snprintf(wpath, PATH_MAX, "%s.write", path); + if ((fp = fopen(wpath, "w")) == NULL) + btpd_err("failed to open '%s' (%s).\n", wpath, strerror(errno)); + fprintf(fp, "d3:dir%d:%s4:name%d:%s", (int)strlen(tl->dir), tl->dir, + (int)strlen(tl->name), tl->name); +#if 0 + fprintf(fp, "11:time activei%lde10:time addedi%lde", tl->t_active, + tl->t_added); + fprintf(fp, "14:total downloadi%llde12:total uploadi%lldee", tl->tot_down, + tl->tot_up); +#else + fprintf(fp, "e"); +#endif + if (ferror(fp) || fclose(fp) != 0) + btpd_err("failed to write '%s'.\n", wpath); + if (rename(wpath, path) != 0) + btpd_err("failed to rename: '%s' -> '%s' (%s).\n", wpath, path, + strerror(errno)); +} + +static void +write_torrent(const char *mi, size_t mi_size, const char *path) +{ + FILE *fp; + if ((fp = fopen(path, "w")) == NULL) + goto err; + if (fwrite(mi, mi_size, 1, fp) != 1) { + errno = EIO; + goto err; + } + if (fclose(fp) != 0) + goto err; + return; +err: + btpd_err("failed to write metainfo '%s' (%s).\n", path, strerror(errno)); +} + +struct tlib * +tlib_add(const uint8_t *hash, const char *mi, size_t mi_size, + const char *content, char *name) +{ + struct tlib *tl = tlib_create(hash); +#if 0 + struct timeval tv; +#endif + char relpath[RELPATH_SIZE], file[PATH_MAX]; + bin2hex(hash, relpath, 20); + + if (name == NULL) + if ((name = mi_name(mi)) == NULL) + btpd_err("out of memory.\n"); + + tl->name = name; + tl->dir = strdup(content); + if (tl->name == NULL || tl->dir == NULL) + btpd_err("out of memory.\n"); + +#if 0 + gettimeofday(&tv, NULL); + tl->t_added = tv.tv_sec; +#endif + + snprintf(file, PATH_MAX, "torrents/%s", relpath); + if (mkdir(file, 0777) != 0) + btpd_err("failed to create dir '%s' (%s).\n", file, strerror(errno)); + snprintf(file, PATH_MAX, "torrents/%s/torrent", relpath); + write_torrent(mi, mi_size, file); + snprintf(file, PATH_MAX, "torrents/%s/info", relpath); + save_info(tl, file); + return tl; +} + +static int +num_test(const void *k1, const void *k2) +{ + return *(const unsigned *)k1 == *(const unsigned *)k2; +} + +static uint32_t +num_hash(const void *k) +{ + return *(const unsigned *)k; +} + +static int +id_test(const void *k1, const void *k2) +{ + return bcmp(k1, k2, 20) == 0; +} + +static uint32_t +id_hash(const void *k) +{ + return net_read32(k + 16); +} + +void +tlib_put_all(struct tlib **v) +{ + hashtbl_tov(m_hashtbl, v); +} + +void +tlib_init(void) +{ + DIR *dirp; + struct dirent *dp; + uint8_t hash[20]; + char file[PATH_MAX]; + + m_numtbl = numtbl_create(num_test, num_hash); + m_hashtbl = hashtbl_create(id_test, id_hash); + if (m_numtbl == NULL || m_hashtbl == NULL) + btpd_err("Out of memory.\n"); + + if ((dirp = opendir("torrents")) == NULL) + btpd_err("couldn't open the torrents directory.\n"); + while ((dp = readdir(dirp)) != NULL) { + if (dp->d_namlen == 40 && ishex(dp->d_name)) { + struct tlib * tl = tlib_create(hex2bin(dp->d_name, hash, 20)); + snprintf(file, PATH_MAX, "torrents/%s/info", dp->d_name); + load_info(tl, file); + } + } + closedir(dirp); +} diff --git a/btpd/tlib.h b/btpd/tlib.h new file mode 100644 index 0000000..2dc604e --- /dev/null +++ b/btpd/tlib.h @@ -0,0 +1,30 @@ +#ifndef BTPD_TLIB_H +#define BTPD_TLIB_H + +struct tlib { + unsigned num; + uint8_t hash[20]; + struct torrent *tp; + + char *name; + char *dir; +#if 0 + unsigned long long tot_up, tot_down; + long t_added, t_active; +#endif + HTBL_ENTRY(nchain); + HTBL_ENTRY(hchain); +}; + +void tlib_init(void); +void tlib_put_all(struct tlib **v); + +struct tlib *tlib_add(const uint8_t *hash, const char *mi, size_t mi_size, + const char *content, char *name); +int tlib_del(struct tlib *tl); + +struct tlib *tlib_by_hash(const uint8_t *hash); +struct tlib *tlib_by_num(unsigned num); +unsigned tlib_count(void); + +#endif diff --git a/btpd/torrent.c b/btpd/torrent.c index db4f7dc..552a5ae 100644 --- a/btpd/torrent.c +++ b/btpd/torrent.c @@ -35,28 +35,33 @@ torrent_count(void) } struct torrent * -torrent_get(const uint8_t *hash) +torrent_by_num(unsigned num) { - struct torrent *tp = BTPDQ_FIRST(&m_torrents); - while (tp != NULL && bcmp(hash, tp->meta.info_hash, 20) != 0) - tp = BTPDQ_NEXT(tp, entry); - return tp; + struct tlib *tl = tlib_by_num(num); + return tl != NULL ? tl->tp : NULL; +} + +struct torrent * +torrent_by_hash(const uint8_t *hash) +{ + struct tlib *tl = tlib_by_hash(hash); + return tl != NULL ? tl->tp : NULL; } const char * torrent_name(struct torrent *tp) { - return tp->meta.name; + return tp->tl->name; } off_t torrent_piece_size(struct torrent *tp, uint32_t index) { - if (index < tp->meta.npieces - 1) - return tp->meta.piece_length; + if (index < tp->npieces - 1) + return tp->piece_length; else { - off_t allbutlast = tp->meta.piece_length * (tp->meta.npieces - 1); - return tp->meta.total_length - allbutlast; + off_t allbutlast = tp->piece_length * (tp->npieces - 1); + return tp->total_length - allbutlast; } } @@ -78,85 +83,84 @@ torrent_block_size(struct torrent *tp, uint32_t piece, uint32_t nblocks, } } -static void -torrent_relpath(const uint8_t *hash, char *buf) -{ - for (int i = 0; i < 20; i++) - snprintf(buf + i * 2, 3, "%.2x", hash[i]); -} - -int -torrent_set_links(const uint8_t *hash, const char *torrent, - const char *content) -{ - char relpath[RELPATH_SIZE]; - char file[PATH_MAX]; - torrent_relpath(hash, relpath); - snprintf(file, PATH_MAX, "torrents/%s", relpath); - if (mkdir(file, 0777) == -1 && errno != EEXIST) - return errno; - snprintf(file, PATH_MAX, "torrents/%s/torrent", relpath); - if (unlink(file) == -1 && errno != ENOENT) - return errno; - if (symlink(torrent, file) == -1) - return errno; - snprintf(file, PATH_MAX, "torrents/%s/content", relpath); - if (unlink(file) == -1 && errno != ENOENT) - return errno; - if (symlink(content, file) == -1) - return errno; - return 0; -} - -int -torrent_start(const uint8_t *hash) +enum ipc_err +torrent_start(struct tlib *tl) { + struct stat sb; struct torrent *tp; - struct metainfo *mi; - int error; + char *mi; char relpath[RELPATH_SIZE]; char file[PATH_MAX]; - torrent_relpath(hash, relpath); - snprintf(file, PATH_MAX, "torrents/%s/torrent", relpath); + if (tl->dir == NULL) + return IPC_EBADTENT; + + if (mkdir(tl->dir, 0777) != 0 && errno != EEXIST) { + btpd_log(BTPD_L_ERROR, "torrent '%s': " + "failed to create content dir '%s' (%s).\n", + tl->name, tl->dir, strerror(errno)); + return IPC_ECREATECDIR; + } else if (stat(tl->dir, &sb) == -1 || + ((sb.st_mode & S_IFMT) != S_IFDIR)) { + btpd_log(BTPD_L_ERROR, + "torrent '%s': content dir '%s' is either not a directory or" + " cannot be accessed.\n", tl->name, tl->dir); + return IPC_EBADCDIR; + } - if ((error = load_metainfo(file, -1, 0, &mi)) != 0) { - btpd_log(BTPD_L_ERROR, "Couldn't load torrent file %s: %s.\n", - file, strerror(error)); - return error; + bin2hex(tl->hash, relpath, 20); + snprintf(file, PATH_MAX, "torrents/%s/torrent", relpath); + if ((mi = mi_load(file, NULL)) == NULL) { + btpd_log(BTPD_L_ERROR, + "torrent '%s': failed to load metainfo (%s).\n", + tl->name, strerror(errno)); + return IPC_EBADTENT; } tp = btpd_calloc(1, sizeof(*tp)); + tp->tl = tl; bcopy(relpath, tp->relpath, RELPATH_SIZE); - tp->meta = *mi; - free(mi); + tp->files = mi_files(mi); + tp->nfiles = mi_nfiles(mi); + if (tp->files == NULL) + btpd_err("out of memory.\n"); + tp->total_length = mi_total_length(mi); + tp->piece_length = mi_piece_length(mi); + tp->npieces = mi_npieces(mi); + tp->pieces_off = + benc_dget_mem(benc_dget_dct(mi, "info"), "pieces", NULL) - mi; btpd_log(BTPD_L_BTPD, "Starting torrent '%s'.\n", torrent_name(tp)); - if ((error = tr_create(tp)) == 0) { + if (tr_create(tp, mi) == 0) { net_create(tp); cm_create(tp); BTPDQ_INSERT_TAIL(&m_torrents, tp, entry); m_ntorrents++; cm_start(tp); + tl->tp = tp; + free(mi); + return IPC_OK; } else { - clear_metainfo(&tp->meta); + mi_free_files(tp->nfiles, tp->files); free(tp); + free(mi); + return IPC_EBADTRACKER; } - return error; } static void torrent_kill(struct torrent *tp) { - btpd_log(BTPD_L_BTPD, "Removed torrent '%s'.\n", torrent_name(tp)); + btpd_log(BTPD_L_BTPD, "Stopped torrent '%s'.\n", torrent_name(tp)); assert(m_ntorrents > 0); assert(!(tr_active(tp) || net_active(tp) || cm_active(tp))); m_ntorrents--; BTPDQ_REMOVE(&m_torrents, tp, entry); - clear_metainfo(&tp->meta); tr_kill(tp); net_kill(tp); cm_kill(tp); + tp->tl->tp = NULL; + mi_free_files(tp->nfiles, tp->files); free(tp); if (m_ntorrents == 0) btpd_on_no_torrents(); diff --git a/btpd/torrent.h b/btpd/torrent.h index 2b1cf19..9181109 100644 --- a/btpd/torrent.h +++ b/btpd/torrent.h @@ -2,7 +2,7 @@ #define BTPD_TORRENT_H #define PIECE_BLOCKLEN (1 << 14) -#define RELPATH_SIZE 41 +#define RELPATH_SIZE SHAHEXSIZE enum torrent_state { T_STARTING, @@ -11,15 +11,22 @@ enum torrent_state { }; struct torrent { - char relpath[RELPATH_SIZE]; - struct metainfo meta; + struct tlib *tl; + char relpath[RELPATH_SIZE]; enum torrent_state state; struct content *cm; struct tracker *tr; struct net *net; + off_t total_length; + off_t piece_length; + uint32_t npieces; + unsigned nfiles; + struct mi_file *files; + size_t pieces_off; + BTPDQ_ENTRY(torrent) entry; }; @@ -27,12 +34,11 @@ BTPDQ_HEAD(torrent_tq, torrent); unsigned torrent_count(void); const struct torrent_tq *torrent_get_all(void); -struct torrent *torrent_get(const uint8_t *hash); +struct torrent *torrent_by_num(unsigned num); +struct torrent *torrent_by_hash(const uint8_t *hash); -int torrent_start(const uint8_t *hash); +enum ipc_err torrent_start(struct tlib *tl); void torrent_stop(struct torrent *tp); -int torrent_set_links(const uint8_t *hash, const char *torrent, - const char *content); off_t torrent_piece_size(struct torrent *tp, uint32_t piece); uint32_t torrent_piece_blocks(struct torrent *tp, uint32_t piece); diff --git a/btpd/tracker_req.c b/btpd/tracker_req.c index e172a84..c21bd5b 100644 --- a/btpd/tracker_req.c +++ b/btpd/tracker_req.c @@ -27,6 +27,7 @@ enum timer_type { }; struct tracker { + struct mi_announce *ann; enum timer_type ttype; enum tr_event event; int interval; @@ -189,7 +190,7 @@ tr_send(struct torrent *tp, enum tr_event event) if (tr->ttype == TIMER_TIMEOUT) http_cancel(tr->req); - if ((busy_secs = http_server_busy_time(tp->meta.announce, 3)) > 0) { + if ((busy_secs = http_server_busy_time(tr->ann->tiers[0].urls[0], 3)) > 0) { tr->ttype = TIMER_RETRY; btpd_ev_add(&tr->timer, (& (struct timeval) { busy_secs, 0 })); return; @@ -198,32 +199,37 @@ tr_send(struct torrent *tp, enum tr_event event) tr->ttype = TIMER_TIMEOUT; btpd_ev_add(&tr->timer, REQ_TIMEOUT); - qc = (strchr(tp->meta.announce, '?') == NULL) ? '?' : '&'; + qc = (strchr(tr->ann->tiers[0].urls[0], '?') == NULL) ? '?' : '&'; for (int i = 0; i < 20; i++) - snprintf(e_hash + i * 3, 4, "%%%.2x", tp->meta.info_hash[i]); + snprintf(e_hash + i * 3, 4, "%%%.2x", tp->tl->hash[i]); for (int i = 0; i < 20; i++) snprintf(e_id + i * 3, 4, "%%%.2x", peer_id[i]); http_get(&tr->req, http_cb, tp, "%s%cinfo_hash=%s&peer_id=%s&port=%d&uploaded=%llu" "&downloaded=%llu&left=%llu&compact=1%s%s", - tp->meta.announce, qc, e_hash, e_id, net_port, + tr->ann->tiers[0].urls[0], qc, e_hash, e_id, net_port, tp->net->uploaded, tp->net->downloaded, - (long long)tp->meta.total_length - cm_content(tp), + (long long)tp->total_length - cm_content(tp), event == TR_EV_EMPTY ? "" : "&event=", m_events[event]); } int -tr_create(struct torrent *tp) +tr_create(struct torrent *tp, const char *mi) { - if (strncmp(tp->meta.announce, "http://", sizeof("http://") - 1) != 0) { + struct mi_announce *ann = mi_announce(mi); + if (ann == NULL) + btpd_err("out of memory.\n"); + if (strncmp(ann->tiers[0].urls[0], + "http://", sizeof("http://") - 1) != 0) { btpd_log(BTPD_L_ERROR, "btpd currently has no support for the protocol specified in " - "'%s'.\n", tp->meta.announce); + "'%s'.\n", ann->tiers[0].urls[0]); return EINVAL; } tp->tr = btpd_calloc(1, sizeof(*tp->tr)); + tp->tr->ann = ann; evtimer_set(&tp->tr->timer, timer_cb, tp); return 0; } @@ -236,6 +242,7 @@ tr_kill(struct torrent *tp) btpd_ev_del(&tr->timer); if (tr->req != NULL) http_cancel(tr->req); + mi_free_announce(tr->ann); free(tr); } diff --git a/btpd/tracker_req.h b/btpd/tracker_req.h index 1e85163..23cbeaa 100644 --- a/btpd/tracker_req.h +++ b/btpd/tracker_req.h @@ -1,7 +1,7 @@ #ifndef TRACKER_REQ_H #define TRACKER_REQ_H -int tr_create(struct torrent *tp); +int tr_create(struct torrent *tp, const char *mi); void tr_kill(struct torrent *tp); void tr_start(struct torrent *tp); void tr_stop(struct torrent *tp); diff --git a/cli/Makefile.am b/cli/Makefile.am index 84e18f9..3222e95 100644 --- a/cli/Makefile.am +++ b/cli/Makefile.am @@ -5,7 +5,7 @@ btinfo_LDADD=../misc/libmisc.a -lcrypto -lm btinfo_CPPFLAGS=-I$(top_srcdir)/misc @openssl_CPPFLAGS@ btinfo_LDFLAGS=@openssl_LDFLAGS@ -btcli_SOURCES=btcli.c btpd_if.c btpd_if.h +btcli_SOURCES=btcli.c btcli.h add.c del.c list.c kill.c start.c stop.c stat.c btcli_LDADD=../misc/libmisc.a -lcrypto -lm btcli_CPPFLAGS=-I$(top_srcdir)/misc @openssl_CPPFLAGS@ btcli_LDFLAGS=@openssl_LDFLAGS@ diff --git a/cli/add.c b/cli/add.c new file mode 100644 index 0000000..34edb1c --- /dev/null +++ b/cli/add.c @@ -0,0 +1,87 @@ +#include "btcli.h" + +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); +} + +static struct option add_opts [] = { + { "help", no_argument, NULL, 'H' }, + { "topdir", no_argument, NULL, 'T'}, + {NULL, 0, NULL, 0} +}; + +void +cmd_add(int argc, char **argv) +{ + int ch, topdir = 0; + size_t dirlen = 0; + char *dir = NULL, *name = NULL; + + while ((ch = getopt_long(argc, argv, "d:n:", add_opts, NULL)) != -1) { + switch (ch) { + case 'T': + topdir = 1; + break; + case 'd': + dir = optarg; + if ((dirlen = strlen(dir)) == 0) + errx(1, "bad option value for -d"); + break; + case 'n': + name = optarg; + break; + default: + usage_add(); + } + } + argc -= optind; + argv += optind; + + if (argc != 1 || dir == NULL) + usage_add(); + + btpd_connect(); + char *mi; + size_t mi_size; + char dpath[PATH_MAX]; + struct io_buffer iob; + + if ((mi = mi_load(argv[0], &mi_size)) == NULL) + err(1, "error loading '%s'", argv[0]); + + buf_init(&iob, PATH_MAX); + buf_write(&iob, dir, dirlen); + if (topdir) { + size_t tdlen; + const char *td = + benc_dget_mem(benc_dget_dct(mi, "info"), "name", &tdlen); + buf_swrite(&iob, "/"); + buf_write(&iob, td, tdlen); + } + buf_swrite(&iob, ""); + if (realpath(iob.buf, dpath) == NULL) + err(1, "realpath '%s'", dpath); + handle_ipc_res(btpd_add(ipc, mi, mi_size, dpath, name), argv[0]); + return; +} 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 -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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); } diff --git a/cli/btcli.h b/cli/btcli.h new file mode 100644 index 0000000..5b64662 --- /dev/null +++ b/cli/btcli.h @@ -0,0 +1,44 @@ +#ifndef BTCLI_H +#define BTCLI_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "btpd_if.h" +#include "metainfo.h" +#include "subr.h" +#include "benc.h" +#include "iobuf.h" +#include "queue.h" + +extern const char *btpd_dir; +extern struct ipc *ipc; + +void btpd_connect(void); +enum ipc_err handle_ipc_res(enum ipc_err err, const char *target); + +char tstate_char(enum ipc_tstate ts); +int torrent_spec(char *arg, struct ipc_torrent *tp); + +void usage_add(void); +void cmd_add(int argc, char **argv); +void usage_del(void); +void cmd_del(int argc, char **argv); +void usage_list(void); +void cmd_list(int argc, char **argv); +void usage_stat(void); +void cmd_stat(int argc, char **argv); +void usage_kill(void); +void cmd_kill(int argc, char **argv); +void usage_start(void); +void cmd_start(int argc, char **argv); +void usage_stop(void); +void cmd_stop(int argc, char **argv); + +#endif diff --git a/cli/btinfo.c b/cli/btinfo.c index f451c85..cfd1991 100644 --- a/cli/btinfo.c +++ b/cli/btinfo.c @@ -8,6 +8,7 @@ #include #include "metainfo.h" +#include "subr.h" static void usage() @@ -21,11 +22,38 @@ static struct option longopts[] = { { NULL, 0, NULL, 0 } }; +static void +print_metainfo(const char *mi) +{ + uint8_t hash[20]; + char hex[SHAHEXSIZE]; + char *name = mi_name(mi); + unsigned nfiles = mi_nfiles(mi); + struct mi_file *files = mi_files(mi); + struct mi_announce *ann = mi_announce(mi); + for (int i = 0; i < ann->ntiers; i++) + for (int j = 0; j < ann->tiers[i].nurls; j++) + printf("%d: %s\n", i, ann->tiers[i].urls[j]); + printf("\n"); + mi_free_announce(ann); + mi_info_hash(mi, hash); + bin2hex(hash, hex, 20); + printf("name: %s\n", name); + printf("info hash: %s\n", hex); + printf("length: %jd\n", (intmax_t)mi_total_length(mi)); + printf("piece length: %jd\n", (intmax_t)mi_piece_length(mi)); + printf("files: %u\n", nfiles); + for (unsigned i = 0; i < nfiles; i++) + printf("%s(%jd)\n", files[i].path, (intmax_t)files[i].length); + free(name); +} + int main(int argc, char **argv) { int ch; + srandom(time(NULL)); while ((ch = getopt_long(argc, argv, "", longopts, NULL)) != -1) usage(); @@ -36,13 +64,12 @@ main(int argc, char **argv) usage(); while (argc > 0) { - struct metainfo *mi; + char *mi = NULL; - if ((errno = load_metainfo(*argv, -1, 1, &mi)) != 0) - err(1, "load_metainfo: %s", *argv); + if ((mi = mi_load(*argv, NULL)) == NULL) + err(1, "mi_load: %s", *argv); print_metainfo(mi); - clear_metainfo(mi); free(mi); argc--; diff --git a/cli/del.c b/cli/del.c new file mode 100644 index 0000000..eaa0a1e --- /dev/null +++ b/cli/del.c @@ -0,0 +1,30 @@ +#include "btcli.h" + +void +usage_del(void) +{ + printf( + "Remove torrents from btpd.\n" + "\n" + "Usage: del torrent ...\n" + "\n" + "Arguments:\n" + "file ...\n" + "\tThe torrents to remove.\n" + "\n"); + exit(1); +} + +void +cmd_del(int argc, char **argv) +{ + struct ipc_torrent t; + + if (argc < 2) + usage_del(); + + btpd_connect(); + for (int i = 1; i < argc; i++) + if (torrent_spec(argv[i], &t)) + handle_ipc_res(btpd_del(ipc, &t), argv[i]); +} diff --git a/cli/kill.c b/cli/kill.c new file mode 100644 index 0000000..b2c6862 --- /dev/null +++ b/cli/kill.c @@ -0,0 +1,35 @@ +#include "btcli.h" + +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"); +} diff --git a/cli/list.c b/cli/list.c new file mode 100644 index 0000000..8bdf851 --- /dev/null +++ b/cli/list.c @@ -0,0 +1,124 @@ +#include "btcli.h" + +void +usage_list(void) +{ + printf( + "List torrents.\n" + "\n" + "Usage: list [-a] [-i]\n" + "\n" + ); + exit(1); +} + +struct item { + unsigned num; + char *name; + char st; + BTPDQ_ENTRY(item) entry; +}; + +struct items { + int count; + BTPDQ_HEAD(item_tq, item) hd; +}; + +void +itm_insert(struct items *itms, struct item *itm) +{ + struct item *p; + BTPDQ_FOREACH(p, &itms->hd, entry) + if (itm->num < p->num) +#if 0 + if (strcmp(itm->name, p->name) < 0) +#endif + break; + if (p != NULL) + BTPDQ_INSERT_BEFORE(p, itm, entry); + else + BTPDQ_INSERT_TAIL(&itms->hd, itm, entry); +} + +static void +list_cb(int obji, enum ipc_err objerr, struct ipc_get_res *res, void *arg) +{ + struct items *itms = arg; + struct item *itm = calloc(1, sizeof(*itm)); + itms->count++; + itm->num = (unsigned)res[IPC_TVAL_NUM].v.num; + itm->st = tstate_char(res[IPC_TVAL_STATE].v.num); + if (res[IPC_TVAL_NAME].type == IPC_TYPE_ERR) + asprintf(&itm->name, "%s", ipc_strerror(res[IPC_TVAL_NAME].v.num)); + else + asprintf(&itm->name, "%.*s", (int)res[IPC_TVAL_NAME].v.str.l, + res[IPC_TVAL_NAME].v.str.p); + itm_insert(itms, itm); +#if 0 + int *count = arg; + (*count)++; + printf("%4u %c.", (unsigned)res[IPC_TVAL_NUM].v.num, + tstate_char(res[IPC_TVAL_STATE].v.num)); + if (res[IPC_TVAL_NAME].type == IPC_TYPE_ERR) + printf(" %s\n", ipc_strerror(res[IPC_TVAL_NAME].v.num)); + else + printf(" %.*s\n", (int)res[IPC_TVAL_NAME].v.str.l, + res[IPC_TVAL_NAME].v.str.p); +#endif +} + +void +print_items(struct items* itms) +{ + int n; + struct item *p; + BTPDQ_FOREACH(p, &itms->hd, entry) { + n = printf("%u: ", p->num); + while (n < 7) { + putchar(' '); + n++; + } + printf("%c. %s\n", p->st, p->name); + } +} + +static struct option list_opts [] = { + { "help", no_argument, NULL, 'H' }, + {NULL, 0, NULL, 0} +}; + +void +cmd_list(int argc, char **argv) +{ + int ch, /*count = 0,*/ inactive = 0, active = 0; + enum ipc_twc twc; + enum ipc_tval keys[] = { IPC_TVAL_NUM, IPC_TVAL_STATE, IPC_TVAL_NAME }; + struct items itms; + while ((ch = getopt_long(argc, argv, "ai", list_opts, NULL)) != -1) { + switch (ch) { + case 'a': + active = 1; + break; + case 'i': + inactive = 1; + break; + default: + usage_list(); + } + } + + if (inactive == active) + twc = IPC_TWC_ALL; + else if (inactive) + twc = IPC_TWC_INACTIVE; + else + twc = IPC_TWC_ACTIVE; + + btpd_connect(); + printf("NUM ST NAME\n"); + itms.count = 0; + BTPDQ_INIT(&itms.hd); + handle_ipc_res(btpd_tget_wc(ipc, twc, keys, 3, list_cb, &itms), "tget"); + print_items(&itms); + printf("Listed %d torrent%s.\n", itms.count, itms.count == 1 ? "" : "s"); +} diff --git a/cli/start.c b/cli/start.c new file mode 100644 index 0000000..4772bb6 --- /dev/null +++ b/cli/start.c @@ -0,0 +1,27 @@ +#include "btcli.h" + +void +usage_start(void) +{ + printf( + "Start torrents.\n" + "\n" + "Usage: start torrent\n" + "\n" + ); + exit(1); +} + +void +cmd_start(int argc, char **argv) +{ + struct ipc_torrent t; + + if (argc < 2) + usage_start(); + + btpd_connect(); + for (int i = 1; i < argc; i++) + if (torrent_spec(argv[i], &t)) + handle_ipc_res(btpd_start(ipc, &t), argv[i]); +} diff --git a/cli/stat.c b/cli/stat.c new file mode 100644 index 0000000..2af1210 --- /dev/null +++ b/cli/stat.c @@ -0,0 +1,193 @@ +#include + +#include "btcli.h" + +void +usage_stat(void) +{ + printf( + "Display stats for active torrents.\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" + "-n\n" + "\tDisplay the name of each torrent. Implies '-i'.\n" + "\n" + "-w n\n" + "\tDisplay stats every n seconds.\n" + "\n"); + exit(1); +} + +struct btstat { + unsigned num; + enum ipc_tstate state; + unsigned peers, tr_errors; + long long content_got, content_size, downloaded, uploaded, rate_up, + rate_down; + uint32_t pieces_seen, torrent_pieces; +}; + +struct cbarg { + int individual, names; + struct btstat tot; +}; + +static enum ipc_tval stkeys[] = { + IPC_TVAL_STATE, + IPC_TVAL_NUM, + IPC_TVAL_NAME, + IPC_TVAL_PCOUNT, + IPC_TVAL_TRERR, + IPC_TVAL_PCCOUNT, + IPC_TVAL_PCSEEN, + IPC_TVAL_SESSUP, + IPC_TVAL_SESSDWN, + IPC_TVAL_RATEUP, + IPC_TVAL_RATEDWN, + IPC_TVAL_CGOT, + IPC_TVAL_CSIZE +}; + +static size_t nstkeys = sizeof(stkeys) / sizeof(stkeys[0]); + +static void +print_stat(struct btstat *st) +{ + printf("%5.1f%% %6.1fM %7.2fkB/s %6.1fM %7.2fkB/s %5u %5.1f%%", + floor(1000.0 * st->content_got / st->content_size) / 10, + (double)st->downloaded / (1 << 20), + (double)st->rate_down / (20 << 10), + (double)st->uploaded / (1 << 20), + (double)st->rate_up / (20 << 10), + st->peers, + floor(1000.0 * st->pieces_seen / st->torrent_pieces) / 10); + if (st->tr_errors > 0) + printf(" E%u", st->tr_errors); + printf("\n"); +} + +void +stat_cb(int obji, enum ipc_err objerr, struct ipc_get_res *res, void *arg) +{ + struct cbarg *cba = arg; + struct btstat st, *tot = &cba->tot; + if (objerr != IPC_OK || res[IPC_TVAL_STATE].v.num == IPC_TSTATE_INACTIVE) + return; + bzero(&st, sizeof(st)); + st.state = res[IPC_TVAL_STATE].v.num; + st.num = res[IPC_TVAL_NUM].v.num; + tot->torrent_pieces += (st.torrent_pieces = res[IPC_TVAL_PCCOUNT].v.num); + tot->pieces_seen += (st.pieces_seen = res[IPC_TVAL_PCSEEN].v.num); + tot->content_got += (st.content_got = res[IPC_TVAL_CGOT].v.num); + tot->content_size += (st.content_size = res[IPC_TVAL_CSIZE].v.num); + tot->downloaded += (st.downloaded = res[IPC_TVAL_SESSDWN].v.num); + tot->uploaded += (st.uploaded = res[IPC_TVAL_SESSUP].v.num); + tot->rate_down += (st.rate_down = res[IPC_TVAL_RATEDWN].v.num); + tot->rate_up += (st.rate_up = res[IPC_TVAL_RATEUP].v.num); + tot->peers += (st.peers = res[IPC_TVAL_PCOUNT].v.num); + if ((st.tr_errors = res[IPC_TVAL_TRERR].v.num) > 0) + tot->tr_errors++; + if (cba->individual) { + if (cba->names) + printf("%.*s\n", (int)res[IPC_TVAL_NAME].v.str.l, + res[IPC_TVAL_NAME].v.str.p); + int n = printf("%u:", st.num); + while (n < 7) { + putchar(' '); + n++; + } + printf("%c. ", tstate_char(st.state)); + print_stat(&st); + } +} + +static void +do_stat(int individual, int names, int seconds, struct ipc_torrent *tps, + int ntps) +{ + enum ipc_err err; + struct cbarg cba; + if (names) + individual = 1; + if (individual) + printf("NUM ST "); + printf(" HAVE DLOAD RTDWN ULOAD RTUP PEERS AVAIL\n"); + cba.individual = individual; + cba.names = names; +again: + bzero(&cba.tot, sizeof(cba.tot)); + cba.tot.state = IPC_TSTATE_INACTIVE; + if (tps == NULL) + err = btpd_tget_wc(ipc, IPC_TWC_ACTIVE, stkeys, nstkeys, + stat_cb, &cba); + else + err = btpd_tget(ipc, tps, ntps, stkeys, nstkeys, stat_cb, &cba); + if (handle_ipc_res(err, "stat") != IPC_OK) + exit(1); + if (names) + printf("-----\n"); + if (individual) + printf("Total: "); + print_stat(&cba.tot); + if (seconds > 0) { + sleep(seconds); + goto again; + } +} + +static struct option stat_opts [] = { + { "help", no_argument, NULL, 'H' }, + {NULL, 0, NULL, 0} +}; + +void +cmd_stat(int argc, char **argv) +{ + int ch; + int wflag = 0, iflag = 0, nflag = 0, seconds = 0; + struct ipc_torrent *tps = NULL; + int ntps = 0; + char *endptr; + while ((ch = getopt_long(argc, argv, "inw:", stat_opts, NULL)) != -1) { + switch (ch) { + case 'i': + iflag = 1; + break; + case 'n': + nflag = 1; + break; + case 'w': + wflag = 1; + seconds = strtol(optarg, &endptr, 10); + if (*endptr != '\0' || seconds < 1) + usage_stat(); + break; + default: + usage_stat(); + } + } + argc -= optind; + argv += optind; + + if (argc > 0) { + tps = malloc(argc * sizeof(*tps)); + for (int i = 0; i < argc; i++) { + if (torrent_spec(argv[i], &tps[ntps])) + ntps++; + else + exit(1); + + } + } + btpd_connect(); + do_stat(iflag, nflag, seconds, tps, ntps); +} diff --git a/cli/stop.c b/cli/stop.c new file mode 100644 index 0000000..caf68f4 --- /dev/null +++ b/cli/stop.c @@ -0,0 +1,27 @@ +#include "btcli.h" + +void +usage_stop(void) +{ + printf( + "Stop torrents.\n" + "\n" + "Usage: stop torrent ...\n" + "\n" + ); + exit(1); +} + +void +cmd_stop(int argc, char **argv) +{ + struct ipc_torrent t; + + if (argc < 2) + usage_stop(); + + btpd_connect(); + for (int i = 1; i < argc; i++) + if (torrent_spec(argv[i], &t)) + handle_ipc_res(btpd_stop(ipc, &t), argv[i]); +}