Browse Source

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.
master
Richard Nyberg 18 years ago
parent
commit
86754d7d53
28 changed files with 1388 additions and 637 deletions
  1. +1
    -2
      btpd/Makefile.am
  2. +3
    -2
      btpd/active.c
  3. +1
    -0
      btpd/btpd.c
  4. +11
    -1
      btpd/btpd.h
  5. +292
    -94
      btpd/cli_if.c
  6. +59
    -66
      btpd/content.c
  7. +1
    -1
      btpd/download.c
  8. +5
    -5
      btpd/download_subr.c
  9. +8
    -11
      btpd/net.c
  10. +3
    -3
      btpd/net_buf.c
  11. +5
    -5
      btpd/peer.c
  12. +246
    -0
      btpd/tlib.c
  13. +30
    -0
      btpd/tlib.h
  14. +61
    -57
      btpd/torrent.c
  15. +13
    -7
      btpd/torrent.h
  16. +15
    -8
      btpd/tracker_req.c
  17. +1
    -1
      btpd/tracker_req.h
  18. +1
    -1
      cli/Makefile.am
  19. +87
    -0
      cli/add.c
  20. +34
    -369
      cli/btcli.c
  21. +44
    -0
      cli/btcli.h
  22. +31
    -4
      cli/btinfo.c
  23. +30
    -0
      cli/del.c
  24. +35
    -0
      cli/kill.c
  25. +124
    -0
      cli/list.c
  26. +27
    -0
      cli/start.c
  27. +193
    -0
      cli/stat.c
  28. +27
    -0
      cli/stop.c

+ 1
- 2
btpd/Makefile.am View File

@@ -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


+ 3
- 2
btpd/active.c View File

@@ -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);
}


+ 1
- 0
btpd/btpd.c View File

@@ -201,6 +201,7 @@ btpd_init(void)
ipc_init();
ul_init();
cm_init();
tlib_init();

signal(SIGPIPE, SIG_IGN);



+ 11
- 1
btpd/btpd.h View File

@@ -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);


+ 292
- 94
btpd/cli_if.c View File

@@ -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]);


+ 59
- 66
btpd/content.c View File

@@ -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) {


+ 1
- 1
btpd/download.c View File

@@ -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]--;



+ 5
- 5
btpd/download_subr.c View File

@@ -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++;


+ 8
- 11
btpd/net.c View File

@@ -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;


+ 3
- 3
btpd/net_buf.c View File

@@ -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;
}


+ 5
- 5
btpd/peer.c View File

@@ -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;
}

+ 246
- 0
btpd/tlib.c View File

@@ -0,0 +1,246 @@
#include <sys/types.h>
#include <sys/stat.h>

#include <dirent.h>
#include <string.h>
#include <unistd.h>

#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);
}

+ 30
- 0
btpd/tlib.h View File

@@ -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

+ 61
- 57
btpd/torrent.c View File

@@ -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();


+ 13
- 7
btpd/torrent.h View File

@@ -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);


+ 15
- 8
btpd/tracker_req.c View File

@@ -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);
}



+ 1
- 1
btpd/tracker_req.h View File

@@ -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);


+ 1
- 1
cli/Makefile.am View File

@@ -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@

+ 87
- 0
cli/add.c View File

@@ -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;
}

+ 34
- 369
cli/btcli.c View File

@@ -1,21 +1,4 @@
#include <sys/types.h>
#include <sys/stat.h>

#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <inttypes.h>
#include <limits.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "btpd_if.h"
#include "metainfo.h"
#include "subr.h"
#include "btcli.h"

const char *btpd_dir;
struct ipc *ipc;
@@ -27,377 +10,55 @@ btpd_connect(void)
err(1, "cannot open connection to btpd in %s", btpd_dir);
}

