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]); +}