o Lot of work on the cli and its communication with btpd.master
@@ -12,58 +12,80 @@ | |||||
#include <unistd.h> | #include <unistd.h> | ||||
#include "btpd.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) | #define buf_swrite(iob, s) buf_write(iob, s, sizeof(s) - 1) | ||||
static struct event m_cli_incoming; | 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 torrent *tp; | ||||
struct io_buffer iob; | 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) { | 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; | 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); | free(iob.buf); | ||||
return 0; | |||||
} | } | ||||
#if 0 | #if 0 | ||||
@@ -71,9 +93,9 @@ static void | |||||
cmd_add(int argc, const char *args, FILE *fp) | cmd_add(int argc, const char *args, FILE *fp) | ||||
{ | { | ||||
struct io_buffer iob; | 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) { | while (args != NULL) { | ||||
size_t plen; | size_t plen; | ||||
char path[PATH_MAX]; | char path[PATH_MAX]; | ||||
@@ -87,16 +109,16 @@ cmd_add(int argc, const char *args, FILE *fp) | |||||
benc_str(args, &pathp, &plen, &args); | benc_str(args, &pathp, &plen, &args); | ||||
if (plen >= PATH_MAX) { | if (plen >= PATH_MAX) { | ||||
errdie(buf_print(&iob, "d4:codei%dee", ENAMETOOLONG)); | |||||
buf_print(&iob, "d4:codei%dee", ENAMETOOLONG); | |||||
continue; | continue; | ||||
} | } | ||||
bcopy(pathp, path, plen); | bcopy(pathp, path, plen); | ||||
path[plen] = '\0'; | path[plen] = '\0'; | ||||
btpd_log(BTPD_L_BTPD, "add request for %s.\n", path); | 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; | uint32_t len = iob.buf_off; | ||||
fwrite(&len, sizeof(len), 1, fp); | fwrite(&len, sizeof(len), 1, fp); | ||||
@@ -108,9 +130,9 @@ static void | |||||
cmd_del(int argc, const char *args, FILE *fp) | cmd_del(int argc, const char *args, FILE *fp) | ||||
{ | { | ||||
struct io_buffer iob; | 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) { | while (args != NULL) { | ||||
size_t len; | size_t len; | ||||
@@ -127,13 +149,13 @@ cmd_del(int argc, const char *args, FILE *fp) | |||||
if (tp != NULL) { | if (tp != NULL) { | ||||
btpd_log(BTPD_L_BTPD, "del request for %s.\n", tp->relpath); | btpd_log(BTPD_L_BTPD, "del request for %s.\n", tp->relpath); | ||||
torrent_unload(tp); | torrent_unload(tp); | ||||
errdie(buf_swrite(&iob, "d4:codei0ee")); | |||||
buf_swrite(&iob, "d4:codei0ee"); | |||||
} else { | } else { | ||||
btpd_log(BTPD_L_BTPD, "del request didn't match.\n"); | 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; | uint32_t len = iob.buf_off; | ||||
fwrite(&len, sizeof(len), 1, fp); | fwrite(&len, sizeof(len), 1, fp); | ||||
@@ -141,78 +163,144 @@ cmd_del(int argc, const char *args, FILE *fp) | |||||
free(iob.buf); | 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"; | char res[] = "d4:codei0ee"; | ||||
uint32_t len = sizeof(res) - 1; | 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_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 { | static struct { | ||||
const char *name; | const char *name; | ||||
int nlen; | int nlen; | ||||
void (*fun)(int, const char *, FILE *); | |||||
int (*fun)(struct cli *cli, int, const char *); | |||||
} cmd_table[] = { | } cmd_table[] = { | ||||
#if 0 | #if 0 | ||||
{ "add", 3, cmd_add }, | { "add", 3, cmd_add }, | ||||
{ "del", 3, cmd_del }, | { "del", 3, cmd_del }, | ||||
{ "die", 3, cmd_die }, | |||||
#endif | #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 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; | size_t cmdlen; | ||||
const char *cmd; | const char *cmd; | ||||
const char *args; | 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 | 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 | void | ||||
client_connection_cb(int sd, short type, void *arg) | client_connection_cb(int sd, short type, void *arg) | ||||
{ | { | ||||
int nsd; | int nsd; | ||||
FILE *fp; | |||||
if ((nsd = accept(sd, NULL, NULL)) < 0) { | if ((nsd = accept(sd, NULL, NULL)) < 0) { | ||||
if (errno == EWOULDBLOCK || errno == ECONNABORTED) | if (errno == EWOULDBLOCK || errno == ECONNABORTED) | ||||
@@ -224,14 +312,10 @@ client_connection_cb(int sd, short type, void *arg) | |||||
if ((errno = set_blocking(nsd)) != 0) | if ((errno = set_blocking(nsd)) != 0) | ||||
btpd_err("set_blocking: %s.\n", strerror(errno)); | 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 | void | ||||
@@ -40,15 +40,12 @@ static void tr_send(struct torrent *tp, enum tr_event event); | |||||
void | void | ||||
maybe_connect_to(struct torrent *tp, const char *pinfo) | 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; | 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; | return; | ||||
if (bcmp(btpd_get_peer_id(), pid, 20) == 0) | 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)) | if (net_torrent_has_peer(tp->net, pid)) | ||||
return; | 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); | peer_create_out(tp->net, pid, ip, port); | ||||
out: | |||||
if (ip != NULL) | if (ip != NULL) | ||||
free(ip); | free(ip); | ||||
} | } | ||||
static int | static int | ||||
parse_reply(struct torrent *tp, const char *content, size_t size) | parse_reply(struct torrent *tp, const char *content, size_t size) | ||||
{ | { | ||||
char *buf; | |||||
const char *buf; | |||||
size_t len; | |||||
const char *peers; | 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; | 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; | 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); | for (peers = benc_first(peers); | ||||
peers != NULL && net_npeers < net_max_peers; | peers != NULL && net_npeers < net_max_peers; | ||||
peers = benc_next(peers)) | peers = benc_next(peers)) | ||||
maybe_connect_to(tp, 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; | return 0; | ||||
bad_data: | |||||
btpd_log(BTPD_L_ERROR, "Bad data from tracker.\n"); | |||||
return 1; | |||||
} | } | ||||
static void | static void | ||||
@@ -1,587 +1,422 @@ | |||||
#include <sys/types.h> | |||||
#include <sys/time.h> | |||||
#include <err.h> | #include <err.h> | ||||
#include <errno.h> | #include <errno.h> | ||||
#include <fcntl.h> | |||||
#include <getopt.h> | #include <getopt.h> | ||||
#include <inttypes.h> | |||||
#include <math.h> | |||||
#include <stdio.h> | #include <stdio.h> | ||||
#include <stdlib.h> | #include <stdlib.h> | ||||
#include <string.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" | #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 | 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; | 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 | 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) | 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) | 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: | 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; | 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) | cmd_stat(int argc, char **argv) | ||||
{ | { | ||||
int ch; | 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) { | while ((ch = getopt_long(argc, argv, "iw:", stat_opts, NULL)) != -1) { | ||||
switch (ch) { | switch (ch) { | ||||
case 'i': | case 'i': | ||||
iflag = 1; | iflag = 1; | ||||
break; | break; | ||||
case 'w': | 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; | break; | ||||
default: | default: | ||||
usage(); | |||||
usage_stat(); | |||||
} | } | ||||
} | } | ||||
argc -= optind; | argc -= optind; | ||||
argv += 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 { | static struct { | ||||
const char *name; | const char *name; | ||||
void (*fun)(int, char **); | void (*fun)(int, char **); | ||||
void (*help)(void); | |||||
} cmd_table[] = { | } 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]); | 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 | int | ||||
main(int argc, char **argv) | main(int argc, char **argv) | ||||
{ | { | ||||
int ch, help = 0; | |||||
if (argc < 2) | if (argc < 2) | ||||
usage(); | 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; | int found = 0; | ||||
for (int i = 0; !found && i < ncmds; i++) { | 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; | 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) | if (!found) | ||||
usage(); | usage(); | ||||
@@ -8,34 +8,42 @@ | |||||
#include <unistd.h> | #include <unistd.h> | ||||
#include "benc.h" | #include "benc.h" | ||||
#include "iobuf.h" | |||||
#include "btpd_if.h" | #include "btpd_if.h" | ||||
#include "iobuf.h" | |||||
#include "subr.h" | |||||
struct ipc { | |||||
int sd; | |||||
}; | |||||
int | 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 plen; | ||||
size_t keylen; | |||||
struct ipc *res; | 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; | *out = res; | ||||
return 0; | return 0; | ||||
} | } | ||||
@@ -43,179 +51,175 @@ ipc_open(const char *key, struct ipc **out) | |||||
int | int | ||||
ipc_close(struct ipc *ipc) | ipc_close(struct ipc *ipc) | ||||
{ | { | ||||
int err; | |||||
err = close(ipc->sd); | |||||
free(ipc); | 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 | static int | ||||
ipc_response(FILE *fp, char **out, uint32_t *len) | |||||
ipc_response(struct ipc *ipc, char **out, uint32_t *len) | |||||
{ | { | ||||
uint32_t size; | uint32_t size; | ||||
char *buf; | 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) | if (size == 0) | ||||
return EINVAL; | |||||
return ECONNRESET; | |||||
if ((buf = malloc(size)) == NULL) | if ((buf = malloc(size)) == NULL) | ||||
return ENOMEM; | 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; | *out = buf; | ||||
*len = size; | *len = size; | ||||
return 0; | return 0; | ||||
} | } | ||||
static int | 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; | goto error; | ||||
if (fflush(fp) != 0) | |||||
if ((errno = write_fully(ipc->sd, req, qlen)) != 0) | |||||
goto error; | goto error; | ||||
if ((errno = ipc_response(fp, res, rlen)) != 0) | |||||
if ((errno = ipc_response(ipc, res, rlen)) != 0) | |||||
goto error; | goto error; | ||||
if ((errno = benc_validate(*res, *rlen)) != 0) | if ((errno = benc_validate(*res, *rlen)) != 0) | ||||
goto error; | goto error; | ||||
fclose(fp); | |||||
return 0; | |||||
if (!benc_isdct(*res)) | |||||
errno = EINVAL; | |||||
error: | 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; | 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"; | const char cmd[] = "l4:state"; | ||||
uint32_t cmdlen = sizeof(cmd) - 1; | uint32_t cmdlen = sizeof(cmd) - 1; | ||||
char *res; | char *res; | ||||
uint32_t reslen; | 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; | 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/socket.h> | ||||
#include <sys/un.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 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 | #endif |
@@ -2,6 +2,7 @@ | |||||
#include <ctype.h> | #include <ctype.h> | ||||
#include <errno.h> | #include <errno.h> | ||||
#include <inttypes.h> | #include <inttypes.h> | ||||
#include <stdarg.h> | |||||
#include <stdlib.h> | #include <stdlib.h> | ||||
#include <string.h> | #include <string.h> | ||||
@@ -99,7 +100,7 @@ benc_length(const char *p) | |||||
; | ; | ||||
return next - p + 1; | return next - p + 1; | ||||
default: | default: | ||||
assert(benc_str(p, &next, &blen, NULL) == 0); | |||||
assert((next = benc_mem(p, &blen, NULL)) != NULL); | |||||
return next - p + blen; | return next - p + blen; | ||||
} | } | ||||
} | } | ||||
@@ -127,11 +128,12 @@ benc_next(const char *p) | |||||
return *(p + blen) == 'e' ? NULL : p + blen; | 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; | size_t blen = 0; | ||||
assert(isdigit(*p)); | |||||
blen = *p - '0'; | blen = *p - '0'; | ||||
p++; | p++; | ||||
while (isdigit(*p)) { | while (isdigit(*p)) { | ||||
@@ -141,53 +143,49 @@ benc_str(const char *p, const char **out, size_t *len, const char**next) | |||||
} | } | ||||
assert(*p == ':'); | assert(*p == ':'); | ||||
benc_safeset(len, blen); | benc_safeset(len, blen); | ||||
benc_safeset(out, p + 1); | |||||
benc_safeset(next, *(p + blen + 1) == 'e' ? NULL : p + blen + 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; | size_t blen; | ||||
const char *bstr; | 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; | size_t blen; | ||||
const char *bstr; | 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; | int sign = 1; | ||||
int64_t res = 0; | |||||
long long res = 0; | |||||
assert(*p == 'i'); | |||||
if (!benc_isint(p)) | |||||
return 0; | |||||
p++; | p++; | ||||
if (*p == '-') { | if (*p == '-') { | ||||
sign = -1; | sign = -1; | ||||
@@ -202,122 +200,79 @@ benc_int64(const char *p, int64_t *out, const char **next) | |||||
p++; | p++; | ||||
} | } | ||||
assert(*p == 'e'); | 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; | size_t len, blen; | ||||
const char *bstr; | const char *bstr; | ||||
assert(benc_isdct(p)); | |||||
if (!benc_isdct(p)) | |||||
return NULL; | |||||
len = strlen(key); | len = strlen(key); | ||||
p = benc_first(p); | p = benc_first(p); | ||||
while (p != NULL) { | 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); | 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 | int | ||||
@@ -343,3 +298,131 @@ benc_isstr(const char *p) | |||||
{ | { | ||||
return isdigit(*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 | #ifndef BTPD_BENC_H | ||||
#define 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_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_length(const char *p); | ||||
size_t benc_nelems(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_first(const char *p); | ||||
const char *benc_next(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 | #endif |
@@ -69,22 +69,22 @@ check_path(const char *path, size_t len) | |||||
int | int | ||||
fill_fileinfo(const char *fdct, struct fileinfo *tfp) | fill_fileinfo(const char *fdct, struct fileinfo *tfp) | ||||
{ | { | ||||
int err; | |||||
//int err; | |||||
size_t npath, plen, len; | size_t npath, plen, len; | ||||
const char *plst, *iter, *str; | 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; | npath = plen = 0; | ||||
iter = benc_first(plst); | iter = benc_first(plst); | ||||
while (iter != NULL) { | while (iter != NULL) { | ||||
if (!benc_isstr(iter)) | if (!benc_isstr(iter)) | ||||
return EINVAL; | return EINVAL; | ||||
benc_str(iter, &str, &len, &iter); | |||||
str = benc_mem(iter, &len, &iter); | |||||
if (!check_path(str, len)) | if (!check_path(str, len)) | ||||
return EINVAL; | return EINVAL; | ||||
npath++; | npath++; | ||||
@@ -97,13 +97,13 @@ fill_fileinfo(const char *fdct, struct fileinfo *tfp) | |||||
return ENOMEM; | return ENOMEM; | ||||
iter = benc_first(plst); | iter = benc_first(plst); | ||||
benc_str(iter, &str, &len, &iter); | |||||
str = benc_mem(iter, &len, &iter); | |||||
memcpy(tfp->path, str, len); | memcpy(tfp->path, str, len); | ||||
plen = len; | plen = len; | ||||
npath--; | npath--; | ||||
while (npath > 0) { | while (npath > 0) { | ||||
tfp->path[plen++] = '/'; | tfp->path[plen++] = '/'; | ||||
benc_str(iter, &str, &len, &iter); | |||||
str = benc_mem(iter, &len, &iter); | |||||
memcpy(tfp->path + plen, str, len); | memcpy(tfp->path + plen, str, len); | ||||
plen += len; | plen += len; | ||||
npath--; | npath--; | ||||
@@ -135,35 +135,25 @@ int | |||||
fill_metainfo(const char *bep, struct metainfo *tp, int mem_hashes) | fill_metainfo(const char *bep, struct metainfo *tp, int mem_hashes) | ||||
{ | { | ||||
size_t len; | size_t len; | ||||
int err; | |||||
int err = 0; | |||||
const char *base_addr = bep; | const char *base_addr = bep; | ||||
const char *hash_addr; | 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; | 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); | 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->npieces = len / 20; | ||||
tp->pieces_off = hash_addr - base_addr; | tp->pieces_off = hash_addr - base_addr; | ||||
if (mem_hashes) { | if (mem_hashes) { | ||||
if ((tp->piece_hash = malloc(len)) == NULL) { | if ((tp->piece_hash = malloc(len)) == NULL) { | ||||
err = ENOMEM; | err = ENOMEM; | ||||
@@ -171,12 +161,10 @@ fill_metainfo(const char *bep, struct metainfo *tp, int mem_hashes) | |||||
} | } | ||||
bcopy(hash_addr, tp->piece_hash, len); | 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->nfiles = 1; | ||||
tp->files = calloc(1, sizeof(struct fileinfo)); | tp->files = calloc(1, sizeof(struct fileinfo)); | ||||
if (tp->files != NULL) { | if (tp->files != NULL) { | ||||
@@ -190,14 +178,11 @@ fill_metainfo(const char *bep, struct metainfo *tp, int mem_hashes) | |||||
err = ENOMEM; | err = ENOMEM; | ||||
goto out; | goto out; | ||||
} | } | ||||
} | |||||
else if (err == ENOENT) { | |||||
} else if (benc_dct_chk(bep, 1, BE_LST, 1, "files")) { | |||||
int i; | int i; | ||||
const char *flst, *fdct; | 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); | tp->nfiles = benc_nelems(flst); | ||||
if (tp->nfiles < 1) { | if (tp->nfiles < 1) { | ||||
err = EINVAL; | err = EINVAL; | ||||
@@ -258,7 +243,6 @@ load_metainfo(const char *path, off_t size, int mem_hashes, | |||||
if (err == 0) | if (err == 0) | ||||
if ((*res = calloc(1, sizeof(**res))) == NULL) | if ((*res = calloc(1, sizeof(**res))) == NULL) | ||||
err = ENOMEM; | err = ENOMEM; | ||||
if (err == 0) | if (err == 0) | ||||
if ((err = fill_metainfo(buf, *res, mem_hashes)) != 0) | if ((err = fill_metainfo(buf, *res, mem_hashes)) != 0) | ||||
free(*res); | free(*res); | ||||