enum ipc_code
handle_ipc_res(enum ipc_code code, const char *target)
enum ipc_err
handle_ipc_res(enum ipc_err code, const char *target)
{
switch (code) {
case IPC_OK:
break;
case IPC_FAIL:
warnx("btpd couldn't execute the requested operation for %s", target);
break;
case IPC_ERROR:
warnx("btpd encountered an error for %s", target);
break;
default:
case IPC_COMMERR:
errx(1, "fatal error in communication with btpd");
default:
warnx("btpd response for '%s': %s", target, ipc_strerror(code));
}
return code;
}

char
state_char(struct tpstat *ts)
tstate_char(enum ipc_tstate ts)
{
switch (ts->state) {
case T_STARTING:
switch (ts) {
case IPC_TSTATE_INACTIVE:
return 'I';
case IPC_TSTATE_START:
return '+';
case T_ACTIVE:
return ts->pieces_got == ts->torrent_pieces ? 'S' : 'L';
case T_STOPPING:
case IPC_TSTATE_STOP:
return '-';
default:
return ' ';
case IPC_TSTATE_LEECH:
return 'L';
case IPC_TSTATE_SEED:
return 'S';
}
errx(1, "bad state");
}

void
print_stat(struct tpstat *ts)
{
printf("%c %5.1f%% %6.1fM %7.2fkB/s %6.1fM %7.2fkB/s %4u %5.1f%%",
state_char(ts),
floor(1000.0 * ts->content_got / ts->content_size) / 10,
(double)ts->downloaded / (1 << 20),
(double)ts->rate_down / (20 << 10),
(double)ts->uploaded / (1 << 20),
(double)ts->rate_up / (20 << 10),
ts->peers,
floor(1000.0 * ts->pieces_seen / ts->torrent_pieces) / 10);
if (ts->tr_errors > 0)
printf(" E%u", ts->tr_errors);
printf("\n");
}

void
usage_add(void)
{
printf(
"Add torrents to btpd.\n"
"\n"
"Usage: add [--topdir] -d dir file\n"
" add file ...\n"
"\n"
"Arguments:\n"
"file ...\n"
"\tOne or more torrents to add.\n"
"\n"
"Options:\n"
"-d dir\n"
"\tUse the dir for content.\n"
"\n"
"--topdir\n"
"\tAppend the torrent top directory (if any) to the content path.\n"
"\tThis option cannot be used without the '-d' option.\n"
"\n"
);
exit(1);
}

struct option add_opts [] = {
{ "help", no_argument, NULL, 'H' },
{ "topdir", no_argument, NULL, 'T'},
{NULL, 0, NULL, 0}
};

int
content_link(uint8_t *hash, char *buf)
{
int n;
char relpath[41];
char path[PATH_MAX];
for (int i = 0; i < 20; i++)
snprintf(relpath + i * 2, 3, "%.2x", hash[i]);
snprintf(path, PATH_MAX, "%s/torrents/%s/content", btpd_dir, relpath);
if ((n = readlink(path, buf, PATH_MAX)) == -1)
return errno;
buf[min(n, PATH_MAX)] = '\0';
return 0;
}

void
cmd_add(int argc, char **argv)
{
int ch, topdir = 0;
char *dir = NULL;

while ((ch = getopt_long(argc, argv, "d:", add_opts, NULL)) != -1) {
switch (ch) {
case 'T':
topdir = 1;
break;
case 'd':
dir = optarg;
break;
default:
usage_add();
}
}
argc -= optind;
argv += optind;

if (argc < 1 || (topdir == 1 && dir == NULL) || (dir != NULL && argc > 1))
usage_add();

btpd_connect();
for (int i = 0; i < argc; i++) {
struct metainfo *mi;
char rdpath[PATH_MAX], dpath[PATH_MAX], fpath[PATH_MAX];

if ((errno = load_metainfo(argv[i], -1, 0, &mi)) != 0) {
warn("error loading torrent %s", argv[i]);
continue;
}

if ((topdir &&
!(mi->nfiles == 1
&& strcmp(mi->name, mi->files[0].path) == 0)))
snprintf(dpath, PATH_MAX, "%s/%s", dir, mi->name);
else if (dir != NULL)
strncpy(dpath, dir, PATH_MAX);
else {
if (content_link(mi->info_hash, dpath) != 0) {
warnx("unknown content dir for %s", argv[i]);
errx(1, "use the '-d' option");
}
}

if (mkdir(dpath, 0777) != 0 && errno != EEXIST)
err(1, "couldn't create directory %s", dpath);

if (realpath(dpath, rdpath) == NULL)
err(1, "path error on %s", dpath);

if (realpath(argv[i], fpath) == NULL)
err(1, "path error on %s", fpath);

handle_ipc_res(btpd_add(ipc, mi->info_hash, fpath, rdpath), argv[i]);
clear_metainfo(mi);
free(mi);
}
}

void
usage_del(void)
{
printf(
"Remove torrents from btpd.\n"
"\n"
"Usage: del file ...\n"
"\n"
"Arguments:\n"
"file ...\n"
"\tThe torrents to remove.\n"
"\n");
exit(1);
}

void
cmd_del(int argc, char **argv)
{
if (argc < 2)
usage_del();

btpd_connect();
for (int i = 1; i < argc; i++) {
struct metainfo *mi;
if ((errno = load_metainfo(argv[i], -1, 0, &mi)) != 0) {
warn("error loading torrent %s", argv[i]);
continue;
}
handle_ipc_res(btpd_del(ipc, mi->info_hash), argv[i]);
clear_metainfo(mi);
free(mi);
}
}

void
usage_kill(void)
{
printf(
"Shutdown btpd.\n"
"\n"
"Usage: kill [seconds]\n"
"\n"
"Arguments:\n"
"seconds\n"
"\tThe number of seconds btpd waits before giving up on unresponsive\n"
"\ttrackers.\n"
"\n"
);
exit(1);
}

void
cmd_kill(int argc, char **argv)
{
int seconds = -1;
char *endptr;

if (argc == 2) {
seconds = strtol(argv[1], &endptr, 10);
if (strlen(argv[1]) > endptr - argv[1] || seconds < 0)
usage_kill();
} else if (argc > 2)
usage_kill();

btpd_connect();
handle_ipc_res(btpd_die(ipc, seconds), "kill");
}

void
usage_list(void)
{
printf(
"List active torrents.\n"
"\n"
"Usage: list\n"
"\n"
);
exit(1);
}

void
cmd_list(int argc, char **argv)
{
struct btstat *st;

if (argc > 1)
usage_list();

btpd_connect();
if (handle_ipc_res(btpd_stat(ipc, &st), "list") != IPC_OK)
exit(1);
for (int i = 0; i < st->ntorrents; i++) {
struct tpstat *ts = &st->torrents[i];
printf("%c. %s\n", state_char(ts), ts->name);
}
printf("%u torrent%s.\n", st->ntorrents,
st->ntorrents == 1 ? "" : "s");
}

void
usage_stat(void)
{
printf(
"Display stats for active torrents.\n"
"The displayed stats are:\n"
"%% got, MB down, rate down. MB up, rate up\n"
"peer count, %% of pieces seen, tracker errors\n"
"\n"
"Usage: stat [-i] [-w seconds] [file ...]\n"
"\n"
"Arguments:\n"
"file ...\n"
"\tOnly display stats for the given torrent(s).\n"
"\n"
"Options:\n"
"-i\n"
"\tDisplay individual lines for each torrent.\n"
"\n"
"-w n\n"
"\tDisplay stats every n seconds.\n"
"\n");
exit(1);
}

void
do_stat(int individual, int seconds, int hash_count, uint8_t (*hashes)[20])
{
struct btstat *st;
struct tpstat tot;
again:
bzero(&tot, sizeof(tot));
tot.state = -1;
if (handle_ipc_res(btpd_stat(ipc, &st), "stat") != IPC_OK)
exit(1);
for (int i = 0; i < st->ntorrents; i++) {
struct tpstat *cur = &st->torrents[i];
if (hash_count > 0) {
int found = 0;
for (int h = 0; !found && h < hash_count; h++)
if (bcmp(cur->hash, hashes[h], 20) == 0)
found = 1;
if (!found)
continue;
}
tot.uploaded += cur->uploaded;
tot.downloaded += cur->downloaded;
tot.rate_up += cur->rate_up;
tot.rate_down += cur->rate_down;
tot.peers += cur->peers;
tot.pieces_seen += cur->pieces_seen;
tot.torrent_pieces += cur->torrent_pieces;
tot.content_got += cur->content_got;
tot.content_size += cur->content_size;
if (cur->tr_errors > 0)
tot.tr_errors++;
if (individual) {
printf("%s:\n", cur->name);
print_stat(cur);
}
}
free_btstat(st);
if (individual)
printf("Total:\n");
print_stat(&tot);
if (seconds > 0) {
sleep(seconds);
goto again;
}
}

struct option stat_opts [] = {
{ "help", no_argument, NULL, 'H' },
{NULL, 0, NULL, 0}
};

void
cmd_stat(int argc, char **argv)
torrent_spec(char *arg, struct ipc_torrent *tp)
{
int ch;
int wflag = 0, iflag = 0, seconds = 0;
uint8_t (*hashes)[20] = NULL;
char *endptr;
while ((ch = getopt_long(argc, argv, "iw:", stat_opts, NULL)) != -1) {
switch (ch) {
case 'i':
iflag = 1;
break;
case 'w':
wflag = 1;
seconds = strtol(optarg, &endptr, 10);
if (strlen(optarg) > endptr - optarg || seconds < 1)
usage_stat();
break;
default:
usage_stat();
}
char *p;
tp->u.num = strtoul(arg, &p, 10);
if (*p == '\0') {
tp->by_hash = 0;
return 1;
}
argc -= optind;
argv += optind;

if (argc > 0) {
hashes = malloc(argc * 20);
for (int i = 0; i < argc; i++) {
struct metainfo *mi;
if ((errno = load_metainfo(argv[i], -1, 0, &mi)) != 0)
err(1, "error loading torrent %s", argv[i]);
bcopy(mi->info_hash, hashes[i], 20);
clear_metainfo(mi);
free(mi);
}
if ((p = mi_load(arg, NULL)) == NULL) {
warnx("bad torrent '%s' (%s)", arg, strerror(errno));
return 0;
}
btpd_connect();
do_stat(iflag, seconds, argc, hashes);
tp->by_hash = 1;
mi_info_hash(p, tp->u.hash);
free(p);
return 1;
}

struct {
@@ -409,6 +70,8 @@ struct {
{ "del", cmd_del, usage_del },
{ "kill", cmd_kill, usage_kill },
{ "list", cmd_list, usage_list },
{ "start", cmd_start, usage_start },
{ "stop", cmd_stop, usage_stop },
{ "stat", cmd_stat, usage_stat }
};

@@ -434,7 +97,9 @@ usage(void)
"del\n"
"kill\n"
"list\n"
"start\n"
"stat\n"
"stop\n"
"\n");
exit(1);
}


+ 44
- 0
cli/btcli.h View File

@@ -0,0 +1,44 @@
#ifndef BTCLI_H
#define BTCLI_H

#include <err.h>
#include <errno.h>
#include <getopt.h>
#include <inttypes.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#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

+ 31
- 4
cli/btinfo.c View File

@@ -8,6 +8,7 @@
#include <stdlib.h>

#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--;


+ 30
- 0
cli/del.c View File

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

+ 35
- 0
cli/kill.c View File

@@ -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");
}

+ 124
- 0
cli/list.c View File

@@ -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");
}

+ 27
- 0
cli/start.c View File

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

+ 193
- 0
cli/stat.c View File

@@ -0,0 +1,193 @@
#include <math.h>

#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);
}

+ 27
- 0
cli/stop.c View File

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

Loading…
Cancel
Save