has moved toward btpd having a library of torrent to wich one may add or remove torrents, and where interaction on torrents are done by their assigned number. This commit is a step back from that and it makes life simpler and better for all :) * Some options to btpd has changed: --no-daemon is the old -d. -d is now used to specify the btpd directory. --logfile option is reintroduced. * The ipc code has been improved on both btpd and cli sides. * All commands have been implemented. * Various improvements in btpd. With this commit we're very close to 0.8 :)master
@@ -32,9 +32,6 @@ | |||
static uint8_t m_peer_id[20]; | |||
static struct event m_sigint; | |||
static struct event m_sigterm; | |||
static unsigned m_ntorrents; | |||
static struct torrent_tq m_torrents = BTPDQ_HEAD_INITIALIZER(m_torrents); | |||
static unsigned m_nactive; | |||
static int m_shutdown; | |||
void | |||
@@ -44,92 +41,35 @@ btpd_exit(int code) | |||
exit(code); | |||
} | |||
void | |||
btpd_tp_activated(struct torrent *tp) | |||
{ | |||
m_nactive++; | |||
} | |||
void | |||
btpd_tp_deactivated(struct torrent *tp) | |||
{ | |||
m_nactive--; | |||
if (m_nactive == 0 && m_shutdown) | |||
btpd_exit(0); | |||
} | |||
static void | |||
grace_cb(int fd, short type, void *arg) | |||
{ | |||
struct torrent *tp; | |||
BTPDQ_FOREACH(tp, &m_torrents, entry) | |||
torrent_deactivate(tp); | |||
BTPDQ_FOREACH(tp, torrent_get_all(), entry) | |||
torrent_stop(tp); | |||
} | |||
void | |||
btpd_shutdown(struct timeval *grace_tv) | |||
btpd_shutdown(int grace_seconds) | |||
{ | |||
if (m_nactive == 0) | |||
if (torrent_count() == 0) | |||
btpd_exit(0); | |||
else { | |||
struct torrent *tp; | |||
m_shutdown = 1; | |||
BTPDQ_FOREACH(tp, &m_torrents, entry) | |||
torrent_deactivate(tp); | |||
if (grace_tv != NULL) | |||
event_once(-1, EV_TIMEOUT, grace_cb, NULL, grace_tv); | |||
BTPDQ_FOREACH(tp, torrent_get_all(), entry) | |||
if (tp->state != T_STOPPING) | |||
torrent_stop(tp); | |||
if (grace_seconds >= 0) { | |||
event_once(-1, EV_TIMEOUT, grace_cb, NULL, | |||
(& (struct timeval) { grace_seconds, 0 })); | |||
} | |||
} | |||
} | |||
static void | |||
signal_cb(int signal, short type, void *arg) | |||
{ | |||
btpd_log(BTPD_L_BTPD, "Got signal %d.\n", signal); | |||
btpd_shutdown((& (struct timeval) { 30, 0 })); | |||
} | |||
void | |||
btpd_add_torrent(struct torrent *tp) | |||
{ | |||
BTPDQ_INSERT_TAIL(&m_torrents, tp, entry); | |||
m_ntorrents++; | |||
} | |||
void | |||
btpd_del_torrent(struct torrent *tp) | |||
{ | |||
BTPDQ_REMOVE(&m_torrents, tp, entry); | |||
m_ntorrents--; | |||
} | |||
const struct torrent_tq * | |||
btpd_get_torrents(void) | |||
{ | |||
return &m_torrents; | |||
} | |||
unsigned | |||
btpd_get_ntorrents(void) | |||
{ | |||
return m_ntorrents; | |||
} | |||
struct torrent * | |||
btpd_get_torrent(const uint8_t *hash) | |||
{ | |||
struct torrent *tp = BTPDQ_FIRST(&m_torrents); | |||
while (tp != NULL && bcmp(hash, tp->meta.info_hash, 20) != 0) | |||
tp = BTPDQ_NEXT(tp, entry); | |||
return tp; | |||
} | |||
struct torrent * | |||
btpd_get_torrent_num(unsigned num) | |||
int btpd_is_stopping(void) | |||
{ | |||
struct torrent *tp = BTPDQ_FIRST(&m_torrents); | |||
while (tp != NULL && tp->num != num) | |||
tp = BTPDQ_NEXT(tp, entry); | |||
return tp; | |||
return m_shutdown; | |||
} | |||
const uint8_t * | |||
@@ -138,28 +78,18 @@ btpd_get_peer_id(void) | |||
return m_peer_id; | |||
} | |||
static int | |||
nodot(struct dirent *dp) | |||
void | |||
btpd_on_no_torrents(void) | |||
{ | |||
return !(strcmp(".", dp->d_name) == 0 || strcmp("..", dp->d_name) == 0); | |||
if (m_shutdown) | |||
btpd_exit(0); | |||
} | |||
static void | |||
load_library(void) | |||
signal_cb(int signal, short type, void *arg) | |||
{ | |||
int ne; | |||
struct dirent **entries; | |||
if ((ne = scandir("library", &entries, nodot, NULL)) < 0) | |||
btpd_err("Couldn't open the library.\n"); | |||
for (int i = 0; i < ne; i++) { | |||
struct torrent *tp; | |||
struct dirent *e = entries[i]; | |||
if (torrent_load(&tp, e->d_name) == 0) | |||
btpd_add_torrent(tp); | |||
free(e); | |||
} | |||
free(entries); | |||
btpd_log(BTPD_L_BTPD, "Got signal %d.\n", signal); | |||
btpd_shutdown(30); | |||
} | |||
struct td_cb { | |||
@@ -260,14 +190,6 @@ btpd_init(void) | |||
ul_init(); | |||
cm_init(); | |||
load_library(); | |||
#if 0 | |||
struct torrent *tp; | |||
BTPDQ_FOREACH(tp, &m_torrents, entry) | |||
torrent_activate(tp); | |||
#endif | |||
signal(SIGPIPE, SIG_IGN); | |||
signal_set(&m_sigint, SIGINT, signal_cb, NULL); | |||
@@ -48,14 +48,9 @@ void btpd_err(const char *fmt, ...); | |||
void *btpd_malloc(size_t size); | |||
void *btpd_calloc(size_t nmemb, size_t size); | |||
void btpd_shutdown(struct timeval *grace_tv); | |||
struct torrent *btpd_get_torrent(const uint8_t *hash); | |||
struct torrent *btpd_get_torrent_num(unsigned num); | |||
const struct torrent_tq *btpd_get_torrents(void); | |||
void btpd_add_torrent(struct torrent *tp); | |||
void btpd_del_torrent(struct torrent *tp); | |||
unsigned btpd_get_ntorrents(void); | |||
void btpd_shutdown(int grace_seconds); | |||
int btpd_is_stopping(void); | |||
const uint8_t *btpd_get_peer_id(void); | |||
void td_acquire_lock(void); | |||
@@ -65,7 +60,6 @@ void td_release_lock(void); | |||
void td_post(void (*fun)(void *), void *arg); | |||
void td_post_end(void); | |||
void btpd_tp_activated(struct torrent *tp); | |||
void btpd_tp_deactivated(struct torrent *tp); | |||
void btpd_on_no_torrents(void); | |||
#endif |
@@ -19,10 +19,39 @@ struct cli { | |||
struct event read; | |||
}; | |||
#define buf_swrite(iob, s) buf_write(iob, s, sizeof(s) - 1) | |||
static struct event m_cli_incoming; | |||
enum ipc_code { // XXX: Same as in cli/btpd_if.h | |||
IPC_OK, | |||
IPC_FAIL, | |||
IPC_ERROR, | |||
IPC_COMMERR | |||
}; | |||
static int | |||
write_buffer(struct cli *cli, struct io_buffer *iob) | |||
{ | |||
int err = 0; | |||
if (!iob->error) { | |||
uint32_t len = iob->buf_off; | |||
write_fully(cli->sd, &len, sizeof(len)); | |||
err = write_fully(cli->sd, iob->buf, iob->buf_off); | |||
} else | |||
btpd_err("Out of memory.\n"); | |||
if (iob->buf != NULL) | |||
free(iob->buf); | |||
return err; | |||
} | |||
static int | |||
write_code_buffer(struct cli *cli, enum ipc_code code) | |||
{ | |||
struct io_buffer iob; | |||
buf_init(&iob, 16); | |||
buf_print(&iob, "d4:codei%uee", code); | |||
return write_buffer(cli, &iob); | |||
} | |||
static int | |||
cmd_stat(struct cli *cli, int argc, const char *args) | |||
{ | |||
@@ -33,9 +62,9 @@ cmd_stat(struct cli *cli, int argc, const char *args) | |||
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_print(&iob, "9:ntorrentsi%ue", torrent_count()); | |||
buf_swrite(&iob, "8:torrentsl"); | |||
BTPDQ_FOREACH(tp, btpd_get_torrents(), entry) { | |||
BTPDQ_FOREACH(tp, torrent_get_all(), entry) { | |||
if (tp->state == T_ACTIVE) { | |||
uint32_t seen_npieces = 0; | |||
for (uint32_t i = 0; i < tp->meta.npieces; i++) | |||
@@ -49,183 +78,96 @@ cmd_stat(struct cli *cli, int argc, const char *args) | |||
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:statei%ue", tp->state); | |||
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(); | |||
} | |||
buf_print(&iob, "5:statei%uee", tp->state); | |||
} | |||
} | |||
buf_swrite(&iob, "ee"); | |||
uint32_t len = iob.buf_off; | |||
write_fully(cli->sd, &len, sizeof(len)); | |||
write_fully(cli->sd, iob.buf, iob.buf_off); | |||
free(iob.buf); | |||
return 0; | |||
return write_buffer(cli, &iob); | |||
} | |||
#if 0 | |||
static void | |||
cmd_add(int argc, const char *args, FILE *fp) | |||
static int | |||
cmd_add(struct cli *cli, int argc, const char *args) | |||
{ | |||
struct io_buffer iob; | |||
buf_init(&iob, (1 << 10)); | |||
buf_write(&iob, "l", 1); | |||
while (args != NULL) { | |||
size_t plen; | |||
char path[PATH_MAX]; | |||
const char *pathp; | |||
if (!benc_isstr(args)) { | |||
free(iob.buf); | |||
return; | |||
} | |||
benc_str(args, &pathp, &plen, &args); | |||
if (plen >= PATH_MAX) { | |||
buf_print(&iob, "d4:codei%dee", ENAMETOOLONG); | |||
continue; | |||
} | |||
if (argc != 1) | |||
return EINVAL; | |||
if (btpd_is_stopping()) | |||
return write_code_buffer(cli, IPC_FAIL); | |||
bcopy(pathp, path, plen); | |||
path[plen] = '\0'; | |||
btpd_log(BTPD_L_BTPD, "add request for %s.\n", path); | |||
buf_print(&iob, "d4:codei%dee", torrent_load(path)); | |||
size_t hlen; | |||
struct torrent *tp; | |||
enum ipc_code code = IPC_OK; | |||
const uint8_t *hash = benc_dget_mem(args, "hash", &hlen); | |||
char *content = benc_dget_str(args, "content", NULL); | |||
char *torrent = benc_dget_str(args, "torrent", NULL); | |||
if (!(hlen == 20 && content != NULL && torrent != NULL)) { | |||
code = IPC_COMMERR; | |||
goto out; | |||
} | |||
buf_write(&iob, "e", 1); | |||
uint32_t len = iob.buf_off; | |||
fwrite(&len, sizeof(len), 1, fp); | |||
fwrite(iob.buf, 1, iob.buf_off, fp); | |||
free(iob.buf); | |||
} | |||
static void | |||
cmd_del(int argc, const char *args, FILE *fp) | |||
{ | |||
struct io_buffer iob; | |||
buf_init(&iob, (1 << 10)); | |||
buf_swrite(&iob, "l"); | |||
while (args != NULL) { | |||
size_t len; | |||
const char *hash; | |||
struct torrent *tp; | |||
if (!benc_isstr(args) || | |||
benc_str(args, &hash, &len, &args) != 0 || len != 20) { | |||
free(iob.buf); | |||
return; | |||
} | |||
tp = btpd_get_torrent(hash); | |||
if (tp != NULL) { | |||
btpd_log(BTPD_L_BTPD, "del request for %s.\n", tp->relpath); | |||
torrent_unload(tp); | |||
buf_swrite(&iob, "d4:codei0ee"); | |||
} else { | |||
btpd_log(BTPD_L_BTPD, "del request didn't match.\n"); | |||
buf_print(&iob, "d4:codei%dee", ENOENT); | |||
} | |||
if ((tp = torrent_get(hash)) != NULL) { | |||
code = tp->state == T_STOPPING ? IPC_FAIL : IPC_OK; | |||
goto out; | |||
} | |||
buf_swrite(&iob, "e"); | |||
uint32_t len = iob.buf_off; | |||
fwrite(&len, sizeof(len), 1, fp); | |||
fwrite(iob.buf, 1, iob.buf_off, fp); | |||
free(iob.buf); | |||
} | |||
if (torrent_set_links(hash, torrent, content) != 0) { | |||
code = IPC_ERROR; | |||
goto out; | |||
} | |||
if (torrent_start(hash) != 0) | |||
code = IPC_ERROR; | |||
#endif | |||
out: | |||
if (content != NULL) | |||
free(content); | |||
if (torrent != NULL) | |||
free(torrent); | |||
static int | |||
cmd_die(struct cli *cli, int argc, const char *args) | |||
{ | |||
char res[] = "d4:codei0ee"; | |||
uint32_t len = sizeof(res) - 1; | |||
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((& (struct timeval) { 0, 0 })); | |||
return 0; | |||
if (code == IPC_COMMERR) | |||
return EINVAL; | |||
else | |||
return write_code_buffer(cli, code); | |||
} | |||
static int | |||
cmd_start(struct cli *cli, int argc, const char *args) | |||
cmd_del(struct cli *cli, int argc, const char *args) | |||
{ | |||
if (argc != 1 || !benc_isint(args)) | |||
if (argc != 1 || !benc_isstr(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; | |||
size_t hlen; | |||
uint8_t *hash = (uint8_t *)benc_mem(args, &hlen, NULL); | |||
if (hlen != 20) | |||
return EINVAL; | |||
struct torrent *tp = torrent_get(hash); | |||
if (tp != NULL) | |||
torrent_stop(tp); | |||
return write_code_buffer(cli, IPC_OK); | |||
} | |||
static int | |||
cmd_stop(struct cli *cli, int argc, const char *args) | |||
cmd_die(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; | |||
int err = write_code_buffer(cli, IPC_OK); | |||
if (!btpd_is_stopping()) { | |||
int grace_seconds = -1; | |||
if (argc == 1 && benc_isint(args)) | |||
grace_seconds = benc_int(args, NULL); | |||
btpd_log(BTPD_L_BTPD, "Someone wants me dead.\n"); | |||
btpd_shutdown(grace_seconds); | |||
} | |||
return err; | |||
} | |||
static struct { | |||
@@ -233,14 +175,10 @@ static struct { | |||
int nlen; | |||
int (*fun)(struct cli *cli, int, const char *); | |||
} cmd_table[] = { | |||
#if 0 | |||
{ "add", 3, cmd_add }, | |||
{ "del", 3, cmd_del }, | |||
#endif | |||
{ "die", 3, cmd_die }, | |||
{ "start", 5, cmd_start }, | |||
{ "stat", 4, cmd_stat }, | |||
{ "stop", 4, cmd_stop } | |||
{ "stat", 4, cmd_stat } | |||
}; | |||
static int ncmds = sizeof(cmd_table) / sizeof(cmd_table[0]); | |||
@@ -248,22 +186,19 @@ static int ncmds = sizeof(cmd_table) / sizeof(cmd_table[0]); | |||
static int | |||
cmd_dispatch(struct cli *cli, const char *buf) | |||
{ | |||
int err = 0; | |||
size_t cmdlen; | |||
const char *cmd; | |||
const char *args; | |||
cmd = benc_mem(benc_first(buf), &cmdlen, &args); | |||
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 cmd_table[i].fun(cli, benc_nelems(buf) - 1, args); | |||
} | |||
} | |||
return err; | |||
return ENOENT; | |||
} | |||
static void | |||
@@ -98,14 +98,14 @@ static int | |||
fd_cb_rd(const char *path, int *fd, void *arg) | |||
{ | |||
struct torrent *tp = arg; | |||
return vopen(fd, O_RDONLY, "library/%s/content/%s", tp->relpath, path); | |||
return vopen(fd, O_RDONLY, "torrents/%s/content/%s", tp->relpath, path); | |||
} | |||
static int | |||
fd_cb_wr(const char *path, int *fd, void *arg) | |||
{ | |||
struct torrent *tp = arg; | |||
return vopen(fd, O_RDWR|O_CREAT, "library/%s/content/%s", tp->relpath, | |||
return vopen(fd, O_RDWR|O_CREAT, "torrents/%s/content/%s", tp->relpath, | |||
path); | |||
} | |||
@@ -464,7 +464,7 @@ test_hash(struct torrent *tp, uint8_t *hash, uint32_t piece) | |||
int fd; | |||
int err; | |||
err = vopen(&fd, O_RDONLY, "library/%s/torrent", tp->relpath); | |||
err = vopen(&fd, O_RDONLY, "torrents/%s/torrent", tp->relpath); | |||
if (err != 0) | |||
btpd_err("test_hash: %s\n", strerror(err)); | |||
@@ -531,7 +531,7 @@ test_torrent(struct torrent *tp, volatile sig_atomic_t *cancel) | |||
uint8_t (*hashes)[SHA_DIGEST_LENGTH]; | |||
uint8_t hash[SHA_DIGEST_LENGTH]; | |||
if ((err = vfopen(&fp, "r", "library/%s/torrent", tp->relpath)) != 0) | |||
if ((err = vfopen(&fp, "r", "torrents/%s/torrent", tp->relpath)) != 0) | |||
return err; | |||
hashes = btpd_malloc(tp->meta.npieces * SHA_DIGEST_LENGTH); | |||
@@ -573,7 +573,7 @@ stat_and_adjust(struct torrent *tp, struct rstat ret[]) | |||
char path[PATH_MAX]; | |||
struct stat sb; | |||
for (int i = 0; i < tp->meta.nfiles; i++) { | |||
snprintf(path, PATH_MAX, "library/%s/content/%s", tp->relpath, | |||
snprintf(path, PATH_MAX, "torrents/%s/content/%s", tp->relpath, | |||
tp->meta.files[i].path); | |||
again: | |||
if (stat(path, &sb) == -1) { | |||
@@ -603,7 +603,7 @@ load_resume(struct torrent *tp, struct rstat sbs[]) | |||
size_t pfsiz = ceil(tp->meta.npieces / 8.0); | |||
size_t bfsiz = tp->meta.npieces * tp->cm->bppbf; | |||
if ((err = vfopen(&fp, "r" , "library/%s/resume", tp->relpath)) != 0) | |||
if ((err = vfopen(&fp, "r" , "torrents/%s/resume", tp->relpath)) != 0) | |||
return err; | |||
if (fscanf(fp, "%d\n", &ver) != 1) | |||
@@ -636,7 +636,7 @@ save_resume(struct torrent *tp, struct rstat sbs[]) | |||
{ | |||
int err; | |||
FILE *fp; | |||
if ((err = vfopen(&fp, "wb", "library/%s/resume", tp->relpath)) != 0) | |||
if ((err = vfopen(&fp, "wb", "torrents/%s/resume", tp->relpath)) != 0) | |||
return err; | |||
fprintf(fp, "%d\n", 1); | |||
for (int i = 0; i < tp->meta.nfiles; i++) | |||
@@ -7,7 +7,6 @@ | |||
#include <fcntl.h> | |||
#include <getopt.h> | |||
#include <locale.h> | |||
#include <pwd.h> | |||
#include <stdio.h> | |||
#include <stdlib.h> | |||
#include <string.h> | |||
@@ -23,31 +22,17 @@ writepid(int pidfd) | |||
fclose(fp); | |||
} | |||
static char * | |||
find_homedir(void) | |||
{ | |||
char *res = getenv("BTPD_HOME"); | |||
if (res == NULL) { | |||
char *home = getenv("HOME"); | |||
if (home == NULL) { | |||
struct passwd *pwent = getpwuid(getuid()); | |||
if (pwent == NULL) | |||
errx(1, "Can't find my home directory.\n"); | |||
home = pwent->pw_dir; | |||
endpwent(); | |||
} | |||
asprintf(&res, "%s/.btpd", home); | |||
} | |||
return res; | |||
} | |||
static void | |||
setup_daemon(const char *dir) | |||
setup_daemon(int daemonize, const char *dir, const char *log) | |||
{ | |||
int pidfd; | |||
if (log == NULL) | |||
log = "log"; | |||
if (dir == NULL) | |||
dir = find_homedir(); | |||
if ((dir = find_btpd_dir()) == NULL) | |||
errx(1, "Cannot find the btpd directory"); | |||
btpd_dir = dir; | |||
@@ -57,8 +42,8 @@ setup_daemon(const char *dir) | |||
if (chdir(dir) != 0) | |||
err(1, "Couldn't change working directory to '%s'", dir); | |||
if (mkdir("library", 0777) == -1 && errno != EEXIST) | |||
err(1, "Couldn't create library"); | |||
if (mkdir("torrents", 0777) == -1 && errno != EEXIST) | |||
err(1, "Couldn't create torrents subdir"); | |||
pidfd = open("pid", O_CREAT|O_WRONLY|O_NONBLOCK|O_EXLOCK, 0666); | |||
if (pidfd == -1) { | |||
@@ -69,12 +54,12 @@ setup_daemon(const char *dir) | |||
err(1, "Couldn't open 'pid'"); | |||
} | |||
if (btpd_daemon) { | |||
if (daemonize) { | |||
if (daemon(1, 1) != 0) | |||
err(1, "Failed to daemonize"); | |||
freopen("/dev/null", "r", stdin); | |||
if (freopen("log", "a", stdout) == NULL) | |||
err(1, "Couldn't open 'log'"); | |||
if (freopen(log, "a", stdout) == NULL) | |||
err(1, "Couldn't open '%s'", log); | |||
dup2(fileno(stdout), fileno(stderr)); | |||
setlinebuf(stdout); | |||
setlinebuf(stderr); | |||
@@ -89,11 +74,7 @@ usage(void) | |||
printf( | |||
"The BitTorrent Protocol Daemon.\n" | |||
"\n" | |||
"Usage: btpd [options] [dir]\n" | |||
"\n" | |||
"Arguments:\n" | |||
"dir\n" | |||
"\tThe directory in which to run btpd. Default is '$HOME/.btpd'.\n" | |||
"Usage: btpd [-d dir] [-p port] [more options...]\n" | |||
"\n" | |||
"Options:\n" | |||
"--bw-in n\n" | |||
@@ -104,9 +85,8 @@ usage(void) | |||
"\tLimit outgoing BitTorrent traffic to n kB/s.\n" | |||
"\tDefault is 0 which means unlimited.\n" | |||
"\n" | |||
"-d\n" | |||
"\tKeep the btpd process in the foregorund and log to std{out,err}.\n" | |||
"\tThis option is intended for debugging purposes.\n" | |||
"-d dir\n" | |||
"\tThe directory in which to run btpd. Default is '$HOME/.btpd'.\n" | |||
"\n" | |||
"--downloaders n\n" | |||
"\tControls the number of simultaneous uploads.\n" | |||
@@ -119,9 +99,16 @@ usage(void) | |||
"--help\n" | |||
"\tShow this text.\n" | |||
"\n" | |||
"--logfile file\n" | |||
"\tWhere to put the logfile. By default it's put in the btpd dir.\n" | |||
"\n" | |||
"--max-peers n\n" | |||
"\tLimit the amount of peers to n.\n" | |||
"\n" | |||
"--no-daemon\n" | |||
"\tKeep the btpd process in the foregorund and log to std{out,err}.\n" | |||
"\tThis option is intended for debugging purposes.\n" | |||
"\n" | |||
"-p n, --port n\n" | |||
"\tListen at port n. Default is 6881.\n" | |||
"\n" | |||
@@ -142,6 +129,8 @@ static struct option longopts[] = { | |||
{ "prealloc", required_argument, &longval, 3 }, | |||
{ "downloaders", required_argument, &longval, 4 }, | |||
{ "max-peers", required_argument, &longval, 5 }, | |||
{ "no-daemon", no_argument, &longval, 6 }, | |||
{ "logfile", required_argument, &longval, 7 }, | |||
{ "help", no_argument, &longval, 128 }, | |||
{ NULL, 0, NULL, 0 } | |||
}; | |||
@@ -149,16 +138,17 @@ static struct option longopts[] = { | |||
int | |||
main(int argc, char **argv) | |||
{ | |||
char *dir = NULL; | |||
char *dir = NULL, *log = NULL; | |||
int daemonize = 1; | |||
setlocale(LC_ALL, ""); | |||
for (;;) { | |||
switch (getopt_long(argc, argv, "dp:", longopts, NULL)) { | |||
switch (getopt_long(argc, argv, "d:p:", longopts, NULL)) { | |||
case -1: | |||
goto args_done; | |||
case 'd': | |||
btpd_daemon = 0; | |||
dir = optarg; | |||
break; | |||
case 'p': | |||
net_port = atoi(optarg); | |||
@@ -180,6 +170,12 @@ main(int argc, char **argv) | |||
case 5: | |||
net_max_peers = atoi(optarg); | |||
break; | |||
case 6: | |||
daemonize = 0; | |||
break; | |||
case 7: | |||
log = optarg; | |||
break; | |||
default: | |||
usage(); | |||
} | |||
@@ -193,12 +189,10 @@ args_done: | |||
argc -= optind; | |||
argv += optind; | |||
if (argc > 1) | |||
usage(); | |||
if (argc > 0) | |||
dir = argv[0]; | |||
usage(); | |||
setup_daemon(dir); | |||
setup_daemon(daemonize, dir, log); | |||
event_init(); | |||
@@ -448,9 +448,10 @@ net_connect2(struct sockaddr *sa, socklen_t salen, int *sd) | |||
set_nonblocking(*sd); | |||
if (connect(*sd, sa, salen) == -1 && errno != EINPROGRESS) { | |||
btpd_log(BTPD_L_CONN, "Botched connection %s.", strerror(errno)); | |||
int err = errno; | |||
btpd_log(BTPD_L_CONN, "Botched connection %s.\n", strerror(errno)); | |||
close(*sd); | |||
return errno; | |||
return err; | |||
} | |||
return 0; | |||
@@ -1,6 +1,5 @@ | |||
#include <btpd.h> | |||
short btpd_daemon = 1; | |||
const char *btpd_dir; | |||
#ifdef DEBUG | |||
uint32_t btpd_logmask = BTPD_L_ALL; | |||
@@ -1,7 +1,6 @@ | |||
#ifndef BTPD_OPTS_H | |||
#define BTPD_OPTS_H | |||
extern short btpd_daemon; | |||
extern const char *btpd_dir; | |||
extern uint32_t btpd_logmask; | |||
extern int net_max_downloaders; | |||
@@ -19,14 +19,28 @@ | |||
#include "tracker_req.h" | |||
#include "stream.h" | |||
static unsigned m_next_num; | |||
static unsigned m_ntorrents; | |||
static struct torrent_tq m_torrents = BTPDQ_HEAD_INITIALIZER(m_torrents); | |||
static unsigned | |||
num_get_next(void) | |||
const struct torrent_tq * | |||
torrent_get_all(void) | |||
{ | |||
if (m_next_num == UINT_MAX) | |||
btpd_err("Reached maximum torrent number.\n"); | |||
return m_next_num++; | |||
return &m_torrents; | |||
} | |||
unsigned | |||
torrent_count(void) | |||
{ | |||
return m_ntorrents; | |||
} | |||
struct torrent * | |||
torrent_get(const uint8_t *hash) | |||
{ | |||
struct torrent *tp = BTPDQ_FIRST(&m_torrents); | |||
while (tp != NULL && bcmp(hash, tp->meta.info_hash, 20) != 0) | |||
tp = BTPDQ_NEXT(tp, entry); | |||
return tp; | |||
} | |||
off_t | |||
@@ -58,22 +72,71 @@ torrent_block_size(struct torrent *tp, uint32_t piece, uint32_t nblocks, | |||
} | |||
} | |||
void | |||
torrent_activate(struct torrent *tp) | |||
static void | |||
torrent_relpath(const uint8_t *hash, char *buf) | |||
{ | |||
for (int i = 0; i < 20; i++) | |||
snprintf(buf + i * 2, 3, "%.2x", hash[i]); | |||
} | |||
int | |||
torrent_set_links(const uint8_t *hash, const char *torrent, | |||
const char *content) | |||
{ | |||
if (tp->state == T_INACTIVE) { | |||
tp->state = T_STARTING; | |||
cm_start(tp); | |||
btpd_tp_activated(tp); | |||
char relpath[RELPATH_SIZE]; | |||
char file[PATH_MAX]; | |||
torrent_relpath(hash, relpath); | |||
snprintf(file, PATH_MAX, "torrents/%s", relpath); | |||
if (mkdir(file, 0777) == -1 && errno != EEXIST) | |||
return errno; | |||
snprintf(file, PATH_MAX, "torrents/%s/torrent", relpath); | |||
if (unlink(file) == -1 && errno != ENOENT) | |||
return errno; | |||
if (symlink(torrent, file) == -1) | |||
return errno; | |||
snprintf(file, PATH_MAX, "torrents/%s/content", relpath); | |||
if (unlink(file) == -1 && errno != ENOENT) | |||
return errno; | |||
if (symlink(content, file) == -1) | |||
return errno; | |||
return 0; | |||
} | |||
int | |||
torrent_start(const uint8_t *hash) | |||
{ | |||
struct torrent *tp; | |||
struct metainfo *mi; | |||
int error; | |||
char relpath[RELPATH_SIZE]; | |||
char file[PATH_MAX]; | |||
torrent_relpath(hash, relpath); | |||
snprintf(file, PATH_MAX, "torrents/%s/torrent", relpath); | |||
if ((error = load_metainfo(file, -1, 0, &mi)) != 0) { | |||
btpd_log(BTPD_L_ERROR, "Couldn't load torrent file %s: %s.\n", | |||
file, strerror(error)); | |||
return error; | |||
} | |||
btpd_log(BTPD_L_BTPD, "Starting torrent '%s'.\n", mi->name); | |||
tp = btpd_calloc(1, sizeof(*tp)); | |||
bcopy(relpath, tp->relpath, RELPATH_SIZE); | |||
tp->meta = *mi; | |||
free(mi); | |||
BTPDQ_INSERT_TAIL(&m_torrents, tp, entry); | |||
m_ntorrents++; | |||
cm_start(tp); | |||
return 0; | |||
} | |||
void | |||
torrent_deactivate(struct torrent *tp) | |||
torrent_stop(struct torrent *tp) | |||
{ | |||
switch (tp->state) { | |||
case T_INACTIVE: | |||
break; | |||
case T_STARTING: | |||
case T_ACTIVE: | |||
tp->state = T_STOPPING; | |||
@@ -88,69 +151,43 @@ torrent_deactivate(struct torrent *tp) | |||
if (tp->tr != NULL) | |||
tr_destroy(tp); | |||
break; | |||
default: | |||
abort(); | |||
} | |||
} | |||
int | |||
torrent_load(struct torrent **res, const char *path) | |||
static void | |||
torrent_kill(struct torrent *tp) | |||
{ | |||
struct metainfo *mi; | |||
int error; | |||
char file[PATH_MAX]; | |||
snprintf(file, PATH_MAX, "library/%s/torrent", path); | |||
if ((error = load_metainfo(file, -1, 0, &mi)) != 0) { | |||
btpd_log(BTPD_L_ERROR, "Couldn't load metainfo file %s: %s.\n", | |||
file, strerror(error)); | |||
return error; | |||
} | |||
if (btpd_get_torrent(mi->info_hash) != NULL) { | |||
btpd_log(BTPD_L_BTPD, | |||
"%s has same hash as an already loaded torrent.\n", path); | |||
error = EEXIST; | |||
} | |||
if (error == 0) { | |||
*res = btpd_calloc(1, sizeof(**res)); | |||
(*res)->relpath = strdup(path); | |||
(*res)->meta = *mi; | |||
(*res)->num = num_get_next(); | |||
free(mi); | |||
} else { | |||
clear_metainfo(mi); | |||
free(mi); | |||
} | |||
return error; | |||
btpd_log(BTPD_L_BTPD, "Removed torrent '%s'.\n", tp->meta.name); | |||
assert(m_ntorrents > 0); | |||
m_ntorrents--; | |||
BTPDQ_REMOVE(&m_torrents, tp, entry); | |||
clear_metainfo(&tp->meta); | |||
free(tp); | |||
if (m_ntorrents == 0) | |||
btpd_on_no_torrents(); | |||
} | |||
void | |||
torrent_on_cm_started(struct torrent *tp) | |||
{ | |||
net_add_torrent(tp); | |||
tr_start(tp); | |||
tp->state = T_ACTIVE; | |||
net_add_torrent(tp); | |||
if (tr_start(tp) != 0) | |||
torrent_stop(tp); | |||
} | |||
void | |||
torrent_on_cm_stopped(struct torrent *tp) | |||
{ | |||
assert(tp->state == T_STOPPING); | |||
if (tp->tr == NULL) { | |||
tp->state = T_INACTIVE; | |||
btpd_tp_deactivated(tp); | |||
} | |||
if (tp->tr == NULL) | |||
torrent_kill(tp); | |||
} | |||
void | |||
torrent_on_tr_stopped(struct torrent *tp) | |||
{ | |||
assert(tp->state == T_STOPPING); | |||
if (tp->cm == NULL) { | |||
tp->state = T_INACTIVE; | |||
btpd_tp_deactivated(tp); | |||
} | |||
if (tp->cm == NULL) | |||
torrent_kill(tp); | |||
} |
@@ -2,17 +2,16 @@ | |||
#define BTPD_TORRENT_H | |||
#define PIECE_BLOCKLEN (1 << 14) | |||
#define RELPATH_SIZE 41 | |||
enum torrent_state { | |||
T_INACTIVE, | |||
T_STARTING, | |||
T_ACTIVE, | |||
T_STOPPING | |||
}; | |||
struct torrent { | |||
unsigned num; | |||
const char *relpath; | |||
char relpath[RELPATH_SIZE]; | |||
struct metainfo meta; | |||
enum torrent_state state; | |||
@@ -26,9 +25,14 @@ struct torrent { | |||
BTPDQ_HEAD(torrent_tq, torrent); | |||
int torrent_load(struct torrent **res, const char *path); | |||
void torrent_activate(struct torrent *tp); | |||
void torrent_deactivate(struct torrent *tp); | |||
unsigned torrent_count(void); | |||
const struct torrent_tq *torrent_get_all(void); | |||
struct torrent *torrent_get(const uint8_t *hash); | |||
int torrent_start(const uint8_t *hash); | |||
void torrent_stop(struct torrent *tp); | |||
int torrent_set_links(const uint8_t *hash, const char *torrent, | |||
const char *content); | |||
off_t torrent_piece_size(struct torrent *tp, uint32_t piece); | |||
uint32_t torrent_piece_blocks(struct torrent *tp, uint32_t piece); | |||
@@ -1,66 +1,179 @@ | |||
#include <sys/types.h> | |||
#include <sys/stat.h> | |||
#include <err.h> | |||
#include <errno.h> | |||
#include <fcntl.h> | |||
#include <getopt.h> | |||
#include <limits.h> | |||
#include <stdio.h> | |||
#include <stdlib.h> | |||
#include <string.h> | |||
#include <unistd.h> | |||
#include "btpd_if.h" | |||
#include "metainfo.h" | |||
#include "subr.h" | |||
static const char *btpd_dir = "/usr/btpd"; | |||
static struct ipc *ipc; | |||
const char *btpd_dir; | |||
struct ipc *ipc; | |||
static void | |||
handle_ipc_res(enum ipc_code code) | |||
void | |||
btpd_connect(void) | |||
{ | |||
if ((errno = ipc_open(btpd_dir, &ipc)) != 0) | |||
err(1, "cannot open connection to btpd in %s", btpd_dir); | |||
} | |||
enum ipc_code | |||
handle_ipc_res(enum ipc_code code, const char *target) | |||
{ | |||
switch (code) { | |||
case IPC_OK: | |||
return; | |||
break; | |||
case IPC_FAIL: | |||
warnx("Ipc failed.\n"); | |||
warnx("btpd couldn't execute the requested operation for %s", target); | |||
break; | |||
case IPC_ERROR: | |||
warnx("btpd encountered an error for %s", target); | |||
break; | |||
case IPC_COMMERR: | |||
errx(1, "Communication error.\n"); | |||
default: | |||
errx(1, "fatal error in communication with btpd"); | |||
} | |||
return code; | |||
} | |||
static void | |||
btpd_connect(void) | |||
void | |||
print_state_name(struct tpstat *ts) | |||
{ | |||
if ((errno = ipc_open(btpd_dir, &ipc)) != 0) | |||
errx(1, "Couldn't connect to btpd in %s (%s).\n", | |||
btpd_dir, strerror(errno)); | |||
char statec[] = ">*<U"; | |||
int state = min(ts->state, 3); | |||
printf("%c. %s", statec[state], ts->name); | |||
} | |||
void | |||
print_stat(struct tpstat *cur) | |||
{ | |||
printf("%5.1f%% %6.1fM %7.2fkB/s %6.1fM %7.2fkB/s %4u %5.1f%%", | |||
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"); | |||
} | |||
void | |||
usage_add(void) | |||
{ | |||
printf( | |||
"Add a torrent to btpd.\n" | |||
"Add torrents 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" | |||
"Usage: add [--topdir] -d dir file\n" | |||
" add file ...\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" | |||
"Arguments:\n" | |||
"file ...\n" | |||
"\tOne or more torrents to add.\n" | |||
"\n" | |||
"-f file\n" | |||
"\tThe torrent to add.\n" | |||
"Options:\n" | |||
"-d dir\n" | |||
"\tUse the dir for content.\n" | |||
"\n" | |||
"-s\n" | |||
"\tStart the torrent.\n" | |||
"--topdir\n" | |||
"\tAppend the torrent top directory (if any) to the content path.\n" | |||
"\tThis option cannot be used without the '-d' option.\n" | |||
"\n" | |||
); | |||
exit(1); | |||
} | |||
struct option add_opts [] = { | |||
{ "help", no_argument, NULL, 'H' }, | |||
{ "topdir", no_argument, NULL, 'T'}, | |||
{NULL, 0, NULL, 0} | |||
}; | |||
int | |||
content_link(uint8_t *hash, char *buf) | |||
{ | |||
int n; | |||
char relpath[41]; | |||
char path[PATH_MAX]; | |||
for (int i = 0; i < 20; i++) | |||
snprintf(relpath + i * 2, 3, "%.2x", hash[i]); | |||
snprintf(path, PATH_MAX, "%s/torrents/%s/content", btpd_dir, relpath); | |||
if ((n = readlink(path, buf, PATH_MAX)) == -1) | |||
return errno; | |||
buf[min(n, PATH_MAX)] = '\0'; | |||
return 0; | |||
} | |||
void | |||
cmd_add(int argc, char **argv) | |||
{ | |||
int ch, topdir = 0; | |||
char *dir = NULL, bdir[PATH_MAX]; | |||
while ((ch = getopt_long(argc, argv, "d:", add_opts, NULL)) != -1) { | |||
switch (ch) { | |||
case 'T': | |||
topdir = 1; | |||
break; | |||
case 'd': | |||
dir = optarg; | |||
break; | |||
default: | |||
usage_add(); | |||
} | |||
} | |||
argc -= optind; | |||
argv += optind; | |||
if (argc < 1 || (topdir == 1 && dir == NULL) || (dir != NULL && argc > 1)) | |||
usage_add(); | |||
if (dir != NULL) | |||
if (realpath(dir, bdir) == NULL) | |||
err(1, "path error on %s", bdir); | |||
btpd_connect(); | |||
for (int i = 0; i < argc; i++) { | |||
struct metainfo *mi; | |||
char dpath[PATH_MAX], fpath[PATH_MAX]; | |||
if ((errno = load_metainfo(argv[i], -1, 0, &mi)) != 0) { | |||
warn("error loading torrent %s", argv[i]); | |||
continue; | |||
} | |||
if ((topdir && | |||
!(mi->nfiles == 1 | |||
&& strcmp(mi->name, mi->files[0].path) == 0))) | |||
snprintf(dpath, PATH_MAX, "%s/%s", bdir, mi->name); | |||
else if (dir != NULL) | |||
strlcpy(dpath, bdir, PATH_MAX); | |||
else { | |||
if (content_link(mi->info_hash, dpath) != 0) { | |||
warnx("unknown content dir for %s", argv[i]); | |||
errx(1, "use the '-d' option"); | |||
} | |||
} | |||
if (mkdir(dpath, 0777) != 0 && errno != EEXIST) | |||
err(1, "couldn't create directory %s", dpath); | |||
if (realpath(argv[i], fpath) == NULL) | |||
err(1, "path error on %s", fpath); | |||
handle_ipc_res(btpd_add(ipc, mi->info_hash, fpath, dpath), argv[1]); | |||
clear_metainfo(mi); | |||
free(mi); | |||
} | |||
} | |||
void | |||
@@ -69,11 +182,11 @@ usage_del(void) | |||
printf( | |||
"Remove torrents from btpd.\n" | |||
"\n" | |||
"Usage: del num ...\n" | |||
"Usage: del file ...\n" | |||
"\n" | |||
"Arguments:\n" | |||
"num\n" | |||
"\tThe number of the torrent to remove.\n" | |||
"file ...\n" | |||
"\tThe torrents to remove.\n" | |||
"\n"); | |||
exit(1); | |||
} | |||
@@ -84,16 +197,17 @@ cmd_del(int argc, char **argv) | |||
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(); | |||
} | |||
btpd_connect(); | |||
for (int i = 0; i < argc -1; i++) | |||
handle_ipc_res(btpd_del_num(ipc, nums[i])); | |||
for (int i = 1; i < argc; i++) { | |||
struct metainfo *mi; | |||
if ((errno = load_metainfo(argv[i], -1, 0, &mi)) != 0) { | |||
warn("error loading torrent %s", argv[i]); | |||
continue; | |||
} | |||
handle_ipc_res(btpd_del(ipc, mi->info_hash), argv[i]); | |||
clear_metainfo(mi); | |||
free(mi); | |||
} | |||
} | |||
void | |||
@@ -118,24 +232,23 @@ cmd_kill(int argc, char **argv) | |||
{ | |||
int seconds = -1; | |||
char *endptr; | |||
if (argc == 1) | |||
; | |||
else if (argc == 2) { | |||
if (argc == 2) { | |||
seconds = strtol(argv[1], &endptr, 10); | |||
if (strlen(argv[1]) > endptr - argv[1] || seconds < 0) | |||
usage_kill(); | |||
} else | |||
} else if (argc > 2) | |||
usage_kill(); | |||
btpd_connect(); | |||
btpd_die(ipc, seconds); | |||
handle_ipc_res(btpd_die(ipc, seconds), "kill"); | |||
} | |||
void | |||
usage_list(void) | |||
{ | |||
printf( | |||
"List btpd's torrents.\n" | |||
"List active torrents.\n" | |||
"\n" | |||
"Usage: list\n" | |||
"\n" | |||
@@ -152,12 +265,13 @@ cmd_list(int argc, char **argv) | |||
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, | |||
if (handle_ipc_res(btpd_stat(ipc, &st), "list") != IPC_OK) | |||
exit(1); | |||
for (int i = 0; i < st->ntorrents; i++) { | |||
print_state_name(&st->torrents[i]); | |||
putchar('\n'); | |||
} | |||
printf("%u torrent%s.\n", st->ntorrents, | |||
st->ntorrents == 1 ? "" : "s"); | |||
} | |||
@@ -166,9 +280,9 @@ usage_stat(void) | |||
{ | |||
printf( | |||
"Display stats for active torrents.\n" | |||
"The stats displayed are:\n" | |||
"The displayed stats are:\n" | |||
"%% got, MB down, rate down. MB up, rate up\n" | |||
"peers, %% of pieces seen, tracker errors\n" | |||
"peer count, %% of pieces seen, tracker errors\n" | |||
"\n" | |||
"Usage: stat [-i] [-w seconds]\n" | |||
"\n" | |||
@@ -182,23 +296,6 @@ usage_stat(void) | |||
exit(1); | |||
} | |||
void | |||
print_stat(struct tpstat *cur) | |||
{ | |||
printf("%5.1f%% %6.1fM %7.2fkB/s %6.1fM %7.2fkB/s %4u %5.1f%%", | |||
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"); | |||
} | |||
void | |||
do_stat(int individual, int seconds) | |||
{ | |||
@@ -206,13 +303,11 @@ do_stat(int individual, int seconds) | |||
struct tpstat tot; | |||
again: | |||
bzero(&tot, sizeof(tot)); | |||
tot.num = -1; | |||
if ((errno = btpd_stat(ipc, &st)) != 0) | |||
err(1, "btpd_stat"); | |||
tot.state = T_ACTIVE; | |||
if (handle_ipc_res(btpd_stat(ipc, &st), "stat") != IPC_OK) | |||
exit(1); | |||
for (int i = 0; i < st->ntorrents; i++) { | |||
struct tpstat *cur = &st->torrents[i]; | |||
if (cur->state != 'A') | |||
continue; | |||
tot.uploaded += cur->uploaded; | |||
tot.downloaded += cur->downloaded; | |||
tot.rate_up += cur->rate_up; | |||
@@ -223,7 +318,8 @@ again: | |||
tot.have += cur->have; | |||
tot.total += cur->total; | |||
if (individual) { | |||
printf("%u. %s:\n", cur->num, cur->name); | |||
print_state_name(cur); | |||
printf(":\n"); | |||
print_stat(cur); | |||
} | |||
} | |||
@@ -237,8 +333,8 @@ again: | |||
} | |||
} | |||
static struct option stat_opts [] = { | |||
{ "help", no_argument, NULL, 1 }, | |||
struct option stat_opts [] = { | |||
{ "help", no_argument, NULL, 'H' }, | |||
{NULL, 0, NULL, 0} | |||
}; | |||
@@ -272,73 +368,7 @@ cmd_stat(int argc, char **argv) | |||
do_stat(iflag, seconds); | |||
} | |||
void | |||
usage_start(void) | |||
{ | |||
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); | |||
} | |||
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(); | |||
} | |||
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(); | |||
} | |||
btpd_connect(); | |||
for (int i = 0; i < argc -1; i++) | |||
handle_ipc_res(btpd_stop_num(ipc, nums[i])); | |||
} | |||
static struct { | |||
struct { | |||
const char *name; | |||
void (*fun)(int, char **); | |||
void (*help)(void); | |||
@@ -347,19 +377,16 @@ static struct { | |||
{ "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 } | |||
{ "stat", cmd_stat, usage_stat } | |||
}; | |||
static int ncmds = sizeof(cmd_table) / sizeof(cmd_table[0]); | |||
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" | |||
"btcli is the btpd command line interface.\n" | |||
"\n" | |||
"Usage: btcli [main options] command [command options]\n" | |||
"\n" | |||
@@ -375,15 +402,13 @@ usage(void) | |||
"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 }, | |||
struct option base_opts [] = { | |||
{ "help", no_argument, NULL, 'H' }, | |||
{NULL, 0, NULL, 0} | |||
}; | |||
@@ -400,7 +425,7 @@ main(int argc, char **argv) | |||
case 'd': | |||
btpd_dir = optarg; | |||
break; | |||
case 1: | |||
case 'H': | |||
help = 1; | |||
break; | |||
default: | |||
@@ -413,6 +438,10 @@ main(int argc, char **argv) | |||
if (argc == 0) | |||
usage(); | |||
if (btpd_dir == NULL) | |||
if ((btpd_dir = find_btpd_dir()) == NULL) | |||
errx(1, "cannot find the btpd directory"); | |||
optind = 0; | |||
int found = 0; | |||
for (int i = 0; !found && i < ncmds; i++) { | |||
@@ -424,7 +453,7 @@ main(int argc, char **argv) | |||
cmd_table[i].fun(argc, argv); | |||
} | |||
} | |||
if (!found) | |||
usage(); | |||
@@ -1,3 +1,7 @@ | |||
#include <sys/types.h> | |||
#include <sys/socket.h> | |||
#include <sys/un.h> | |||
#include <ctype.h> | |||
#include <err.h> | |||
#include <errno.h> | |||
@@ -81,7 +85,7 @@ ipc_response(struct ipc *ipc, char **out, uint32_t *len) | |||
*len = size; | |||
return 0; | |||
} | |||
static int | |||
ipc_req_res(struct ipc *ipc, const char *req, uint32_t qlen, char **res, | |||
uint32_t *rlen) | |||
@@ -125,7 +129,7 @@ btpd_die(struct ipc *ipc, int seconds) | |||
if (seconds >= 0) | |||
buf_print(&iob, "l3:diei%dee", seconds); | |||
else | |||
buf_print(&iob, "l3:diee"); | |||
buf_swrite(&iob, "l3:diee"); | |||
return ipc_buf_req(ipc, &iob); | |||
} | |||
@@ -150,21 +154,18 @@ parse_btstat(const uint8_t *res, struct btstat **out) | |||
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"); | |||
} | |||
ts->state = benc_dget_int(tp, "state"); | |||
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, "down"); | |||
ts->uploaded = benc_dget_int(tp, "up"); | |||
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++; | |||
} | |||
*out = st; | |||
@@ -197,29 +198,26 @@ btpd_stat(struct ipc *ipc, struct btstat **out) | |||
return err; | |||
} | |||
static enum ipc_code | |||
btpd_common_num(struct ipc *ipc, const char *cmd, unsigned num) | |||
{ | |||
struct io_buffer iob; | |||
buf_init(&iob, 16); | |||
buf_print(&iob, "l%d:%si%uee", (int)strlen(cmd), cmd, num); | |||
return ipc_buf_req(ipc, &iob); | |||
} | |||
enum ipc_code | |||
btpd_del_num(struct ipc *ipc, unsigned num) | |||
btpd_add(struct ipc *ipc, const uint8_t *hash, const char *torrent, | |||
const char *content) | |||
{ | |||
return btpd_common_num(ipc, "del", num); | |||
} | |||
enum ipc_code | |||
btpd_start_num(struct ipc *ipc, unsigned num) | |||
{ | |||
return btpd_common_num(ipc, "start", num); | |||
struct io_buffer iob; | |||
buf_init(&iob, (1 << 10)); | |||
buf_print(&iob, "l3:addd7:content%d:%s4:hash20:", (int)strlen(content), | |||
content); | |||
buf_write(&iob, hash, 20); | |||
buf_print(&iob, "7:torrent%d:%see", (int)strlen(torrent), torrent); | |||
return ipc_buf_req(ipc, &iob); | |||
} | |||
enum ipc_code | |||
btpd_stop_num(struct ipc *ipc, unsigned num) | |||
btpd_del(struct ipc *ipc, const uint8_t *hash) | |||
{ | |||
return btpd_common_num(ipc, "stop", num); | |||
struct io_buffer iob; | |||
buf_init(&iob, 32); | |||
buf_swrite(&iob, "l3:del20:"); | |||
buf_write(&iob, hash, 20); | |||
buf_write(&iob, "e", 1); | |||
return ipc_buf_req(ipc, &iob); | |||
} |
@@ -1,15 +1,18 @@ | |||
#ifndef BTPD_IF_H | |||
#define BTPD_IF_H | |||
#include <sys/types.h> | |||
#include <sys/socket.h> | |||
#include <sys/un.h> | |||
struct ipc; | |||
enum torrent_state { //XXX: Same as in btpd/torrent.h | |||
T_STARTING, | |||
T_ACTIVE, | |||
T_STOPPING | |||
}; | |||
enum ipc_code { | |||
IPC_OK, | |||
IPC_FAIL, | |||
IPC_ERROR, | |||
IPC_COMMERR | |||
}; | |||
@@ -17,8 +20,7 @@ struct btstat { | |||
unsigned ntorrents; | |||
struct tpstat { | |||
char *name; | |||
unsigned num; | |||
char state; | |||
enum torrent_state state; | |||
unsigned errors; | |||
unsigned npeers; | |||
@@ -32,13 +34,11 @@ struct btstat { | |||
int ipc_open(const char *dir, struct ipc **out); | |||
int ipc_close(struct ipc *ipc); | |||
enum ipc_code btpd_add(struct ipc *ipc, const uint8_t *hash, | |||
const char *torrent, const char *content); | |||
enum ipc_code btpd_del(struct ipc *ipc, const uint8_t *hash); | |||
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 |