o Lot of work on the cli and its communication with btpd.master
@@ -12,58 +12,80 @@ | |||
#include <unistd.h> | |||
#include "btpd.h" | |||
#include "tracker_req.h" | |||
struct cli { | |||
int sd; | |||
struct event read; | |||
}; | |||
#define buf_swrite(iob, s) buf_write(iob, s, sizeof(s) - 1) | |||
static struct event m_cli_incoming; | |||
static void | |||
errdie(int error) | |||
{ | |||
if (error != 0) | |||
btpd_err("io_buf: %s.\n", strerror(error)); | |||
} | |||
static void | |||
cmd_stat(int argc, const char *args, FILE *fp) | |||
static int | |||
cmd_stat(struct cli *cli, int argc, const char *args) | |||
{ | |||
struct torrent *tp; | |||
struct io_buffer iob; | |||
errdie(buf_init(&iob, (1 << 14))); | |||
buf_init(&iob, (1 << 14)); | |||
errdie(buf_swrite(&iob, "d")); | |||
errdie(buf_print(&iob, "6:npeersi%ue", net_npeers)); | |||
errdie(buf_print(&iob, "9:ntorrentsi%ue", btpd_get_ntorrents())); | |||
errdie(buf_swrite(&iob, "8:torrentsl")); | |||
buf_swrite(&iob, "d"); | |||
buf_swrite(&iob, "4:codei0e"); | |||
buf_print(&iob, "6:npeersi%ue", net_npeers); | |||
buf_print(&iob, "9:ntorrentsi%ue", btpd_get_ntorrents()); | |||
buf_swrite(&iob, "8:torrentsl"); | |||
BTPDQ_FOREACH(tp, btpd_get_torrents(), entry) { | |||
if (tp->state != T_ACTIVE) | |||
continue; | |||
uint32_t seen_npieces = 0; | |||
for (uint32_t i = 0; i < tp->meta.npieces; i++) | |||
if (tp->net->piece_count[i] > 0) | |||
seen_npieces++; | |||
errdie(buf_print(&iob, "d4:downi%jue", (intmax_t)tp->net->downloaded)); | |||
errdie(buf_swrite(&iob, "4:hash20:")); | |||
errdie(buf_write(&iob, tp->meta.info_hash, 20)); | |||
errdie(buf_print(&iob, "4:havei%jde", (intmax_t)cm_get_size(tp))); | |||
errdie(buf_print(&iob, "6:npeersi%ue", tp->net->npeers)); | |||
errdie(buf_print(&iob, "7:npiecesi%ue", tp->meta.npieces)); | |||
errdie(buf_print(&iob, "4:path%d:%s", | |||
(int)strlen(tp->relpath), tp->relpath)); | |||
errdie(buf_print(&iob, "2:rdi%lue", tp->net->rate_dwn)); | |||
errdie(buf_print(&iob, "2:rui%lue", tp->net->rate_up)); | |||
errdie(buf_print(&iob, "12:seen npiecesi%ue", seen_npieces)); | |||
errdie(buf_print(&iob, "5:totali%jde", | |||
(intmax_t)tp->meta.total_length)); | |||
errdie(buf_print(&iob, "2:upi%juee", (intmax_t)tp->net->uploaded)); | |||
if (tp->state == T_ACTIVE) { | |||
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_print(&iob, "d4:downi%jue", (intmax_t)tp->net->downloaded); | |||
buf_print(&iob, "6:errorsi%ue", tr_errors(tp)); | |||
buf_swrite(&iob, "4:hash20:"); | |||
buf_write(&iob, tp->meta.info_hash, 20); | |||
buf_print(&iob, "4:havei%jde", (intmax_t)cm_get_size(tp)); | |||
buf_print(&iob, "6:npeersi%ue", tp->net->npeers); | |||
buf_print(&iob, "7:npiecesi%ue", tp->meta.npieces); | |||
buf_print(&iob, "3:numi%ue", tp->num); | |||
buf_print(&iob, "4:path%d:%s", (int)strlen(tp->meta.name), | |||
tp->meta.name); | |||
buf_print(&iob, "2:rdi%lue", tp->net->rate_dwn); | |||
buf_print(&iob, "2:rui%lue", tp->net->rate_up); | |||
buf_print(&iob, "12:seen npiecesi%ue", seen_npieces); | |||
buf_swrite(&iob, "5:state1:A"); | |||
buf_print(&iob, "5:totali%jde", (intmax_t)tp->meta.total_length); | |||
buf_print(&iob, "2:upi%juee", (intmax_t)tp->net->uploaded); | |||
} else { | |||
buf_swrite(&iob, "d4:hash20:"); | |||
buf_write(&iob, tp->meta.info_hash, 20); | |||
buf_print(&iob, "3:numi%ue", tp->num); | |||
buf_print(&iob, "4:path%d:%s", (int)strlen(tp->meta.name), | |||
tp->meta.name); | |||
switch (tp->state) { | |||
case T_INACTIVE: | |||
buf_swrite(&iob, "5:state1:Ie"); | |||
break; | |||
case T_STARTING: | |||
buf_swrite(&iob, "5:state1:Be"); | |||
break; | |||
case T_STOPPING: | |||
buf_swrite(&iob, "5:state1:Ee"); | |||
break; | |||
case T_ACTIVE: | |||
abort(); | |||
} | |||
} | |||
} | |||
errdie(buf_swrite(&iob, "ee")); | |||
buf_swrite(&iob, "ee"); | |||
uint32_t len = iob.buf_off; | |||
fwrite(&len, sizeof(len), 1, fp); | |||
fwrite(iob.buf, 1, iob.buf_off, fp); | |||
write_fully(cli->sd, &len, sizeof(len)); | |||
write_fully(cli->sd, iob.buf, iob.buf_off); | |||
free(iob.buf); | |||
return 0; | |||
} | |||
#if 0 | |||
@@ -71,9 +93,9 @@ static void | |||
cmd_add(int argc, const char *args, FILE *fp) | |||
{ | |||
struct io_buffer iob; | |||
errdie(buf_init(&iob, (1 << 10))); | |||
buf_init(&iob, (1 << 10)); | |||
errdie(buf_write(&iob, "l", 1)); | |||
buf_write(&iob, "l", 1); | |||
while (args != NULL) { | |||
size_t plen; | |||
char path[PATH_MAX]; | |||
@@ -87,16 +109,16 @@ cmd_add(int argc, const char *args, FILE *fp) | |||
benc_str(args, &pathp, &plen, &args); | |||
if (plen >= PATH_MAX) { | |||
errdie(buf_print(&iob, "d4:codei%dee", ENAMETOOLONG)); | |||
buf_print(&iob, "d4:codei%dee", ENAMETOOLONG); | |||
continue; | |||
} | |||
bcopy(pathp, path, plen); | |||
path[plen] = '\0'; | |||
btpd_log(BTPD_L_BTPD, "add request for %s.\n", path); | |||
errdie(buf_print(&iob, "d4:codei%dee", torrent_load(path))); | |||
buf_print(&iob, "d4:codei%dee", torrent_load(path)); | |||
} | |||
errdie(buf_write(&iob, "e", 1)); | |||
buf_write(&iob, "e", 1); | |||
uint32_t len = iob.buf_off; | |||
fwrite(&len, sizeof(len), 1, fp); | |||
@@ -108,9 +130,9 @@ static void | |||
cmd_del(int argc, const char *args, FILE *fp) | |||
{ | |||
struct io_buffer iob; | |||
errdie(buf_init(&iob, (1 << 10))); | |||
buf_init(&iob, (1 << 10)); | |||
errdie(buf_swrite(&iob, "l")); | |||
buf_swrite(&iob, "l"); | |||
while (args != NULL) { | |||
size_t len; | |||
@@ -127,13 +149,13 @@ cmd_del(int argc, const char *args, FILE *fp) | |||
if (tp != NULL) { | |||
btpd_log(BTPD_L_BTPD, "del request for %s.\n", tp->relpath); | |||
torrent_unload(tp); | |||
errdie(buf_swrite(&iob, "d4:codei0ee")); | |||
buf_swrite(&iob, "d4:codei0ee"); | |||
} else { | |||
btpd_log(BTPD_L_BTPD, "del request didn't match.\n"); | |||
errdie(buf_print(&iob, "d4:codei%dee", ENOENT)); | |||
buf_print(&iob, "d4:codei%dee", ENOENT); | |||
} | |||
} | |||
errdie(buf_swrite(&iob, "e")); | |||
buf_swrite(&iob, "e"); | |||
uint32_t len = iob.buf_off; | |||
fwrite(&len, sizeof(len), 1, fp); | |||
@@ -141,78 +163,144 @@ cmd_del(int argc, const char *args, FILE *fp) | |||
free(iob.buf); | |||
} | |||
static void | |||
cmd_die(int argc, const char *args, FILE *fp) | |||
#endif | |||
static int | |||
cmd_die(struct cli *cli, int argc, const char *args) | |||
{ | |||
char res[] = "d4:codei0ee"; | |||
uint32_t len = sizeof(res) - 1; | |||
fwrite(&len, sizeof(len), 1, fp); | |||
fwrite(res, 1, len, fp); | |||
fflush(fp); | |||
write_fully(cli->sd, &len, sizeof(len)); | |||
write_fully(cli->sd, res, len); | |||
btpd_log(BTPD_L_BTPD, "Someone wants me dead.\n"); | |||
btpd_shutdown(); | |||
btpd_shutdown((& (struct timeval) { 0, 0 })); | |||
return 0; | |||
} | |||
static int | |||
cmd_start(struct cli *cli, int argc, const char *args) | |||
{ | |||
if (argc != 1 || !benc_isint(args)) | |||
return EINVAL; | |||
int code; | |||
unsigned num; | |||
num = benc_int(args, NULL); | |||
struct torrent *tp = btpd_get_torrent_num(num); | |||
if (tp != NULL) { | |||
torrent_activate(tp); | |||
code = 0; | |||
} else | |||
code = 1; | |||
struct io_buffer iob; | |||
buf_init(&iob, 16); | |||
buf_print(&iob, "d4:codei%dee", code); | |||
uint32_t len = iob.buf_off; | |||
write_fully(cli->sd, &len, sizeof(len)); | |||
write_fully(cli->sd, iob.buf, iob.buf_off); | |||
return 0; | |||
} | |||
static int | |||
cmd_stop(struct cli *cli, int argc, const char *args) | |||
{ | |||
btpd_log(BTPD_L_BTPD, "%d\n", argc); | |||
if (argc != 1 || !benc_isint(args)) | |||
return EINVAL; | |||
int code; | |||
unsigned num; | |||
num = benc_int(args, NULL); | |||
struct torrent *tp = btpd_get_torrent_num(num); | |||
if (tp != NULL) { | |||
torrent_deactivate(tp); | |||
code = 0; | |||
} else | |||
code = 1; | |||
struct io_buffer iob; | |||
buf_init(&iob, 16); | |||
buf_print(&iob, "d4:codei%dee", code); | |||
uint32_t len = iob.buf_off; | |||
write_fully(cli->sd, &len, sizeof(len)); | |||
write_fully(cli->sd, iob.buf, iob.buf_off); | |||
return 0; | |||
} | |||
#endif | |||
static struct { | |||
const char *name; | |||
int nlen; | |||
void (*fun)(int, const char *, FILE *); | |||
int (*fun)(struct cli *cli, int, const char *); | |||
} cmd_table[] = { | |||
#if 0 | |||
{ "add", 3, cmd_add }, | |||
{ "del", 3, cmd_del }, | |||
{ "die", 3, cmd_die }, | |||
#endif | |||
{ "stat", 4, cmd_stat } | |||
{ "die", 3, cmd_die }, | |||
{ "start", 5, cmd_start }, | |||
{ "stat", 4, cmd_stat }, | |||
{ "stop", 4, cmd_stop } | |||
}; | |||
static int ncmds = sizeof(cmd_table) / sizeof(cmd_table[0]); | |||
static void | |||
cmd_dispatch(const char *buf, FILE *fp) | |||
static int | |||
cmd_dispatch(struct cli *cli, const char *buf) | |||
{ | |||
int err = 0; | |||
size_t cmdlen; | |||
const char *cmd; | |||
const char *args; | |||
int found = 0; | |||
benc_str(benc_first(buf), &cmd, &cmdlen, &args); | |||
cmd = benc_mem(benc_first(buf), &cmdlen, &args); | |||
for (int i = 0; !found && i < ncmds; i++) { | |||
if (cmdlen == cmd_table[i].nlen && | |||
strncmp(cmd_table[i].name, cmd, cmdlen) == 0) { | |||
cmd_table[i].fun(benc_nelems(buf) - 1, args, fp); | |||
found = 1; | |||
btpd_log(BTPD_L_BTPD, "%.*s\n", (int)cmdlen, cmd); | |||
for (int i = 0; i < ncmds; i++) { | |||
if ((cmdlen == cmd_table[i].nlen && | |||
strncmp(cmd_table[i].name, cmd, cmdlen) == 0)) { | |||
err = cmd_table[i].fun(cli, benc_nelems(buf) - 1, args); | |||
break; | |||
} | |||
} | |||
return err; | |||
} | |||
static void | |||
do_ipc(FILE *fp) | |||
cli_read_cb(int sd, short type, void *arg) | |||
{ | |||
uint32_t cmdlen, nread; | |||
char *buf; | |||
struct cli *cli = arg; | |||
uint32_t cmdlen; | |||
uint8_t *msg = NULL; | |||
if (fread(&cmdlen, sizeof(cmdlen), 1, fp) != 1) | |||
return; | |||
if (read_fully(sd, &cmdlen, sizeof(cmdlen)) != 0) | |||
goto error; | |||
buf = btpd_malloc(cmdlen); | |||
msg = btpd_malloc(cmdlen); | |||
if (read_fully(sd, msg, cmdlen) != 0) | |||
goto error; | |||
if ((nread = fread(buf, 1, cmdlen, fp)) == cmdlen) { | |||
if (benc_validate(buf, cmdlen) == 0 && benc_islst(buf) && | |||
benc_first(buf) != NULL && benc_isstr(benc_first(buf))) | |||
cmd_dispatch(buf, fp); | |||
} | |||
if (!(benc_validate(msg, cmdlen) == 0 && benc_islst(msg) && | |||
benc_first(msg) != NULL && benc_isstr(benc_first(msg)))) | |||
goto error; | |||
free(buf); | |||
if (cmd_dispatch(cli, msg) != 0) | |||
goto error; | |||
event_add(&cli->read, NULL); | |||
return; | |||
error: | |||
close(cli->sd); | |||
free(cli); | |||
if (msg != NULL) | |||
free(msg); | |||
} | |||
void | |||
client_connection_cb(int sd, short type, void *arg) | |||
{ | |||
int nsd; | |||
FILE *fp; | |||
if ((nsd = accept(sd, NULL, NULL)) < 0) { | |||
if (errno == EWOULDBLOCK || errno == ECONNABORTED) | |||
@@ -224,14 +312,10 @@ client_connection_cb(int sd, short type, void *arg) | |||
if ((errno = set_blocking(nsd)) != 0) | |||
btpd_err("set_blocking: %s.\n", strerror(errno)); | |||
if ((fp = fdopen(nsd, "r+")) == NULL) { | |||
close(nsd); | |||
return; | |||
} | |||
do_ipc(fp); | |||
fclose(fp); | |||
struct cli *cli = btpd_calloc(1, sizeof(*cli)); | |||
cli->sd = nsd; | |||
event_set(&cli->read, cli->sd, EV_READ, cli_read_cb, cli); | |||
event_add(&cli->read, NULL); | |||
} | |||
void | |||
@@ -40,15 +40,12 @@ static void tr_send(struct torrent *tp, enum tr_event event); | |||
void | |||
maybe_connect_to(struct torrent *tp, const char *pinfo) | |||
{ | |||
const char *pid = NULL; | |||
char *ip = NULL; | |||
int64_t port; | |||
const char *pid; | |||
char *ip; | |||
int port; | |||
size_t len; | |||
if (!benc_isdct(pinfo)) | |||
return; | |||
if (benc_dget_str(pinfo, "peer id", &pid, &len) != 0 || len != 20) | |||
if ((pid = benc_dget_mem(pinfo, "peer id", &len)) == NULL || len != 20) | |||
return; | |||
if (bcmp(btpd_get_peer_id(), pid, 20) == 0) | |||
@@ -56,71 +53,60 @@ maybe_connect_to(struct torrent *tp, const char *pinfo) | |||
if (net_torrent_has_peer(tp->net, pid)) | |||
return; | |||
if ((ip = benc_dget_str(pinfo, "ip", NULL)) == NULL) | |||
return; | |||
if (benc_dget_strz(pinfo, "ip", &ip, NULL) != 0) | |||
goto out; | |||
if (benc_dget_int64(pinfo, "port", &port) != 0) | |||
goto out; | |||
port = benc_dget_int(pinfo, "port"); | |||
peer_create_out(tp->net, pid, ip, port); | |||
out: | |||
if (ip != NULL) | |||
free(ip); | |||
} | |||
static int | |||
parse_reply(struct torrent *tp, const char *content, size_t size) | |||
{ | |||
char *buf; | |||
const char *buf; | |||
size_t len; | |||
const char *peers; | |||
uint32_t interval; | |||
int interval; | |||
if (benc_validate(content, size) != 0 || !benc_isdct(content)) { | |||
btpd_log(BTPD_L_ERROR, "Bad data from tracker.\n"); | |||
if ((buf = benc_dget_mem(content, "failure reason", &len)) != NULL) { | |||
btpd_log(BTPD_L_ERROR, "Tracker failure: %.*s.\n", (int)len, buf); | |||
return 1; | |||
} | |||
if ((benc_dget_strz(content, "failure reason", &buf, NULL)) == 0) { | |||
btpd_log(BTPD_L_ERROR, "Tracker failure: %s.\n", buf); | |||
free(buf); | |||
return 1; | |||
} | |||
if ((benc_validate(content, size) != 0 || | |||
!benc_dct_chk(content, 2, BE_INT, 1, "interval", | |||
BE_ANY, 1, "peers"))) | |||
goto bad_data; | |||
if ((benc_dget_uint32(content, "interval", &interval)) != 0) { | |||
btpd_log(BTPD_L_ERROR, "Bad data from tracker.\n"); | |||
return 1; | |||
} | |||
interval = benc_dget_int(content, "interval"); | |||
if (interval < 1) | |||
goto bad_data; | |||
tp->tr->interval = interval; | |||
btpd_log(BTPD_L_BTPD, "Got interval %d.\n", interval); | |||
int error = 0; | |||
size_t length; | |||
peers = benc_dget_any(content, "peers"); | |||
if ((error = benc_dget_lst(content, "peers", &peers)) == 0) { | |||
if (benc_islst(peers)) { | |||
for (peers = benc_first(peers); | |||
peers != NULL && net_npeers < net_max_peers; | |||
peers = benc_next(peers)) | |||
maybe_connect_to(tp, peers); | |||
} | |||
if (error == EINVAL) { | |||
error = benc_dget_str(content, "peers", &peers, &length); | |||
if (error == 0 && length % 6 == 0) { | |||
size_t i; | |||
for (i = 0; i < length && net_npeers < net_max_peers; i += 6) | |||
peer_create_out_compact(tp->net, peers + i); | |||
} | |||
} | |||
if (error != 0) { | |||
btpd_log(BTPD_L_ERROR, "Bad data from tracker.\n"); | |||
return 1; | |||
} | |||
} else if (benc_isstr(peers)) { | |||
peers = benc_dget_mem(content, "peers", &len); | |||
for (size_t i = 0; i < len && net_npeers < net_max_peers; i += 6) | |||
peer_create_out_compact(tp->net, peers + i); | |||
} else | |||
goto bad_data; | |||
return 0; | |||
bad_data: | |||
btpd_log(BTPD_L_ERROR, "Bad data from tracker.\n"); | |||
return 1; | |||
} | |||
static void | |||
@@ -1,587 +1,422 @@ | |||
#include <sys/types.h> | |||
#include <sys/time.h> | |||
#include <err.h> | |||
#include <errno.h> | |||
#include <fcntl.h> | |||
#include <getopt.h> | |||
#include <inttypes.h> | |||
#include <math.h> | |||
#include <stdio.h> | |||
#include <stdlib.h> | |||
#include <string.h> | |||
#include <unistd.h> | |||
#include <openssl/sha.h> | |||
#include "benc.h" | |||
#include "metainfo.h" | |||
#include "stream.h" | |||
#include "subr.h" | |||
#include "btpd_if.h" | |||
static void | |||
usage() | |||
{ | |||
printf("Usage: btcli command [options] [files]\n" | |||
"Commands:\n" | |||
"add <file_1> ... [file_n]\n" | |||
"\tAdd the given torrents to btpd.\n" | |||
"\n" | |||
"del <file_1> ... [file_n]\n" | |||
"\tRemove the given torrents from btpd.\n" | |||
"\n" | |||
"die\n" | |||
"\tShut down btpd.\n" | |||
"\n" | |||
"list\n" | |||
"\tList active torrents.\n" | |||
"\n" | |||
"stat [-i] [-w n] [file_1] ... [file_n]\n" | |||
"\tShow stats for either all active or the given torrents.\n" | |||
"\tThe stats displayed are:\n" | |||
"\t%% of pieces seen, %% of pieces verified, \n" | |||
"\tMB down, rate down, MB up, rate up, no peers\n" | |||
"-i\n" | |||
"\tShow stats per torrent in addition to total stats.\n" | |||
"-w n\n" | |||
"\tRepeat every n seconds.\n" | |||
"\n" | |||
"Common options:\n" | |||
"--ipc key\n" | |||
"\tTalk to the btpd started with the same key.\n" | |||
"\n" | |||
"--help\n" | |||
"\tShow this help.\n" | |||
"\n"); | |||
exit(1); | |||
} | |||
static const char *btpd_dir = "/usr/btpd"; | |||
static struct ipc *ipc; | |||
static void | |||
handle_error(int error) | |||
handle_ipc_res(enum ipc_code code) | |||
{ | |||
switch (error) { | |||
case 0: | |||
switch (code) { | |||
case IPC_OK: | |||
return; | |||
case IPC_FAIL: | |||
warnx("Ipc failed.\n"); | |||
break; | |||
case ENOENT: | |||
case ECONNREFUSED: | |||
errx(1, "Couldn't connect. Check that btpd is running."); | |||
default: | |||
errx(1, "%s", strerror(error)); | |||
case IPC_COMMERR: | |||
errx(1, "Communication error.\n"); | |||
} | |||
} | |||
static void | |||
do_ipc_open(char *ipctok, struct ipc **ipc) | |||
{ | |||
switch (ipc_open(ipctok, ipc)) { | |||
case 0: | |||
break; | |||
case EINVAL: | |||
errx(1, "--ipc argument only takes letters and digits."); | |||
case ENAMETOOLONG: | |||
errx(1, "--ipc argument is too long."); | |||
} | |||
} | |||
struct cb { | |||
char *path; | |||
uint8_t *piece_field; | |||
uint32_t have; | |||
struct metainfo *meta; | |||
}; | |||
static void | |||
hash_cb(uint32_t index, uint8_t *hash, void *arg) | |||
{ | |||
struct cb *cb = arg; | |||
if (hash != NULL) | |||
if (bcmp(hash, cb->meta->piece_hash[index], SHA_DIGEST_LENGTH) == 0) { | |||
set_bit(cb->piece_field, index); | |||
cb->have++; | |||
} | |||
printf("\rTested: %5.1f%%", 100.0 * (index + 1) / cb->meta->npieces); | |||
fflush(stdout); | |||
} | |||
static int | |||
fd_cb(const char *path, int *fd, void *arg) | |||
{ | |||
struct cb *fp = arg; | |||
return vopen(fd, O_RDONLY, "%s.d/%s", fp->path, path); | |||
} | |||
static void | |||
gen_ifile(char *path) | |||
btpd_connect(void) | |||
{ | |||
int fd; | |||
struct cb cb; | |||
struct metainfo *mi; | |||
size_t field_len; | |||
if ((errno = load_metainfo(path, -1, 1, &mi)) != 0) | |||
err(1, "load_metainfo: %s", path); | |||
field_len = ceil(mi->npieces / 8.0); | |||
cb.path = path; | |||
cb.piece_field = calloc(1, field_len); | |||
cb.have = 0; | |||
cb.meta = mi; | |||
if (cb.piece_field == NULL) | |||
errx(1, "Out of memory.\n"); | |||
if ((errno = bts_hashes(mi, fd_cb, hash_cb, &cb)) != 0) | |||
err(1, "bts_hashes"); | |||
printf("\nHave: %5.1f%%\n", 100.0 * cb.have / cb.meta->npieces); | |||
if ((errno = vopen(&fd, O_WRONLY|O_CREAT, "%s.i", path)) != 0) | |||
err(1, "opening %s.i", path); | |||
if (ftruncate(fd, field_len + mi->npieces * | |||
(off_t)ceil(mi->piece_length / (double)(1 << 17))) < 0) | |||
err(1, "ftruncate: %s", path); | |||
if (write(fd, cb.piece_field, field_len) != field_len) | |||
err(1, "write %s.i", path); | |||
if (close(fd) < 0) | |||
err(1, "close %s.i", path); | |||
clear_metainfo(mi); | |||
free(mi); | |||
if ((errno = ipc_open(btpd_dir, &ipc)) != 0) | |||
errx(1, "Couldn't connect to btpd in %s (%s).\n", | |||
btpd_dir, strerror(errno)); | |||
} | |||
static struct option add_opts[] = { | |||
{ "ipc", required_argument, NULL, 1 }, | |||
{ "help", required_argument, NULL, 2}, | |||
{NULL, 0, NULL, 0} | |||
}; | |||
static void | |||
do_add(char *ipctok, char **paths, int npaths, char **out) | |||
void | |||
usage_add(void) | |||
{ | |||
struct ipc *ipc; | |||
do_ipc_open(ipctok, &ipc); | |||
handle_error(btpd_add(ipc, paths, npaths, out)); | |||
ipc_close(ipc); | |||
printf( | |||
"Add a torrent to btpd.\n" | |||
"\n" | |||
"Usage: add [-a] [-s] [-c dir] -f file\n" | |||
"\n" | |||
"Options:\n" | |||
"-a\n" | |||
"\tAppend the torrent top directory (if any) to the content path.\n" | |||
"\n" | |||
"-c dir\n" | |||
"\tThe directory where the content is (or will be downloaded to).\n" | |||
"\tDefault is the directory containing the torrent file.\n" | |||
"\n" | |||
"-f file\n" | |||
"\tThe torrent to add.\n" | |||
"\n" | |||
"-s\n" | |||
"\tStart the torrent.\n" | |||
"\n" | |||
); | |||
exit(1); | |||
} | |||
static void | |||
void | |||
cmd_add(int argc, char **argv) | |||
{ | |||
int ch; | |||
char *ipctok = NULL; | |||
while ((ch = getopt_long(argc, argv, "", add_opts, NULL)) != -1) { | |||
switch(ch) { | |||
case 1: | |||
ipctok = optarg; | |||
break; | |||
default: | |||
usage(); | |||
} | |||
} | |||
argc -= optind; | |||
argv += optind; | |||
if (argc < 1) | |||
usage(); | |||
for (int i = 0; i < argc; i++) { | |||
int64_t code; | |||
char *res; | |||
int fd; | |||
char *path; | |||
errno = vopen(&fd, O_RDONLY, "%s.i", argv[i]); | |||
if (errno == ENOENT) { | |||
printf("Testing %s for content.\n", argv[i]); | |||
gen_ifile(argv[i]); | |||
} else if (errno != 0) | |||
err(1, "open %s.i", argv[i]); | |||
else | |||
close(fd); | |||
if ((errno = canon_path(argv[i], &path)) != 0) | |||
err(1, "canon_path"); | |||
do_add(ipctok, &path, 1, &res); | |||
free(path); | |||
benc_dget_int64(benc_first(res), "code", &code); | |||
if (code == EEXIST) | |||
printf("btpd already had %s.\n", argv[i]); | |||
else if (code != 0) { | |||
printf("btpd indicates error: %s for %s.\n", | |||
strerror(code), argv[i]); | |||
} | |||
free(res); | |||
} | |||
} | |||
static struct option del_opts[] = { | |||
{ "ipc", required_argument, NULL, 1 }, | |||
{ "help", required_argument, NULL, 2}, | |||
{NULL, 0, NULL, 0} | |||
}; | |||
static void | |||
do_del(char *ipctok, uint8_t (*hashes)[20], int nhashes, char **out) | |||
void | |||
usage_del(void) | |||
{ | |||
struct ipc *ipc; | |||
do_ipc_open(ipctok, &ipc); | |||
handle_error(btpd_del(ipc, hashes, nhashes, out)); | |||
ipc_close(ipc); | |||
printf( | |||
"Remove torrents from btpd.\n" | |||
"\n" | |||
"Usage: del num ...\n" | |||
"\n" | |||
"Arguments:\n" | |||
"num\n" | |||
"\tThe number of the torrent to remove.\n" | |||
"\n"); | |||
exit(1); | |||
} | |||
static void | |||
void | |||
cmd_del(int argc, char **argv) | |||
{ | |||
int ch; | |||
char *ipctok = NULL; | |||
while ((ch = getopt_long(argc, argv, "", del_opts, NULL)) != -1) { | |||
switch(ch) { | |||
case 1: | |||
ipctok = optarg; | |||
break; | |||
default: | |||
usage(); | |||
} | |||
} | |||
argc -= optind; | |||
argv += optind; | |||
if (argc < 1) | |||
usage(); | |||
uint8_t hashes[argc][20]; | |||
char *res; | |||
const char *d; | |||
for (int i = 0; i < argc; i++) { | |||
struct metainfo *mi; | |||
if ((errno = load_metainfo(argv[i], -1, 0, &mi)) != 0) | |||
err(1, "load_metainfo: %s", argv[i]); | |||
bcopy(mi->info_hash, hashes[i], 20); | |||
clear_metainfo(mi); | |||
free(mi); | |||
} | |||
do_del(ipctok, hashes, argc, &res); | |||
d = benc_first(res); | |||
for (int i = 0; i < argc; i++) { | |||
int64_t code; | |||
benc_dget_int64(d, "code", &code); | |||
if (code == ENOENT) | |||
printf("btpd didn't have %s.\n", argv[i]); | |||
else if (code != 0) { | |||
printf("btpd indicates error: %s for %s.\n", | |||
strerror(code), argv[i]); | |||
} | |||
d = benc_next(d); | |||
if (argc < 2) | |||
usage_del(); | |||
unsigned nums[argc - 1]; | |||
char *endptr; | |||
for (int i = 0; i < argc - 1; i++) { | |||
nums[i] = strtoul(argv[i + 1], &endptr, 10); | |||
if (strlen(argv[i + 1]) > endptr - argv[i + 1]) | |||
usage_del(); | |||
} | |||
free(res); | |||
btpd_connect(); | |||
for (int i = 0; i < argc -1; i++) | |||
handle_ipc_res(btpd_del_num(ipc, nums[i])); | |||
} | |||
static struct option die_opts[] = { | |||
{ "ipc", required_argument, NULL, 1 }, | |||
{ "help", no_argument, NULL, 2 }, | |||
{NULL, 0, NULL, 0} | |||
}; | |||
static void | |||
do_die(char *ipctok) | |||
void | |||
usage_kill(void) | |||
{ | |||
struct ipc *ipc; | |||
do_ipc_open(ipctok, &ipc); | |||
handle_error(btpd_die(ipc)); | |||
ipc_close(ipc); | |||
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); | |||
} | |||
static void | |||
cmd_die(int argc, char **argv) | |||
void | |||
cmd_kill(int argc, char **argv) | |||
{ | |||
int ch; | |||
char *ipctok = NULL; | |||
while ((ch = getopt_long(argc, argv, "", die_opts, NULL)) != -1) { | |||
switch (ch) { | |||
case 1: | |||
ipctok = optarg; | |||
break; | |||
default: | |||
usage(); | |||
} | |||
} | |||
do_die(ipctok); | |||
} | |||
static struct option stat_opts[] = { | |||
{ "ipc", required_argument, NULL, 1 }, | |||
{ "help", no_argument, NULL, 2 }, | |||
{NULL, 0, NULL, 0} | |||
}; | |||
int seconds = -1; | |||
char *endptr; | |||
if (argc == 1) | |||
; | |||
else if (argc == 2) { | |||
seconds = strtol(argv[1], &endptr, 10); | |||
if (strlen(argv[1]) > endptr - argv[1] || seconds < 0) | |||
usage_kill(); | |||
} else | |||
usage_kill(); | |||
static void | |||
do_stat(char *ipctok, char **out) | |||
{ | |||
struct ipc *ipc; | |||
do_ipc_open(ipctok, &ipc); | |||
handle_error(btpd_stat(ipc, out)); | |||
ipc_close(ipc); | |||
btpd_connect(); | |||
btpd_die(ipc, seconds); | |||
} | |||
struct tor { | |||
char *path; | |||
uint8_t hash[20]; | |||
uint64_t down; | |||
uint64_t up; | |||
uint64_t npeers; | |||
uint64_t npieces; | |||
uint64_t have_npieces; | |||
uint64_t seen_npieces; | |||
}; | |||
struct tor **parse_tors(char *res, uint8_t (*hashes)[20], int nhashes) | |||
void | |||
usage_list(void) | |||
{ | |||
struct tor **tors; | |||
int64_t num; | |||
const char *p; | |||
benc_dget_int64(res, "ntorrents", &num); | |||
benc_dget_lst(res, "torrents", &p); | |||
tors = calloc(sizeof(*tors), num + 1); | |||
int i = 0; | |||
for (p = benc_first(p); p; p = benc_next(p)) { | |||
int j; | |||
const char *hash; | |||
benc_dget_str(p, "hash", &hash, NULL); | |||
for (j = 0; j < nhashes; j++) { | |||
if (bcmp(hashes[i], hash, 20) == 0) | |||
break; | |||
} | |||
if (j < nhashes || nhashes == 0) { | |||
tors[i] = calloc(sizeof(*tors[i]), 1); | |||
bcopy(hash, tors[i]->hash, 20); | |||
benc_dget_int64(p, "down", &tors[i]->down); | |||
benc_dget_int64(p, "up", &tors[i]->up); | |||
benc_dget_int64(p, "npeers", &tors[i]->npeers); | |||
benc_dget_int64(p, "npieces", &tors[i]->npieces); | |||
benc_dget_int64(p, "have npieces", &tors[i]->have_npieces); | |||
benc_dget_int64(p, "seen npieces", &tors[i]->seen_npieces); | |||
benc_dget_strz(p, "path", &tors[i]->path, NULL); | |||
i++; | |||
} | |||
} | |||
return tors; | |||
printf( | |||
"List btpd's torrents.\n" | |||
"\n" | |||
"Usage: list\n" | |||
"\n" | |||
); | |||
exit(1); | |||
} | |||
static void | |||
free_tors(struct tor **tors) | |||
void | |||
cmd_list(int argc, char **argv) | |||
{ | |||
for (int i = 0; tors[i] != NULL; i++) { | |||
free(tors[i]->path); | |||
free(tors[i]); | |||
} | |||
free(tors); | |||
struct btstat *st; | |||
if (argc > 1) | |||
usage_list(); | |||
btpd_connect(); | |||
if ((errno = btpd_stat(ipc, &st)) != 0) | |||
err(1, "btpd_stat"); | |||
for (int i = 0; i < st->ntorrents; i++) | |||
printf("%u. %s (%c)\n", st->torrents[i].num, st->torrents[i].name, | |||
st->torrents[i].state); | |||
printf("Listed %u torrent%s.\n", st->ntorrents, | |||
st->ntorrents == 1 ? "" : "s"); | |||
} | |||
static void | |||
print_stat(struct tor *cur, struct tor *old, double ds) | |||
void | |||
usage_stat(void) | |||
{ | |||
if (old == NULL) { | |||
printf("%5.1f%% %5.1f%% %6.1fM - kB/s %6.1fM - kB/s %4u\n", | |||
100 * cur->seen_npieces / (double)cur->npieces, | |||
100 * cur->have_npieces / (double)cur->npieces, | |||
cur->down / (double)(1 << 20), | |||
cur->up / (double)(1 << 20), | |||
(unsigned)cur->npeers); | |||
} else { | |||
printf("%5.1f%% %5.1f%% %6.1fM %7.2fkB/s %6.1fM %7.2fkB/s %4u\n", | |||
100 * cur->seen_npieces / (double)cur->npieces, | |||
100 * cur->have_npieces / (double)cur->npieces, | |||
cur->down / (double)(1 << 20), | |||
(cur->down - old->down) / ds / (1 << 10), | |||
cur->up / (double)(1 << 20), | |||
(cur->up - old->up) / ds / (1 << 10), | |||
(unsigned)cur->npeers | |||
); | |||
} | |||
printf( | |||
"Display btpd stats.\n" | |||
"\n" | |||
"Usage: stat [-i] [-w seconds]\n" | |||
"\n" | |||
"Options:\n" | |||
"-i\n" | |||
"\tDisplay indivudal lines for each active torrent.\n" | |||
"\n" | |||
"-w n\n" | |||
"\tDisplay stats every n seconds.\n" | |||
"\n"); | |||
exit(1); | |||
} | |||
static void | |||
grok_stat(char *ipctok, int iflag, int wait, | |||
uint8_t (*hashes)[20], int nhashes) | |||
void | |||
do_stat(int individual, int seconds) | |||
{ | |||
int i, j; | |||
char *res; | |||
struct tor **cur, **old = NULL; | |||
struct tor curtot, oldtot; | |||
struct timeval tv_cur, tv_old; | |||
double ds; | |||
struct btstat *st; | |||
struct tpstat tot; | |||
again: | |||
do_stat(ipctok, &res); | |||
gettimeofday(&tv_cur, NULL); | |||
if (old == NULL) | |||
ds = wait; | |||
else { | |||
struct timeval delta; | |||
timersub(&tv_old, &tv_cur, &delta); | |||
ds = delta.tv_sec + delta.tv_usec / 1000000.0; | |||
if (ds < 0) | |||
ds = wait; | |||
} | |||
tv_old = tv_cur; | |||
cur = parse_tors(res, hashes, nhashes); | |||
free(res); | |||
if (iflag) { | |||
for (i = 0; cur[i] != NULL; i++) { | |||
if (old == NULL) { | |||
printf("%s:\n", rindex(cur[i]->path, '/') + 1); | |||
print_stat(cur[i], NULL, ds); | |||
} else { | |||
for (j = 0; old[j] != NULL; j++) | |||
if (bcmp(cur[i]->hash, old[j]->hash, 20) == 0) | |||
break; | |||
printf("%s:\n", rindex(cur[i]->path, '/') + 1); | |||
print_stat(cur[i], old[j], ds); | |||
} | |||
bzero(&tot, sizeof(tot)); | |||
if ((errno = btpd_stat(ipc, &st)) != 0) | |||
err(1, "btpd_stat"); | |||
for (int i = 0; i < st->ntorrents; i++) { | |||
struct tpstat *cur = &st->torrents[i]; | |||
if (cur->state != 'A') | |||
continue; | |||
if (!individual) { | |||
tot.uploaded += cur->uploaded; | |||
tot.downloaded += cur->downloaded; | |||
tot.rate_up += cur->rate_up; | |||
tot.rate_down += cur->rate_down; | |||
tot.npeers += cur->npeers; | |||
continue; | |||
} | |||
printf("%u. %5.1f%% %6.1fM %7.2fkB/s %6.1fM %7.2fkB/s %4u %5.1f%%", | |||
cur->num, | |||
100.0 * cur->have / cur->total, | |||
(double)cur->downloaded / (1 << 20), | |||
(double)cur->rate_down / (20 << 10), | |||
(double)cur->uploaded / (1 << 20), | |||
(double)cur->rate_up / (20 << 10), | |||
cur->npeers, | |||
100.0 * cur->nseen / cur->npieces | |||
); | |||
if (cur->errors > 0) | |||
printf(" E%u", cur->errors); | |||
printf("\n"); | |||
} | |||
bzero(&curtot, sizeof(curtot)); | |||
for (i = 0; cur[i] != NULL; i++) { | |||
curtot.down += cur[i]->down; | |||
curtot.up += cur[i]->up; | |||
curtot.npeers += cur[i]->npeers; | |||
curtot.npieces += cur[i]->npieces; | |||
curtot.have_npieces += cur[i]->have_npieces; | |||
curtot.seen_npieces += cur[i]->seen_npieces; | |||
free_btstat(st); | |||
if (!individual) { | |||
printf("%6.1fM %7.2fkB/s %6.1fM %7.2fkB/s %4u\n", | |||
(double)tot.downloaded / (1 << 20), | |||
(double)tot.rate_down / (20 << 10), | |||
(double)tot.uploaded / (1 << 20), | |||
(double)tot.rate_up / (20 << 10), | |||
tot.npeers); | |||
} | |||
if (iflag) | |||
printf("Total:\n"); | |||
if (old != NULL) | |||
print_stat(&curtot, &oldtot, ds); | |||
else | |||
print_stat(&curtot, NULL, ds); | |||
if (wait) { | |||
if (old != NULL) | |||
free_tors(old); | |||
old = cur; | |||
oldtot = curtot; | |||
sleep(wait); | |||
if (seconds > 0) { | |||
sleep(seconds); | |||
goto again; | |||
} | |||
free_tors(cur); | |||
} | |||
static void | |||
static struct option stat_opts [] = { | |||
{ "help", no_argument, NULL, 1 }, | |||
{NULL, 0, NULL, 0} | |||
}; | |||
void | |||
cmd_stat(int argc, char **argv) | |||
{ | |||
int ch; | |||
char *ipctok = NULL; | |||
int wait = 0; | |||
int iflag = 0; | |||
int wflag = 0, iflag = 0, seconds = 0; | |||
char *endptr; | |||
while ((ch = getopt_long(argc, argv, "iw:", stat_opts, NULL)) != -1) { | |||
switch (ch) { | |||
case 'i': | |||
iflag = 1; | |||
break; | |||
case 'w': | |||
wait = atoi(optarg); | |||
if (wait <= 0) | |||
errx(1, "-w argument must be an integer > 0."); | |||
break; | |||
case 1: | |||
ipctok = optarg; | |||
wflag = 1; | |||
seconds = strtol(optarg, &endptr, 10); | |||
if (strlen(optarg) > endptr - optarg || seconds < 1) | |||
usage_stat(); | |||
break; | |||
default: | |||
usage(); | |||
usage_stat(); | |||
} | |||
} | |||
argc -= optind; | |||
argv += optind; | |||
if (argc > 0) | |||
usage_stat(); | |||
if (argc > 0) { | |||
uint8_t hashes[argc][20]; | |||
for (int i = 0; i < argc; i++) { | |||
struct metainfo *mi; | |||
if ((errno = load_metainfo(argv[i], -1, 0, &mi)) != 0) | |||
err(1, "load_metainfo: %s", argv[i]); | |||
bcopy(mi->info_hash, hashes[i], 20); | |||
clear_metainfo(mi); | |||
free(mi); | |||
} | |||
grok_stat(ipctok, iflag, wait, hashes, argc); | |||
} else | |||
grok_stat(ipctok, iflag, wait, NULL, 0); | |||
btpd_connect(); | |||
do_stat(iflag, seconds); | |||
} | |||
static struct option list_opts[] = { | |||
{ "ipc", required_argument, NULL, 1 }, | |||
{ "help", no_argument, NULL, 2 }, | |||
{NULL, 0, NULL, 0} | |||
}; | |||
static void | |||
cmd_list(int argc, char **argv) | |||
void | |||
usage_start(void) | |||
{ | |||
int ch; | |||
char *ipctok = NULL; | |||
printf( | |||
"Activate torrents.\n" | |||
"\n" | |||
"Usage: start num ...\n" | |||
"\n" | |||
"Arguments:\n" | |||
"num\n" | |||
"\tThe number of the torrent to activate.\n" | |||
"\n"); | |||
exit(1); | |||
} | |||
while ((ch = getopt_long(argc, argv, "", list_opts, NULL)) != -1) { | |||
switch (ch) { | |||
case 1: | |||
ipctok = optarg; | |||
break; | |||
default: | |||
usage(); | |||
} | |||
void | |||
cmd_start(int argc, char **argv) | |||
{ | |||
if (argc < 2) | |||
usage_start(); | |||
unsigned nums[argc - 1]; | |||
char *endptr; | |||
for (int i = 0; i < argc - 1; i++) { | |||
nums[i] = strtoul(argv[i + 1], &endptr, 10); | |||
if (strlen(argv[i + 1]) > endptr - argv[i + 1]) | |||
usage_start(); | |||
} | |||
char *res; | |||
const char *p; | |||
char *path; | |||
do_stat(ipctok, &res); | |||
benc_dget_lst(res, "torrents", &p); | |||
int count = 0; | |||
for (p = benc_first(p); p; p = benc_next(p)) { | |||
count++; | |||
benc_dget_strz(p, "path", &path, NULL); | |||
printf("%s\n", path); | |||
free(path); | |||
btpd_connect(); | |||
for (int i = 0; i < argc -1; i++) | |||
handle_ipc_res(btpd_start_num(ipc, nums[i])); | |||
} | |||
void | |||
usage_stop(void) | |||
{ | |||
printf( | |||
"Deactivate torrents.\n" | |||
"\n" | |||
"Usage: stop num ...\n" | |||
"\n" | |||
"Arguments:\n" | |||
"num\n" | |||
"\tThe number of the torrent to deactivate.\n" | |||
"\n"); | |||
exit(1); | |||
} | |||
void | |||
cmd_stop(int argc, char **argv) | |||
{ | |||
if (argc < 2) | |||
usage_stop(); | |||
unsigned nums[argc - 1]; | |||
char *endptr; | |||
for (int i = 0; i < argc - 1; i++) { | |||
nums[i] = strtoul(argv[i + 1], &endptr, 10); | |||
if (strlen(argv[i + 1]) > endptr - argv[i + 1]) | |||
usage_stop(); | |||
} | |||
printf("%d torrent%s.\n", count, count == 1 ? "" : "s"); | |||
btpd_connect(); | |||
for (int i = 0; i < argc -1; i++) | |||
handle_ipc_res(btpd_stop_num(ipc, nums[i])); | |||
} | |||
static struct { | |||
const char *name; | |||
void (*fun)(int, char **); | |||
void (*help)(void); | |||
} cmd_table[] = { | |||
{ "add", cmd_add }, | |||
{ "del", cmd_del }, | |||
{ "die", cmd_die }, | |||
{ "list", cmd_list}, | |||
{ "stat", cmd_stat } | |||
{ "add", cmd_add, usage_add }, | |||
{ "del", cmd_del, usage_del }, | |||
{ "kill", cmd_kill, usage_kill }, | |||
{ "list", cmd_list, usage_list }, | |||
{ "start", cmd_start, usage_start }, | |||
{ "stat", cmd_stat, usage_stat }, | |||
{ "stop", cmd_stop, usage_stop } | |||
}; | |||
static int ncmds = sizeof(cmd_table) / sizeof(cmd_table[0]); | |||
void | |||
usage(void) | |||
{ | |||
printf( | |||
"btcli is the btpd command line interface. Use this tool to interact\n" | |||
"with a btpd process.\n" | |||
"\n" | |||
"Usage: btcli [main options] command [command options]\n" | |||
"\n" | |||
"Main options:\n" | |||
"-d dir\n" | |||
"\tThe btpd directory.\n" | |||
"\n" | |||
"--help [command]\n" | |||
"\tShow this text or help for the specified command.\n" | |||
"\n" | |||
"Commands:\n" | |||
"add\n" | |||
"del\n" | |||
"kill\n" | |||
"list\n" | |||
"start\n" | |||
"stat\n" | |||
"stop\n" | |||
"\n"); | |||
exit(1); | |||
} | |||
static struct option base_opts [] = { | |||
{ "help", no_argument, NULL, 1 }, | |||
{NULL, 0, NULL, 0} | |||
}; | |||
int | |||
main(int argc, char **argv) | |||
{ | |||
int ch, help = 0; | |||
if (argc < 2) | |||
usage(); | |||
while ((ch = getopt_long(argc, argv, "+d:", base_opts, NULL)) != -1) { | |||
switch (ch) { | |||
case 'd': | |||
btpd_dir = optarg; | |||
break; | |||
case 1: | |||
help = 1; | |||
break; | |||
default: | |||
usage(); | |||
} | |||
} | |||
argc -= optind; | |||
argv += optind; | |||
if (argc == 0) | |||
usage(); | |||
optind = 0; | |||
int found = 0; | |||
for (int i = 0; !found && i < ncmds; i++) { | |||
if (strcmp(argv[1], cmd_table[i].name) == 0) { | |||
if (strcmp(argv[0], cmd_table[i].name) == 0) { | |||
found = 1; | |||
cmd_table[i].fun(argc - 1, argv + 1); | |||
if (help) | |||
cmd_table[i].help(); | |||
else | |||
cmd_table[i].fun(argc, argv); | |||
} | |||
} | |||
if (!found) | |||
usage(); | |||
@@ -8,34 +8,42 @@ | |||
#include <unistd.h> | |||
#include "benc.h" | |||
#include "iobuf.h" | |||
#include "btpd_if.h" | |||
#include "iobuf.h" | |||
#include "subr.h" | |||
struct ipc { | |||
int sd; | |||
}; | |||
int | |||
ipc_open(const char *key, struct ipc **out) | |||
ipc_open(const char *dir, struct ipc **out) | |||
{ | |||
int sd = -1, err = 0; | |||
size_t plen; | |||
size_t keylen; | |||
struct ipc *res; | |||
struct sockaddr_un addr; | |||
if (key == NULL) | |||
key = "default"; | |||
keylen = strlen(key); | |||
for (int i = 0; i < keylen; i++) | |||
if (!isalnum(key[i])) | |||
return EINVAL; | |||
plen = sizeof(addr.sun_path); | |||
if (snprintf(addr.sun_path, plen, "%s/sock", dir) >= plen) | |||
return ENAMETOOLONG; | |||
addr.sun_family = AF_UNIX; | |||
res = malloc(sizeof(*res)); | |||
if (res == NULL) | |||
return ENOMEM; | |||
if ((sd = socket(PF_UNIX, SOCK_STREAM, 0)) == -1) | |||
return errno; | |||
plen = sizeof(res->addr.sun_path); | |||
if (snprintf(res->addr.sun_path, plen, | |||
"/tmp/btpd_%u_%s", geteuid(), key) >= plen) { | |||
free(res); | |||
return ENAMETOOLONG; | |||
if (connect(sd, (struct sockaddr *)&addr, sizeof(addr)) == -1) { | |||
err = errno; | |||
close(sd); | |||
return err; | |||
} | |||
res->addr.sun_family = AF_UNIX; | |||
if ((res = malloc(sizeof(*res))) == NULL) { | |||
close(sd); | |||
return ENOMEM; | |||
} | |||
res->sd = sd; | |||
*out = res; | |||
return 0; | |||
} | |||
@@ -43,179 +51,175 @@ ipc_open(const char *key, struct ipc **out) | |||
int | |||
ipc_close(struct ipc *ipc) | |||
{ | |||
int err; | |||
err = close(ipc->sd); | |||
free(ipc); | |||
return 0; | |||
} | |||
static int | |||
ipc_connect(struct ipc *ipc, FILE **out) | |||
{ | |||
FILE *fp; | |||
int sd; | |||
int error; | |||
if ((sd = socket(PF_UNIX, SOCK_STREAM, 0)) == -1) | |||
return errno; | |||
if (connect(sd, (struct sockaddr *)&ipc->addr, sizeof(ipc->addr)) == -1) | |||
goto error; | |||
if ((fp = fdopen(sd, "r+")) == NULL) | |||
goto error; | |||
*out = fp; | |||
return 0; | |||
error: | |||
error = errno; | |||
close(sd); | |||
return error; | |||
return err; | |||
} | |||
static int | |||
ipc_response(FILE *fp, char **out, uint32_t *len) | |||
ipc_response(struct ipc *ipc, char **out, uint32_t *len) | |||
{ | |||
uint32_t size; | |||
char *buf; | |||
if (fread(&size, sizeof(size), 1, fp) != 1) { | |||
if (ferror(fp)) | |||
return errno; | |||
else | |||
return ECONNRESET; | |||
} | |||
if ((errno = read_fully(ipc->sd, &size, sizeof(size))) != 0) | |||
return errno; | |||
if (size == 0) | |||
return EINVAL; | |||
return ECONNRESET; | |||
if ((buf = malloc(size)) == NULL) | |||
return ENOMEM; | |||
if (fread(buf, 1, size, fp) != size) { | |||
if (ferror(fp)) | |||
return errno; | |||
else | |||
return ECONNRESET; | |||
if ((errno = read_fully(ipc->sd, buf, size)) != 0) { | |||
free(buf); | |||
return errno; | |||
} | |||
*out = buf; | |||
*len = size; | |||
return 0; | |||
} | |||
static int | |||
ipc_req_res(struct ipc *ipc, | |||
const char *req, uint32_t qlen, | |||
char **res, uint32_t *rlen) | |||
ipc_req_res(struct ipc *ipc, const char *req, uint32_t qlen, char **res, | |||
uint32_t *rlen) | |||
{ | |||
FILE *fp; | |||
int error; | |||
if ((error = ipc_connect(ipc, &fp)) != 0) | |||
return error; | |||
if (fwrite(&qlen, sizeof(qlen), 1, fp) != 1) | |||
goto error; | |||
if (fwrite(req, 1, qlen, fp) != qlen) | |||
if ((errno = write_fully(ipc->sd, &qlen, sizeof(qlen))) != 0) | |||
goto error; | |||
if (fflush(fp) != 0) | |||
if ((errno = write_fully(ipc->sd, req, qlen)) != 0) | |||
goto error; | |||
if ((errno = ipc_response(fp, res, rlen)) != 0) | |||
if ((errno = ipc_response(ipc, res, rlen)) != 0) | |||
goto error; | |||
if ((errno = benc_validate(*res, *rlen)) != 0) | |||
goto error; | |||
fclose(fp); | |||
return 0; | |||
if (!benc_isdct(*res)) | |||
errno = EINVAL; | |||
error: | |||
error = errno; | |||
fclose(fp); | |||
return error; | |||
return errno; | |||
} | |||
int | |||
btpd_die(struct ipc *ipc) | |||
static enum ipc_code | |||
ipc_buf_req(struct ipc *ipc, struct io_buffer *iob) | |||
{ | |||
int error; | |||
char *response = NULL; | |||
const char shutdown[] = "l3:diee"; | |||
uint32_t size = sizeof(shutdown) - 1; | |||
uint32_t rsiz; | |||
if ((error = ipc_req_res(ipc, shutdown, size, &response, &rsiz)) != 0) | |||
return error; | |||
error = benc_validate(response, rsiz); | |||
if (error == 0) { | |||
int64_t tmp; | |||
benc_dget_int64(response, "code", &tmp); | |||
error = tmp; | |||
} | |||
free(response); | |||
return error; | |||
int err; | |||
char *res; | |||
size_t reslen; | |||
err = ipc_req_res(ipc, iob->buf, iob->buf_off, &res, &reslen); | |||
free(iob->buf); | |||
if (err != 0) | |||
return IPC_COMMERR; | |||
int code; | |||
code = benc_dget_int(res, "code"); | |||
free(res); | |||
return code; | |||
} | |||
int | |||
btpd_add(struct ipc *ipc, char **paths, unsigned npaths, char **out) | |||
enum ipc_code | |||
btpd_die(struct ipc *ipc, int seconds) | |||
{ | |||
int error; | |||
struct io_buffer iob; | |||
char *res = NULL; | |||
uint32_t reslen; | |||
buf_init(&iob, 16); | |||
if (seconds >= 0) | |||
buf_print(&iob, "l3:diei%dee", seconds); | |||
else | |||
buf_print(&iob, "l3:diee"); | |||
return ipc_buf_req(ipc, &iob); | |||
} | |||
buf_init(&iob, 1024); | |||
buf_print(&iob, "l3:add"); | |||
for (unsigned i = 0; i < npaths; i++) { | |||
int plen = strlen(paths[i]); | |||
buf_print(&iob, "%d:", plen); | |||
buf_write(&iob, paths[i], plen); | |||
enum ipc_code | |||
parse_btstat(const uint8_t *res, struct btstat **out) | |||
{ | |||
int code; | |||
unsigned ntorrents; | |||
const char *tlst; | |||
code = benc_dget_int(res, "code"); | |||
if (code != IPC_OK) | |||
return code; | |||
ntorrents = benc_dget_int(res, "ntorrents"); | |||
tlst = benc_dget_lst(res, "torrents"); | |||
struct btstat *st = | |||
malloc(sizeof(struct btstat) + sizeof(struct tpstat) * ntorrents); | |||
st->ntorrents = ntorrents; | |||
int i = 0; | |||
for (const char *tp = benc_first(tlst); tp != NULL; tp = benc_next(tp)) { | |||
struct tpstat *ts = &st->torrents[i]; | |||
ts->num = benc_dget_int(tp, "num"); | |||
ts->name = benc_dget_str(tp, "path", NULL); | |||
ts->state = *benc_dget_str(tp, "state", NULL); | |||
if (ts->state == 'A') { | |||
ts->errors = benc_dget_int(tp, "errors"); | |||
ts->npieces = benc_dget_int(tp, "npieces"); | |||
ts->nseen = benc_dget_int(tp, "seen npieces"); | |||
ts->npeers = benc_dget_int(tp, "npeers"); | |||
ts->downloaded = benc_dget_int(tp, "downloaded"); | |||
ts->uploaded = benc_dget_int(tp, "uploaded"); | |||
ts->rate_down = benc_dget_int(tp, "rd"); | |||
ts->rate_up = benc_dget_int(tp, "ru"); | |||
ts->have = benc_dget_int(tp, "have"); | |||
ts->total = benc_dget_int(tp, "total"); | |||
} | |||
i++; | |||
} | |||
buf_print(&iob, "e"); | |||
error = ipc_req_res(ipc, iob.buf, iob.buf_off, &res, &reslen); | |||
free(iob.buf); | |||
if (error == 0) | |||
*out = res; | |||
*out = st; | |||
return IPC_OK; | |||
} | |||
return error; | |||
void | |||
free_btstat(struct btstat *st) | |||
{ | |||
for (unsigned i = 0; i < st->ntorrents; i++) | |||
if (st->torrents[i].name != NULL) | |||
free(st->torrents[i].name); | |||
free(st); | |||
} | |||
int | |||
btpd_stat(struct ipc *ipc, char **out) | |||
enum ipc_code | |||
btpd_stat(struct ipc *ipc, struct btstat **out) | |||
{ | |||
int err; | |||
const char cmd[] = "l4:state"; | |||
uint32_t cmdlen = sizeof(cmd) - 1; | |||
char *res; | |||
uint32_t reslen; | |||
if ((errno = ipc_req_res(ipc, cmd, cmdlen, &res, &reslen)) != 0) | |||
return errno; | |||
*out = res; | |||
return 0; | |||
if ((err = ipc_req_res(ipc, cmd, cmdlen, &res, &reslen)) != 0) | |||
return IPC_COMMERR; | |||
err = parse_btstat(res, out); | |||
free(res); | |||
return err; | |||
} | |||
int | |||
btpd_del(struct ipc *ipc, uint8_t (*hash)[20], unsigned nhashes, char **out) | |||
static enum ipc_code | |||
btpd_common_num(struct ipc *ipc, const char *cmd, unsigned num) | |||
{ | |||
int error; | |||
struct io_buffer iob; | |||
char *res = NULL; | |||
uint32_t reslen; | |||
buf_init(&iob, 16); | |||
buf_print(&iob, "l%d:%si%uee", (int)strlen(cmd), cmd, num); | |||
return ipc_buf_req(ipc, &iob); | |||
} | |||
buf_init(&iob, 1024); | |||
buf_write(&iob, "l3:del", 6); | |||
for (unsigned i = 0; i < nhashes; i++) { | |||
buf_write(&iob, "20:", 3); | |||
buf_write(&iob, hash[i], 20); | |||
} | |||
buf_write(&iob, "e", 1); | |||
enum ipc_code | |||
btpd_del_num(struct ipc *ipc, unsigned num) | |||
{ | |||
return btpd_common_num(ipc, "del", num); | |||
} | |||
error = ipc_req_res(ipc, iob.buf, iob.buf_off, &res, &reslen); | |||
free(iob.buf); | |||
if (error != 0) | |||
return error; | |||
enum ipc_code | |||
btpd_start_num(struct ipc *ipc, unsigned num) | |||
{ | |||
return btpd_common_num(ipc, "start", num); | |||
} | |||
*out = res; | |||
return 0; | |||
enum ipc_code | |||
btpd_stop_num(struct ipc *ipc, unsigned num) | |||
{ | |||
return btpd_common_num(ipc, "stop", num); | |||
} |
@@ -5,17 +5,40 @@ | |||
#include <sys/socket.h> | |||
#include <sys/un.h> | |||
struct ipc { | |||
struct sockaddr_un addr; | |||
struct ipc; | |||
enum ipc_code { | |||
IPC_OK, | |||
IPC_FAIL, | |||
IPC_COMMERR | |||
}; | |||
struct btstat { | |||
unsigned ntorrents; | |||
struct tpstat { | |||
char *name; | |||
unsigned num; | |||
char state; | |||
unsigned errors; | |||
unsigned npeers; | |||
uint32_t npieces, nseen; | |||
off_t have, total; | |||
long long downloaded, uploaded; | |||
unsigned long rate_up, rate_down; | |||
} torrents[]; | |||
}; | |||
int ipc_open(const char *key, struct ipc **out); | |||
int ipc_open(const char *dir, struct ipc **out); | |||
int ipc_close(struct ipc *ipc); | |||
int btpd_add(struct ipc *ipc, char **path, unsigned npaths, char **out); | |||
int btpd_del(struct ipc *ipc, uint8_t (*hash)[20], | |||
unsigned nhashes, char **out); | |||
int btpd_die(struct ipc *ipc); | |||
int btpd_stat(struct ipc *ipc, char **out); | |||
enum ipc_code btpd_die(struct ipc *ipc, int seconds); | |||
enum ipc_code btpd_stat(struct ipc *ipc, struct btstat **out); | |||
enum ipc_code btpd_del_num(struct ipc *ipc, unsigned num); | |||
enum ipc_code btpd_start_num(struct ipc *ipc, unsigned num); | |||
enum ipc_code btpd_stop_num(struct ipc *ipc, unsigned num); | |||
void free_btstat(struct btstat *stat); | |||
#endif |
@@ -2,6 +2,7 @@ | |||
#include <ctype.h> | |||
#include <errno.h> | |||
#include <inttypes.h> | |||
#include <stdarg.h> | |||
#include <stdlib.h> | |||
#include <string.h> | |||
@@ -99,7 +100,7 @@ benc_length(const char *p) | |||
; | |||
return next - p + 1; | |||
default: | |||
assert(benc_str(p, &next, &blen, NULL) == 0); | |||
assert((next = benc_mem(p, &blen, NULL)) != NULL); | |||
return next - p + blen; | |||
} | |||
} | |||
@@ -127,11 +128,12 @@ benc_next(const char *p) | |||
return *(p + blen) == 'e' ? NULL : p + blen; | |||
} | |||
int | |||
benc_str(const char *p, const char **out, size_t *len, const char**next) | |||
const char * | |||
benc_mem(const char *p, size_t *len, const char**next) | |||
{ | |||
if (!benc_isstr(p)) | |||
return NULL; | |||
size_t blen = 0; | |||
assert(isdigit(*p)); | |||
blen = *p - '0'; | |||
p++; | |||
while (isdigit(*p)) { | |||
@@ -141,53 +143,49 @@ benc_str(const char *p, const char **out, size_t *len, const char**next) | |||
} | |||
assert(*p == ':'); | |||
benc_safeset(len, blen); | |||
benc_safeset(out, p + 1); | |||
benc_safeset(next, *(p + blen + 1) == 'e' ? NULL : p + blen + 1); | |||
return 0; | |||
return p + 1; | |||
} | |||
int | |||
benc_strz(const char *p, char **out, size_t *len, const char **next) | |||
char * | |||
benc_str(const char *p, size_t *len, const char **next) | |||
{ | |||
int err; | |||
size_t blen; | |||
const char *bstr; | |||
if ((err = benc_str(p, &bstr, &blen, next)) == 0) { | |||
if ((*out = malloc(blen + 1)) != NULL) { | |||
memcpy(*out, bstr, blen); | |||
(*out)[blen] = '\0'; | |||
benc_safeset(len, blen); | |||
} else | |||
err = ENOMEM; | |||
} | |||
return err; | |||
char *ret; | |||
if ((bstr = benc_mem(p, &blen, next)) == NULL) | |||
return NULL; | |||
if ((ret = malloc(blen + 1)) == NULL) | |||
return NULL; | |||
bcopy(bstr, ret, blen); | |||
ret[blen] = '\0'; | |||
benc_safeset(len, blen); | |||
return ret; | |||
} | |||
int | |||
benc_stra(const char *p, char **out, size_t *len, const char **next) | |||
char * | |||
benc_mema(const char *p, size_t *len, const char **next) | |||
{ | |||
int err; | |||
size_t blen; | |||
const char *bstr; | |||
if ((err = benc_str(p, &bstr, &blen, next)) == 0) { | |||
if ((*out = malloc(blen)) != NULL) { | |||
memcpy(*out, bstr, blen); | |||
benc_safeset(len, blen); | |||
} else | |||
err = ENOMEM; | |||
} | |||
return err; | |||
char *ret; | |||
if ((bstr = benc_mem(p, &blen, next)) == NULL) | |||
return NULL; | |||
if ((ret = malloc(blen)) == NULL) | |||
return NULL; | |||
bcopy(bstr, ret, blen); | |||
benc_safeset(len, blen); | |||
return ret; | |||
} | |||
int | |||
benc_int64(const char *p, int64_t *out, const char **next) | |||
long long | |||
benc_int(const char *p, const char **next) | |||
{ | |||
int sign = 1; | |||
int64_t res = 0; | |||
long long res = 0; | |||
assert(*p == 'i'); | |||
if (!benc_isint(p)) | |||
return 0; | |||
p++; | |||
if (*p == '-') { | |||
sign = -1; | |||
@@ -202,122 +200,79 @@ benc_int64(const char *p, int64_t *out, const char **next) | |||
p++; | |||
} | |||
assert(*p == 'e'); | |||
benc_safeset(out, res); | |||
benc_safeset(next, *(p + 1) == 'e' ? NULL : p + 1); | |||
return 0; | |||
} | |||
int | |||
benc_uint32(const char *p, uint32_t *out, const char **next) | |||
{ | |||
int err; | |||
int64_t res; | |||
if ((err = benc_int64(p, &res, next)) == 0) { | |||
if (res >= 0 && res <= 0xffffffffUL) | |||
*out = (uint32_t)res; | |||
else | |||
err = EINVAL; | |||
} | |||
return err; | |||
benc_safeset(next, *(p + 1) == 'e' ? NULL : p + 1); | |||
return res; | |||
} | |||
int | |||
benc_dget_any(const char *p, const char *key, const char **val) | |||
const char * | |||
benc_dget_any(const char *p, const char *key) | |||
{ | |||
int res; | |||
int cmp; | |||
size_t len, blen; | |||
const char *bstr; | |||
assert(benc_isdct(p)); | |||
if (!benc_isdct(p)) | |||
return NULL; | |||
len = strlen(key); | |||
p = benc_first(p); | |||
while (p != NULL) { | |||
if ((res = benc_str(p, &bstr, &blen, &p)) != 0) | |||
return res; | |||
res = strncmp(bstr, key, blen); | |||
if (res == 0 && len == blen) { | |||
*val = p; | |||
return 0; | |||
} else if (res <= 0) { | |||
if (!benc_isstr(p)) | |||
return NULL; | |||
bstr = benc_mem(p, &blen, &p); | |||
cmp = strncmp(bstr, key, blen); | |||
if (cmp == 0 && len == blen) | |||
return p; | |||
else if (cmp <= 0) | |||
p = benc_next(p); | |||
} else | |||
return ENOENT; | |||
else | |||
return NULL; | |||
} | |||
return ENOENT; | |||
return NULL; | |||
} | |||
int | |||
benc_dget_lst(const char *p, const char *key, const char **val) | |||
{ | |||
int err; | |||
if ((err = benc_dget_any(p, key, val)) == 0) | |||
if (!benc_islst(*val)) | |||
err = EINVAL; | |||
return err; | |||
} | |||
int | |||
benc_dget_dct(const char *p, const char *key, const char **val) | |||
const char * | |||
benc_dget_lst(const char *p, const char *key) | |||
{ | |||
int err; | |||
if ((err = benc_dget_any(p, key, val)) == 0) | |||
if (!benc_isdct(*val)) | |||
err = EINVAL; | |||
return err; | |||
const char *ret = benc_dget_any(p, key); | |||
return ret != NULL && benc_islst(ret) ? ret : NULL; | |||
} | |||
int | |||
benc_dget_str(const char *p, const char *key, const char **val, size_t *len) | |||
const char * | |||
benc_dget_dct(const char *p, const char *key) | |||
{ | |||
int err; | |||
const char *sp; | |||
if ((err = benc_dget_any(p, key, &sp)) == 0) | |||
err = benc_isstr(sp) ? benc_str(sp, val, len, NULL) : EINVAL; | |||
return err; | |||
const char *ret = benc_dget_any(p, key); | |||
return ret != NULL && benc_isdct(ret) ? ret : NULL; | |||
} | |||
int | |||
benc_dget_stra(const char *p, const char *key, char **val, size_t *len) | |||
const char * | |||
benc_dget_mem(const char *p, const char *key, size_t *len) | |||
{ | |||
int err; | |||
const char *sp; | |||
if ((err = benc_dget_any(p, key, &sp)) == 0) | |||
err = benc_isstr(sp) ? benc_stra(sp, val, len, NULL) : EINVAL; | |||
return err; | |||
const char *str = benc_dget_any(p, key); | |||
return str != NULL && benc_isstr(str) ? benc_mem(str, len, NULL) : NULL; | |||
} | |||
int | |||
benc_dget_strz(const char *p, const char *key, char **val, size_t *len) | |||
char * | |||
benc_dget_mema(const char *p, const char *key, size_t *len) | |||
{ | |||
int err; | |||
const char *sp; | |||
if ((err = benc_dget_any(p, key, &sp)) == 0) | |||
err = benc_isstr(sp) ? benc_strz(sp, val, len, NULL) : EINVAL; | |||
return err; | |||
const char *str = benc_dget_any(p, key); | |||
return str != NULL && benc_isstr(str) ? benc_mema(str, len, NULL) : NULL; | |||
} | |||
int | |||
benc_dget_int64(const char *p, const char *key, int64_t *val) | |||
char * | |||
benc_dget_str(const char *p, const char *key, size_t *len) | |||
{ | |||
int err; | |||
const char *ip; | |||
if ((err = benc_dget_any(p, key, &ip)) == 0) | |||
err = benc_isint(ip) ? benc_int64(ip, val, NULL) : EINVAL; | |||
return err; | |||
const char *str = benc_dget_any(p, key); | |||
return str != NULL && benc_isstr(str) ? benc_str(str, len, NULL) : NULL; | |||
} | |||
int | |||
benc_dget_uint32(const char *p, const char *key, uint32_t *val) | |||
long long | |||
benc_dget_int(const char *p, const char *key) | |||
{ | |||
int err; | |||
const char *ip; | |||
if ((err = benc_dget_any(p, key, &ip)) == 0) | |||
err = benc_isint(ip) ? benc_uint32(ip, val, NULL) : EINVAL; | |||
return err; | |||
const char *intp = benc_dget_any(p, key); | |||
return intp != NULL && benc_isint(intp) ? benc_int(intp, NULL) : 0; | |||
} | |||
int | |||
@@ -343,3 +298,131 @@ benc_isstr(const char *p) | |||
{ | |||
return isdigit(*p); | |||
} | |||
int | |||
benc_istype(const char *p, enum be_type type) | |||
{ | |||
switch (type) { | |||
case BE_ANY: | |||
return benc_isdct(p) || benc_isint(p) || | |||
benc_islst(p) || benc_isstr(p); | |||
case BE_DCT: | |||
return benc_isdct(p); | |||
case BE_INT: | |||
return benc_isint(p); | |||
case BE_LST: | |||
return benc_islst(p); | |||
case BE_STR: | |||
return benc_isstr(p); | |||
default: | |||
abort(); | |||
} | |||
} | |||
int | |||
benc_dct_chk(const char *p, int count, ...) | |||
{ | |||
int i, ok = 1; | |||
va_list ap; | |||
if (!benc_isdct(p)) | |||
ok = 0; | |||
va_start(ap, count); | |||
for (i = 0; ok && i < count; i++) { | |||
enum be_type type = va_arg(ap, enum be_type); | |||
int level = va_arg(ap, int); | |||
const char *dct = p; | |||
const char *key = va_arg(ap, const char *); | |||
while (ok && level > 1) { | |||
if ((dct = benc_dget_dct(dct, key)) != NULL) { | |||
level--; | |||
key = va_arg(ap, const char *); | |||
} else | |||
ok = 0; | |||
} | |||
if (ok) { | |||
const char *val = benc_dget_any(dct, key); | |||
if (val == NULL || !benc_istype(val, type)) | |||
ok = 0; | |||
} | |||
} | |||
va_end(ap); | |||
return ok; | |||
} | |||
#if 0 | |||
int | |||
benc_dct_type_check(const char *p, int count, ...) | |||
{ | |||
int i; | |||
va_list ap; | |||
benc_validate_dct(p, 2, BE_INT, "code", BE_STR, "hash", | |||
if (!benc_isdct(p)) | |||
return EINVAL; | |||
va_start(ap, count); | |||
for (i = 0; i < count; i++) { | |||
} | |||
} | |||
int | |||
benc_dget_many(const char *p, int count, ...) | |||
{ | |||
int i; | |||
va_list ap; | |||
if (!benc_isdct(p)) | |||
return 0; | |||
va_start(ap, count); | |||
for (i = 0; i < count; i++) { | |||
const char *name = va_arg(ap, const char *); | |||
enum be_type type = va_arg(ap, enum be_type); | |||
int64_t *iret; | |||
size_t *lret; | |||
const char **mret; | |||
char **aret; | |||
switch (type) { | |||
case BE_INT: | |||
iret = va_arg(ap, int64_t *); | |||
if (benc_dget_int64(p, name, iret) != 0) | |||
goto out; | |||
break; | |||
case BE_LST: | |||
mret = va_arg(ap, const char **); | |||
lret = va_arg(ap, size_t *); | |||
if (benc_dget_lst(p, name, mret) != 0) | |||
goto out; | |||
if (lret != NULL) | |||
*lret = benc_nelems(*mret); | |||
break; | |||
case BE_DCT: | |||
mret = va_arg(ap, const char **); | |||
if (benc_dget_dct(p, name, mret) != 0) | |||
goto out; | |||
break; | |||
case BE_MEM: | |||
mret = va_arg(ap, const char **); | |||
lret = va_arg(ap, size_t *); | |||
if (benc_dget_str(p, name, mret, lret) != 0) | |||
goto out; | |||
break; | |||
case BE_STRZ: | |||
aret = va_arg(ap, char **); | |||
lret = va_arg(ap, size_t *); | |||
if (benc_dget_strz(p, name, aret, lret) != 0) | |||
goto out; | |||
break; | |||
default: | |||
abort(); | |||
} | |||
} | |||
out: | |||
va_end(ap); | |||
return i; | |||
} | |||
#endif |
@@ -1,7 +1,21 @@ | |||
#ifndef BTPD_BENC_H | |||
#define BTPD_BENC_H | |||
enum be_type { | |||
BE_ANY, | |||
BE_DCT, | |||
BE_INT, | |||
BE_LST, | |||
BE_STR | |||
}; | |||
int benc_validate(const char *p, size_t len); | |||
int benc_dct_chk(const char *p, int count, ...); | |||
int benc_islst(const char *p); | |||
int benc_isdct(const char *p); | |||
int benc_isint(const char *p); | |||
int benc_isstr(const char *p); | |||
size_t benc_length(const char *p); | |||
size_t benc_nelems(const char *p); | |||
@@ -9,29 +23,17 @@ size_t benc_nelems(const char *p); | |||
const char *benc_first(const char *p); | |||
const char *benc_next(const char *p); | |||
int benc_str(const char *p, const char **mem, size_t *len, const char**next); | |||
int benc_stra(const char *p, char **out, size_t *len, const char **next); | |||
int benc_strz(const char *p, char **out, size_t *len, const char **next); | |||
int benc_int64(const char *p, int64_t *out, const char **next); | |||
int benc_uint32(const char *p, uint32_t *out, const char **next); | |||
#define benc_off benc_int64 | |||
int benc_dget_any(const char *p, const char *key, const char **val); | |||
int benc_dget_lst(const char *p, const char *key, const char **val); | |||
int benc_dget_dct(const char *p, const char *key, const char **val); | |||
int benc_dget_str(const char *p, const char *key, | |||
const char **val, size_t *len); | |||
int benc_dget_stra(const char *p, const char *key, char **val, size_t *len); | |||
int benc_dget_strz(const char *p, const char *key, char **val, size_t *len); | |||
int benc_dget_int64(const char *p, const char *key, int64_t *val); | |||
int benc_dget_uint32(const char *p, const char *key, uint32_t *val); | |||
#define benc_dget_off benc_dget_int64 | |||
int benc_islst(const char *p); | |||
int benc_isdct(const char *p); | |||
int benc_isint(const char *p); | |||
int benc_isstr(const char *p); | |||
long long benc_int(const char *p, const char **next); | |||
const char *benc_mem(const char *p, size_t *len, const char **next); | |||
char *benc_mema(const char *p, size_t *len, const char **next); | |||
char *benc_str(const char *p, size_t *len, const char **next); | |||
const char *benc_dget_any(const char *p, const char *key); | |||
const char *benc_dget_lst(const char *p, const char *key); | |||
const char *benc_dget_dct(const char *p, const char *key); | |||
long long benc_dget_int(const char *p, const char *key); | |||
const char *benc_dget_mem(const char *p, const char *key, size_t *len); | |||
char *benc_dget_mema(const char *p, const char *key, size_t *len); | |||
char *benc_dget_str(const char *p, const char *key, size_t *len); | |||
#endif |
@@ -69,22 +69,22 @@ check_path(const char *path, size_t len) | |||
int | |||
fill_fileinfo(const char *fdct, struct fileinfo *tfp) | |||
{ | |||
int err; | |||
//int err; | |||
size_t npath, plen, len; | |||
const char *plst, *iter, *str; | |||
if ((err = benc_dget_off(fdct, "length", &tfp->length)) != 0) | |||
return err; | |||
if (!benc_dct_chk(fdct, 2, BE_INT, 1, "length", BE_LST, 1, "path")) | |||
return EINVAL; | |||
if ((err = benc_dget_lst(fdct, "path", &plst)) != 0) | |||
return err; | |||
tfp->length = benc_dget_int(fdct, "length"); | |||
plst = benc_dget_lst(fdct, "path"); | |||
npath = plen = 0; | |||
iter = benc_first(plst); | |||
while (iter != NULL) { | |||
if (!benc_isstr(iter)) | |||
return EINVAL; | |||
benc_str(iter, &str, &len, &iter); | |||
str = benc_mem(iter, &len, &iter); | |||
if (!check_path(str, len)) | |||
return EINVAL; | |||
npath++; | |||
@@ -97,13 +97,13 @@ fill_fileinfo(const char *fdct, struct fileinfo *tfp) | |||
return ENOMEM; | |||
iter = benc_first(plst); | |||
benc_str(iter, &str, &len, &iter); | |||
str = benc_mem(iter, &len, &iter); | |||
memcpy(tfp->path, str, len); | |||
plen = len; | |||
npath--; | |||
while (npath > 0) { | |||
tfp->path[plen++] = '/'; | |||
benc_str(iter, &str, &len, &iter); | |||
str = benc_mem(iter, &len, &iter); | |||
memcpy(tfp->path + plen, str, len); | |||
plen += len; | |||
npath--; | |||
@@ -135,35 +135,25 @@ int | |||
fill_metainfo(const char *bep, struct metainfo *tp, int mem_hashes) | |||
{ | |||
size_t len; | |||
int err; | |||
int err = 0; | |||
const char *base_addr = bep; | |||
const char *hash_addr; | |||
if (!benc_isdct(bep)) | |||
if (!benc_dct_chk(bep, 5, | |||
BE_STR, 1, "announce", | |||
BE_DCT, 1, "info", | |||
BE_INT, 2, "info", "piece length", | |||
BE_STR, 2, "info", "pieces", | |||
BE_STR, 2, "info", "name")) | |||
return EINVAL; | |||
if ((err = benc_dget_strz(bep, "announce", &tp->announce, NULL)) != 0) | |||
goto out; | |||
if ((err = benc_dget_dct(bep, "info", &bep)) != 0) | |||
goto out; | |||
tp->announce = benc_dget_str(bep, "announce", NULL); | |||
bep = benc_dget_dct(bep, "info"); | |||
SHA1(bep, benc_length(bep), tp->info_hash); | |||
if ((err = benc_dget_off(bep, "piece length", &tp->piece_length)) != 0) | |||
goto out; | |||
if ((err = benc_dget_str(bep, "pieces", &hash_addr, &len)) != 0) | |||
goto out; | |||
if (len % 20 != 0) { | |||
err = EINVAL; | |||
goto out; | |||
} | |||
tp->piece_length = benc_dget_int(bep, "piece length"); | |||
hash_addr = benc_dget_mem(bep, "pieces", &len); | |||
tp->npieces = len / 20; | |||
tp->pieces_off = hash_addr - base_addr; | |||
if (mem_hashes) { | |||
if ((tp->piece_hash = malloc(len)) == NULL) { | |||
err = ENOMEM; | |||
@@ -171,12 +161,10 @@ fill_metainfo(const char *bep, struct metainfo *tp, int mem_hashes) | |||
} | |||
bcopy(hash_addr, tp->piece_hash, len); | |||
} | |||
tp->name = benc_dget_str(bep, "name", NULL); | |||
if ((err = benc_dget_strz(bep, "name", &tp->name, NULL)) != 0) | |||
goto out; | |||
err = benc_dget_off(bep, "length", &tp->total_length); | |||
if (err == 0) { | |||
if (benc_dct_chk(bep, 1, BE_INT, 1, "length")) { | |||
tp->total_length = benc_dget_int(bep, "length"); | |||
tp->nfiles = 1; | |||
tp->files = calloc(1, sizeof(struct fileinfo)); | |||
if (tp->files != NULL) { | |||
@@ -190,14 +178,11 @@ fill_metainfo(const char *bep, struct metainfo *tp, int mem_hashes) | |||
err = ENOMEM; | |||
goto out; | |||
} | |||
} | |||
else if (err == ENOENT) { | |||
} else if (benc_dct_chk(bep, 1, BE_LST, 1, "files")) { | |||
int i; | |||
const char *flst, *fdct; | |||
if ((err = benc_dget_lst(bep, "files", &flst)) != 0) | |||
goto out; | |||
flst = benc_dget_lst(bep, "files"); | |||
tp->nfiles = benc_nelems(flst); | |||
if (tp->nfiles < 1) { | |||
err = EINVAL; | |||
@@ -258,7 +243,6 @@ load_metainfo(const char *path, off_t size, int mem_hashes, | |||
if (err == 0) | |||
if ((*res = calloc(1, sizeof(**res))) == NULL) | |||
err = ENOMEM; | |||
if (err == 0) | |||
if ((err = fill_metainfo(buf, *res, mem_hashes)) != 0) | |||
free(*res); | |||