about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--btpd/Makefile.am2
-rw-r--r--btpd/btpd.c420
-rw-r--r--btpd/btpd.h75
-rw-r--r--btpd/cli_if.c46
-rw-r--r--btpd/main.c182
-rw-r--r--btpd/net.c139
-rw-r--r--btpd/net.h8
-rw-r--r--btpd/net_buf.c39
-rw-r--r--btpd/opts.c14
-rw-r--r--btpd/opts.h8
-rw-r--r--btpd/peer.c28
-rw-r--r--btpd/policy_choke.c4
-rw-r--r--btpd/policy_if.c10
-rw-r--r--btpd/torrent.c21
-rw-r--r--btpd/torrent.h2
-rw-r--r--btpd/tracker_req.c41
-rw-r--r--btpd/util.c70
17 files changed, 590 insertions, 519 deletions
diff --git a/btpd/Makefile.am b/btpd/Makefile.am
index 7135b63..718cfbd 100644
--- a/btpd/Makefile.am
+++ b/btpd/Makefile.am
@@ -1,6 +1,8 @@
 bin_PROGRAMS=btpd
 btpd_SOURCES=\
+	main.c util.c\
 	btpd.c btpd.h\
+	opts.c opts.h\
 	cli_if.c\
 	net.c net.h\
 	net_buf.c net_buf.h\
diff --git a/btpd/btpd.c b/btpd/btpd.c
index 7f905f9..fe6b099 100644
--- a/btpd/btpd.c
+++ b/btpd/btpd.c
@@ -17,8 +17,8 @@
 #include <getopt.h>
 #include <math.h>
 #include <locale.h>
+#include <pwd.h>
 #include <signal.h>
-#include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -26,146 +26,33 @@
 #include <unistd.h>
 
 #include "btpd.h"
-#include "tracker_req.h"
 
-extern void client_connection_cb(int sd, short type, void *arg);
-
-struct btpd btpd;
-
-void *
-btpd_malloc(size_t size)
-{
-    void *a;
-    if ((a = malloc(size)) == NULL)
-	btpd_err("Failed to allocate %d bytes.\n", (int)size);
-    return a;
-}
-
-void *
-btpd_calloc(size_t nmemb, size_t size)
-{
-    void *a;
-    if ((a = calloc(nmemb, size)) == NULL)
-	btpd_err("Failed to allocate %d bytes.\n", (int)(nmemb * size));
-    return a;
-}
-
-const char *
-logtype_str(uint32_t type)
-{
-    if (type & BTPD_L_BTPD)
-	return "btpd";
-    else if (type & BTPD_L_ERROR)
-	return "error";
-    else if (type & BTPD_L_CONN)
-	return "conn";
-    else if (type & BTPD_L_TRACKER)
-	return "tracker";
-    else if (type & BTPD_L_MSG)
-	return "msg";
-    else
-	return "";
-}
-
-void
-btpd_err(const char *fmt, ...)
-{
-    va_list ap;
-    va_start(ap, fmt);
-    if (BTPD_L_ERROR & btpd.logmask) {
-	char tbuf[20];
-	time_t tp = time(NULL);
-	strftime(tbuf, 20, "%b %e %T", localtime(&tp));
-	printf("%s %s: ", tbuf, logtype_str(BTPD_L_ERROR));
-	vprintf(fmt, ap);
-    }
-    va_end(ap);
-    exit(1);
-}
-
-void
-btpd_log(uint32_t type, const char *fmt, ...)
-{
-    va_list ap;
-    va_start(ap, fmt);
-    if (type & btpd.logmask) {
-	char tbuf[20];
-	time_t tp = time(NULL);
-	strftime(tbuf, 20, "%b %e %T", localtime(&tp));
-	printf("%s %s: ", tbuf, logtype_str(type));
-	vprintf(fmt, ap);
-    }
-    va_end(ap);
-}
-
-static void
-btpd_init(void)
-{
-    bcopy(BTPD_VERSION, btpd.peer_id, sizeof(BTPD_VERSION) - 1);
-    btpd.peer_id[sizeof(BTPD_VERSION) - 1] = '|';
-    srandom(time(NULL));
-    for (int i = sizeof(BTPD_VERSION); i < 20; i++)
-	btpd.peer_id[i] = rint(random() * 255.0 / RAND_MAX);
-
-    btpd.version = BTPD_VERSION;
-
-#ifdef DEBUG
-    btpd.logmask = BTPD_L_ALL;
-#else
-    btpd.logmask =  BTPD_L_BTPD | BTPD_L_ERROR;
-#endif
-
-    BTPDQ_INIT(&btpd.kids);
-
-    btpd.ntorrents = 0;
-    BTPDQ_INIT(&btpd.cm_list);
-
-    BTPDQ_INIT(&btpd.readq);
-    BTPDQ_INIT(&btpd.writeq);
-
-    BTPDQ_INIT(&btpd.unattached);
-
-    btpd.port = 6881;
-
-    btpd.bw_hz = 8;
-    btpd.bwcalls = 0;
-    for (int i = 0; i < BWCALLHISTORY; i++)
-	btpd.bwrate[i] = 0;
-
-    btpd.obwlim = 0;
-    btpd.ibwlim = 0;
-    btpd.obw_left = 0;
-    btpd.ibw_left = 0;
+struct child {
+    pid_t pid;
+    void *arg;
+    void (*cb)(pid_t, void *);
+    BTPDQ_ENTRY(child) entry;
+};
 
-    btpd.npeers = 0;
+BTPDQ_HEAD(child_tq, child);
 
-    int nfiles = getdtablesize();
-    if (nfiles <= 20)
-	btpd_err("Too few open files allowed (%d). "
-		 "Check \"ulimit -n\"\n", nfiles);
-    else if (nfiles < 64)
-	btpd_log(BTPD_L_BTPD,
-		 "You have restricted the number of open files to %d. "
-		 "More could be beneficial to the download performance.\n",
-		 nfiles);
-    btpd.maxpeers = nfiles - 20;
+static uint8_t m_peer_id[20];
+static struct event m_heartbeat;
+static struct event m_sigint;
+static struct event m_sigterm;
+static struct event m_sigchld;
+static struct child_tq m_kids = BTPDQ_HEAD_INITIALIZER(m_kids);
+static unsigned m_ntorrents;
+static struct torrent_tq m_cm_list = BTPDQ_HEAD_INITIALIZER(m_cm_list);
 
-    btpd.choke_msg = nb_create_choke();
-    nb_hold(btpd.choke_msg);
-    btpd.unchoke_msg = nb_create_unchoke();
-    nb_hold(btpd.unchoke_msg);
-    btpd.interest_msg = nb_create_interest();
-    nb_hold(btpd.interest_msg);
-    btpd.uninterest_msg = nb_create_uninterest();
-    nb_hold(btpd.uninterest_msg);
-}
+unsigned long btpd_seconds;
 
 void
 btpd_shutdown(void)
 {
     struct torrent *tp;
 
-    tp = BTPDQ_FIRST(&btpd.cm_list);
+    tp = BTPDQ_FIRST(&m_cm_list);
     while (tp != NULL) {
         struct torrent *next = BTPDQ_NEXT(tp, entry);
         torrent_unload(tp);
@@ -182,6 +69,16 @@ signal_cb(int signal, short type, void *arg)
     btpd_shutdown();
 }
 
+void
+btpd_add_child(pid_t pid, void (*cb)(pid_t, void *), void *arg)
+{
+    struct child *kid = btpd_calloc(1, sizeof(*kid));
+    kid->pid = pid;
+    kid->arg = arg;
+    kid->cb = cb;
+    BTPDQ_INSERT_TAIL(&m_kids, kid, entry);
+}
+
 static void
 child_cb(int signal, short type, void *arg)
 {
@@ -190,12 +87,13 @@ child_cb(int signal, short type, void *arg)
 
     while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
 	if (WIFEXITED(status) || WIFSIGNALED(status)) {
-	    struct child *kid = BTPDQ_FIRST(&btpd.kids);
+	    struct child *kid = BTPDQ_FIRST(&m_kids);
 	    while (kid != NULL && kid->pid != pid)
 		kid = BTPDQ_NEXT(kid, entry);
 	    assert(kid != NULL);
-	    BTPDQ_REMOVE(&btpd.kids, kid, entry);
-	    kid->child_done(kid);
+	    BTPDQ_REMOVE(&m_kids, kid, entry);
+	    kid->cb(kid->pid, kid->arg);
+	    free(kid);
 	}
     }
 }
@@ -205,224 +103,78 @@ heartbeat_cb(int sd, short type, void *arg)
 {
     struct torrent *tp;
 
-    btpd.seconds++;
+    btpd_seconds++;
 
-    net_bw_rate();
-
-    BTPDQ_FOREACH(tp, &btpd.cm_list, entry)
+    BTPDQ_FOREACH(tp, &m_cm_list, entry)
 	cm_by_second(tp);
 
-    evtimer_add(&btpd.heartbeat, (& (struct timeval) { 1, 0 }));
+    evtimer_add(&m_heartbeat, (& (struct timeval) { 1, 0 }));
 }
 
-static void
-usage()
+void
+btpd_add_torrent(struct torrent *tp)
 {
-    printf("Usage: btpd [options]\n"
-	"\n"
-	"Options:\n"
-	"\n"
-	"--bw-hz n\n"
-	"\tRun the bandwidth limiter at n hz.\n"
-	"\tDefault is 8 hz.\n"
-	"\n"
-	"--bw-in n\n"
-	"\tLimit incoming BitTorrent traffic to n kB/s.\n"
-	"\tDefault is 0 which means unlimited.\n"
-	"\n"
-	"--bw-out n\n"
-	"\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"
-	"\n"
-	"--ipc key\n"
-	"\tThe same key must be used by the cli to talk to this\n"
-	"\tbtpd instance. You shouldn't need to use this option.\n"
-	"\n"
-	"--logfile file\n"
-	"\tLog to the given file. By default btpd logs to ./btpd.log.\n"
-	"\n"
-	"-p n, --port n\n"
-	"\tListen at port n. Default is 6881.\n"
-	"\n"
-	"--help\n"
-	"\tShow this help.\n"
-	"\n");
-    exit(1);
+    BTPDQ_INSERT_TAIL(&m_cm_list, tp, entry);
+    m_ntorrents++;
 }
 
-static int longval = 0;
-
-static struct option longopts[] = {
-    { "port",	required_argument,	NULL,		'p' },
-    { "bw-hz",	required_argument,	&longval,	6 },
-    { "bw-in",	required_argument,	&longval,	1 },
-    { "bw-out",	required_argument,	&longval,	2 },
-    { "logfile", required_argument,	&longval,	3 },
-    { "ipc", 	required_argument,	&longval,	4 },
-    { "help",	no_argument,		&longval,	5 },
-    { NULL,	0,			NULL,		0 }
-};
-
-int
-main(int argc, char **argv)
+void 
+btpd_del_torrent(struct torrent *tp)
 {
-    int error, ch;
-    char *logfile = NULL, *ipc = NULL;
-    int d_opt = 0;
-
-    setlocale(LC_ALL, "");
-    btpd_init();
-
-    while ((ch = getopt_long(argc, argv, "dp:", longopts, NULL)) != -1) {
-	switch (ch) {
-	case 'd':
-	    d_opt = 1;
-	    break;
-	case 'p':
-	    btpd.port = atoi(optarg);
-	    break;
-	case 0:
-	    switch (longval) {
-	    case 1:
-		btpd.ibwlim = atoi(optarg) * 1024;
-		break;
-	    case 2:
-		btpd.obwlim = atoi(optarg) * 1024;
-		break;
-	    case 3:
-		logfile = optarg;
-		break;
-	    case 4:
-		ipc = optarg;
-		for (int i = 0; i < strlen(ipc); i++)
-		    if (!isalnum(ipc[i]))
-			btpd_err("--ipc only takes letters and digits.\n");
-		break;
-	    case 5:
-		usage();
-	    case 6:
-		btpd.bw_hz = atoi(optarg);
-		if (btpd.bw_hz <= 0 || btpd.bw_hz > 100)
-		    btpd_err("I will only accept bw limiter hz "
-			"between 1 and 100.\n");
-		break;
-	    default:
-		usage();
-	    }
-	    break;
-	case '?':
-	default:
-	    usage();
-	}
-    }
-    argc -= optind;
-    argv += optind;
-
-    if (argc != 0)
-	usage();
-
-    //net_init();
-    {
-	int sd;
-	int flag = 1;
-	struct sockaddr_in addr;
-	addr.sin_family = AF_INET;
-	addr.sin_addr.s_addr = htonl(INADDR_ANY);
-	addr.sin_port = htons(btpd.port);
+    BTPDQ_REMOVE(&m_cm_list, tp, entry);
+    m_ntorrents--;
+}
 
-	if ((sd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
-	    btpd_err("socket: %s\n", strerror(errno));
-	setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag));
-	if (bind(sd, (struct sockaddr *)&addr, sizeof(addr)) == -1)
-	    btpd_err("bind: %s\n", strerror(errno));
-	listen(sd, 10);
-	set_nonblocking(sd);
-	btpd.peer4_sd = sd;
-    }
+const struct torrent_tq *
+btpd_get_torrents(void)
+{
+    return &m_cm_list;
+}
 
-    //ipc_init();
-    {
-	int sd;
-	struct sockaddr_un addr;
-	size_t psiz = sizeof(addr.sun_path);
+unsigned
+btpd_get_ntorrents(void)
+{
+    return m_ntorrents;
+}
 
-	addr.sun_family = PF_UNIX;
-	if (ipc != NULL) {
-	    if (snprintf(addr.sun_path, psiz, "/tmp/btpd_%u_%s",
-			 geteuid(), ipc) >= psiz)
-		btpd_err("%s is too long.\n", ipc);
-	} else
-	    snprintf(addr.sun_path, psiz, "/tmp/btpd_%u_default", geteuid());
+struct torrent *
+btpd_get_torrent(const uint8_t *hash)
+{
+    struct torrent *tp = BTPDQ_FIRST(&m_cm_list);
+    while (tp != NULL && bcmp(hash, tp->meta.info_hash, 20) != 0)
+	tp = BTPDQ_NEXT(tp, entry);
+    return tp;
+}
 
-	if ((sd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
-	    btpd_err("sock: %s\n", strerror(errno));
-	if (bind(sd, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
-	    if (errno == EADDRINUSE) {
-		if (connect(sd, (struct sockaddr *)&addr, sizeof(addr)) == 0)
-		    btpd_err("btpd already running at %s.\n", addr.sun_path);
-		else {
-		    unlink(addr.sun_path);
-		    if (bind(sd, (struct sockaddr *)&addr, sizeof(addr)) != 0)
-			btpd_err("bind: %s\n", strerror(errno));
-		}
-	    } else
-		btpd_err("bind: %s\n", strerror(errno));
-	}
-	if (chmod(addr.sun_path, 0600) == -1)
-	    btpd_err("chmod: %s (%s).\n", addr.sun_path, strerror(errno));
-	listen(sd, 4);
-	set_nonblocking(sd);
-	btpd.ipc_sd = sd;
-    }
+const uint8_t *
+btpd_get_peer_id(void)
+{
+    return m_peer_id;
+}
 
-    freopen("/dev/null", "r", stdin);
-    if (logfile == NULL)
-	logfile = "btpd.log";
-    if (!d_opt) {
-	freopen(logfile, "w", stdout);
-	freopen(logfile, "w", stderr);
-	daemon(1, 1);
-    }
+extern void ipc_init(void);
 
-    setlinebuf(stdout);
-    setlinebuf(stderr);
+void
+btpd_init(void)
+{
+    bcopy(BTPD_VERSION, m_peer_id, sizeof(BTPD_VERSION) - 1);
+    m_peer_id[sizeof(BTPD_VERSION) - 1] = '|';
+    srandom(time(NULL));
+    for (int i = sizeof(BTPD_VERSION); i < 20; i++)
+	m_peer_id[i] = rint(random() * 255.0 / RAND_MAX);
 
-    event_init();
+    net_init();
+    ipc_init();
 
     signal(SIGPIPE, SIG_IGN);
 
-    signal_set(&btpd.sigint, SIGINT, signal_cb, NULL);
-    signal_add(&btpd.sigint, NULL);
-    signal_set(&btpd.sigterm, SIGTERM, signal_cb, NULL);
-    signal_add(&btpd.sigterm, NULL);
-    signal_set(&btpd.sigchld, SIGCHLD, child_cb, NULL);
-    signal_add(&btpd.sigchld, NULL);
-
-    evtimer_set(&btpd.heartbeat, heartbeat_cb,  NULL);
-    evtimer_add(&btpd.heartbeat, (& (struct timeval) { 1, 0 }));
-
-    event_set(&btpd.cli, btpd.ipc_sd, EV_READ | EV_PERSIST,
-        client_connection_cb, &btpd);
-    event_add(&btpd.cli, NULL);
-
-    event_set(&btpd.accept4, btpd.peer4_sd, EV_READ | EV_PERSIST,
-        net_connection_cb, &btpd);
-    event_add(&btpd.accept4, NULL);
-
-    evtimer_set(&btpd.bwlim, net_bw_cb, NULL);
-    if (btpd.obwlim > 0 || btpd.ibwlim > 0) {
-	btpd.ibw_left = btpd.ibwlim / btpd.bw_hz;
-	btpd.obw_left = btpd.obwlim / btpd.bw_hz;
-	evtimer_add(&btpd.bwlim,
-	    (& (struct timeval) { 0, 1000000 / btpd.bw_hz }));
-    }
-
-    error = event_dispatch();
-    btpd_err("Returned from dispatch. Error = %d.\n", error);
+    signal_set(&m_sigint, SIGINT, signal_cb, NULL);
+    signal_add(&m_sigint, NULL);
+    signal_set(&m_sigterm, SIGTERM, signal_cb, NULL);
+    signal_add(&m_sigterm, NULL);
+    signal_set(&m_sigchld, SIGCHLD, child_cb, NULL);
+    signal_add(&m_sigchld, NULL);
 
-    return error;
+    evtimer_set(&m_heartbeat, heartbeat_cb,  NULL);
+    evtimer_add(&m_heartbeat, (& (struct timeval) { 1, 0 }));
 }
diff --git a/btpd/btpd.h b/btpd/btpd.h
index 1a5c357..4b0a4a0 100644
--- a/btpd/btpd.h
+++ b/btpd/btpd.h
@@ -25,69 +25,11 @@
 #include "policy.h"
 #include "subr.h"
 
-#define BTPD_VERSION (PACKAGE_NAME "/" PACKAGE_VERSION)
-
-#define BWCALLHISTORY 5
-
-struct child {
-    pid_t pid;
-    void *data;
-    void (*child_done)(struct child *child);
-    BTPDQ_ENTRY(child) entry;
-};
-
-BTPDQ_HEAD(child_tq, child);
-
-struct btpd {
-    uint8_t peer_id[20];
-
-    const char *version;
-
-    uint32_t logmask;
-
-    struct child_tq kids;
-
-    unsigned ntorrents;
-    struct torrent_tq cm_list;
-
-    struct peer_tq readq;
-    struct peer_tq writeq;
-
-    struct peer_tq unattached;
+#include "opts.h"
 
-    int port;
-    int peer4_sd;
-    int ipc_sd;
-
-    unsigned bw_hz;
-    double bw_hz_avg;
-    unsigned bwcalls;
-    unsigned bwrate[BWCALLHISTORY];
-    unsigned long obwlim, ibwlim;
-    unsigned long ibw_left, obw_left;
-    struct event bwlim;    
-
-    unsigned npeers;
-    unsigned maxpeers;
-
-    unsigned long seconds;
-
-    struct event cli;
-    struct event accept4;
-
-    struct event heartbeat;
-
-    struct event sigint;
-    struct event sigterm;
-    struct event sigchld;
-
-    struct net_buf *choke_msg;
-    struct net_buf *unchoke_msg;
-    struct net_buf *interest_msg;
-    struct net_buf *uninterest_msg;
-};
+#define BTPD_VERSION (PACKAGE_NAME "/" PACKAGE_VERSION)
 
-extern struct btpd btpd;
+extern unsigned long btpd_seconds;
 
 #define BTPD_L_ALL	0xffffffff
 #define BTPD_L_ERROR	0x00000001
@@ -97,6 +39,8 @@ extern struct btpd btpd;
 #define BTPD_L_BTPD	0x00000010
 #define BTPD_L_POL	0x00000020
 
+void btpd_init(void);
+
 void btpd_log(uint32_t type, const char *fmt, ...);
 
 void btpd_err(const char *fmt, ...);
@@ -106,4 +50,13 @@ void *btpd_calloc(size_t nmemb, size_t size);
 
 void btpd_shutdown(void);
 
+void btpd_add_child(pid_t pid, void (*cb)(pid_t, void *), void *arg);
+
+struct torrent * btpd_get_torrent(const uint8_t *hash);
+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);
+const uint8_t *btpd_get_peer_id(void);
+
 #endif
diff --git a/btpd/cli_if.c b/btpd/cli_if.c
index 07662fe..a10d60b 100644
--- a/btpd/cli_if.c
+++ b/btpd/cli_if.c
@@ -1,6 +1,8 @@
 #include <sys/types.h>
 #include <sys/socket.h>
+#include <sys/un.h>
 #include <arpa/inet.h>
+#include <sys/stat.h>
 
 #include <inttypes.h>
 #include <limits.h>
@@ -17,6 +19,8 @@
 
 #define buf_swrite(iob, s) buf_write(iob, s, sizeof(s) - 1)
 
+static struct event m_cli_incoming;
+
 static void
 errdie(int error)
 {
@@ -32,11 +36,11 @@ cmd_stat(int argc, const char *args, FILE *fp)
     errdie(buf_init(&iob, (1 << 14)));
     
     errdie(buf_swrite(&iob, "d"));
-    errdie(buf_print(&iob, "6:npeersi%ue", btpd.npeers));
-    errdie(buf_print(&iob, "9:ntorrentsi%ue", btpd.ntorrents));
-    errdie(buf_print(&iob, "7:secondsi%lue", btpd.seconds));
+    errdie(buf_print(&iob, "6:npeersi%ue", net_npeers));
+    errdie(buf_print(&iob, "9:ntorrentsi%ue", btpd_get_ntorrents()));
+    errdie(buf_print(&iob, "7:secondsi%lue", btpd_seconds));
     errdie(buf_swrite(&iob, "8:torrentsl"));
-    BTPDQ_FOREACH(tp, &btpd.cm_list, entry) {
+    BTPDQ_FOREACH(tp, btpd_get_torrents(), entry) {
         uint32_t seen_npieces = 0;
         for (uint32_t i = 0; i < tp->meta.npieces; i++)
             if (tp->piece_count[i] > 0)
@@ -116,7 +120,7 @@ cmd_del(int argc, const char *args, FILE *fp)
             return;
         }
 
-        tp = torrent_get_by_hash(hash);
+        tp = btpd_get_torrent(hash);
         if (tp != NULL) {
 	    btpd_log(BTPD_L_BTPD, "del request for %s.\n", tp->relpath);
             torrent_unload(tp);
@@ -223,3 +227,35 @@ client_connection_cb(int sd, short type, void *arg)
 
     fclose(fp);
 }
+
+void
+ipc_init(void)
+{
+    int sd;
+    struct sockaddr_un addr;
+    size_t psiz = sizeof(addr.sun_path);
+
+    addr.sun_family = PF_UNIX;
+    if (snprintf(addr.sun_path, psiz, "%s/sock", btpd_dir) >= psiz)
+	btpd_err("'%s/sock' is too long.\n", btpd_dir);
+    
+    if ((sd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
+	btpd_err("sock: %s\n", strerror(errno));
+    if (bind(sd, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
+	if (errno == EADDRINUSE) {
+	    unlink(addr.sun_path);
+	    if (bind(sd, (struct sockaddr *)&addr, sizeof(addr)) != 0)
+		btpd_err("bind: %s\n", strerror(errno));
+	} else
+	    btpd_err("bind: %s\n", strerror(errno));
+    }
+
+    if (chmod(addr.sun_path, 0600) == -1)
+	btpd_err("chmod: %s (%s).\n", addr.sun_path, strerror(errno));
+    listen(sd, 4);
+    set_nonblocking(sd);
+
+    event_set(&m_cli_incoming, sd, EV_READ | EV_PERSIST,
+	client_connection_cb, NULL);
+    event_add(&m_cli_incoming, NULL);
+}
diff --git a/btpd/main.c b/btpd/main.c
new file mode 100644
index 0000000..8c75a3d
--- /dev/null
+++ b/btpd/main.c
@@ -0,0 +1,182 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <locale.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "btpd.h"
+
+static void
+writepid(int pidfd)
+{
+    FILE *fp = fdopen(dup(pidfd), "w");
+    fprintf(fp, "%d", getpid());
+    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)
+{
+    int pidfd;
+
+    if (dir == NULL)
+	dir = find_homedir();
+
+    btpd_dir = dir;
+
+    if (mkdir(dir, 0777) == -1 && errno != EEXIST)
+        err(1, "Couldn't create home '%s'", dir);
+
+    if (chdir(dir) != 0)
+        err(1, "Couldn't change working directory to '%s'", dir);
+
+    pidfd = open("pid", O_CREAT|O_WRONLY|O_NONBLOCK|O_EXLOCK, 0666);
+    if (pidfd == -1)
+        err(1, "Couldn't open 'pid'");
+
+    if (btpd_daemon) {
+        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'");
+        dup2(fileno(stdout), fileno(stderr));
+        setlinebuf(stdout);
+        setlinebuf(stderr);
+    }
+
+    writepid(pidfd);
+}
+
+static void
+usage(void)
+{
+    printf("Usage: btpd [options]\n"
+	"\n"
+	"Options:\n"
+	"\n"
+	"--bw-hz n\n"
+	"\tRun the bandwidth limiter at n hz.\n"
+	"\tDefault is 8 hz.\n"
+	"\n"
+	"--bw-in n\n"
+	"\tLimit incoming BitTorrent traffic to n kB/s.\n"
+	"\tDefault is 0 which means unlimited.\n"
+	"\n"
+	"--bw-out n\n"
+	"\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"
+	"\n"
+	"--ipc key\n"
+	"\tThe same key must be used by the cli to talk to this\n"
+	"\tbtpd instance. You shouldn't need to use this option.\n"
+	"\n"
+	"--logfile file\n"
+	"\tLog to the given file. By default btpd logs to ./btpd.log.\n"
+	"\n"
+	"-p n, --port n\n"
+	"\tListen at port n. Default is 6881.\n"
+	"\n"
+	"--help\n"
+	"\tShow this help.\n"
+	"\n");
+    exit(1);
+}
+
+static int longval = 0;
+
+static struct option longopts[] = {
+    { "port",	required_argument,	NULL,		'p' },
+    { "bw-hz",	required_argument,	&longval,	6 },
+    { "bw-in",	required_argument,	&longval,	1 },
+    { "bw-out",	required_argument,	&longval,	2 },
+    { "help",	no_argument,		&longval,	5 },
+    { NULL,	0,			NULL,		0 }
+};
+
+int
+main(int argc, char **argv)
+{
+    char *dir = NULL;
+
+    setlocale(LC_ALL, "");
+
+    for (;;) {
+	switch (getopt_long(argc, argv, "dp:", longopts, NULL)) {
+	case -1:
+	    goto args_done;
+	case 'd':
+	    btpd_daemon = 0;
+	    break;
+	case 'p':
+	    net_port = atoi(optarg);
+	    break;
+	case 0:
+	    switch (longval) {
+	    case 1:
+		net_bw_limit_in = atoi(optarg) * 1024;
+		break;
+	    case 2:
+		net_bw_limit_out = atoi(optarg) * 1024;
+		break;
+	    case 6:
+		net_bw_hz = atoi(optarg);
+		break;
+	    default:
+		usage();
+	    }
+	    break;
+	case '?':
+	default:
+	    usage();
+	}
+    }
+args_done:
+    argc -= optind;
+    argv += optind;
+
+    if (argc != 0)
+	usage();
+
+    setup_daemon(dir);
+
+    event_init();
+
+    btpd_init();
+
+    event_dispatch();
+    btpd_err("Unexpected exit from libevent.\n");
+
+    return 1;
+}
diff --git a/btpd/net.c b/btpd/net.c
index 3e32beb..2eb57cf 100644
--- a/btpd/net.c
+++ b/btpd/net.c
@@ -20,6 +20,18 @@
 
 #define min(x, y) ((x) <= (y) ? (x) : (y))
 
+static struct event m_bw_timer;
+static unsigned long m_bw_bytes_in;
+static unsigned long m_bw_bytes_out;
+
+static struct event m_net_incoming;
+
+unsigned net_npeers;
+
+struct peer_tq net_bw_readq = BTPDQ_HEAD_INITIALIZER(net_bw_readq);
+struct peer_tq net_bw_writeq = BTPDQ_HEAD_INITIALIZER(net_bw_writeq);
+struct peer_tq net_unattached = BTPDQ_HEAD_INITIALIZER(net_unattached);
+
 void
 net_write32(void *buf, uint32_t num)
 {
@@ -89,7 +101,7 @@ net_write(struct peer *p, unsigned long wmax)
 	    peer_sent(p, nl->nb);
 	    if (nl->nb->type == NB_TORRENTDATA) {
 		p->tp->uploaded += bufdelta;
-		p->rate_from_me[btpd.seconds % RATEHISTORY] += bufdelta;
+		p->rate_from_me[btpd_seconds % RATEHISTORY] += bufdelta;
 	    }
 	    bcount -= bufdelta;
 	    BTPDQ_REMOVE(&p->outq, nl, entry);
@@ -100,7 +112,7 @@ net_write(struct peer *p, unsigned long wmax)
 	} else {
 	    if (nl->nb->type == NB_TORRENTDATA) {
 		p->tp->uploaded += bcount;
-		p->rate_from_me[btpd.seconds % RATEHISTORY] += bcount;
+		p->rate_from_me[btpd_seconds % RATEHISTORY] += bcount;
 	    }
 	    p->outq_off +=  bcount;
 	    bcount = 0;
@@ -209,7 +221,7 @@ net_progress(struct peer *p, size_t length)
 {
     if (p->net.state == BTP_MSGBODY && p->net.msg_num == MSG_PIECE) {
 	p->tp->downloaded += length;
-	p->rate_to_me[btpd.seconds % RATEHISTORY] += length;
+	p->rate_to_me[btpd_seconds % RATEHISTORY] += length;
     }
 }
 
@@ -224,7 +236,7 @@ net_state(struct peer *p, const char *buf)
         break;
     case SHAKE_INFO:
 	if (p->flags & PF_INCOMING) {
-	    struct torrent *tp = torrent_get_by_hash(buf);
+	    struct torrent *tp = btpd_get_torrent(buf);
 	    if (tp == NULL)
 		goto bad;
 	    p->tp = tp;
@@ -235,7 +247,7 @@ net_state(struct peer *p, const char *buf)
         break;
     case SHAKE_ID:
 	if ((torrent_has_peer(p->tp, buf)
-             || bcmp(buf, btpd.peer_id, 20) == 0))
+             || bcmp(buf, btpd_get_peer_id(), 20) == 0))
 	    goto bad;
 	bcopy(buf, p->id, 20);
         peer_on_shake(p);
@@ -378,7 +390,7 @@ net_connect(const char *ip, int port, int *sd)
     struct addrinfo hints, *res;
     char portstr[6];
     
-    assert(btpd.npeers < btpd.maxpeers);
+    assert(net_npeers < net_max_peers);
 
     if (snprintf(portstr, sizeof(portstr), "%d", port) >= sizeof(portstr))
 	return EINVAL;
@@ -412,8 +424,8 @@ net_connection_cb(int sd, short type, void *arg)
 	return;
     }
 
-    assert(btpd.npeers <= btpd.maxpeers);
-    if (btpd.npeers == btpd.maxpeers) {
+    assert(net_npeers <= net_max_peers);
+    if (net_npeers == net_max_peers) {
 	close(nsd);
 	return;
     }
@@ -424,17 +436,13 @@ net_connection_cb(int sd, short type, void *arg)
 }
 
 void
-net_bw_rate(void)
+add_bw_timer(void)
 {
-    unsigned sum = 0;
-    for (int i = 0; i < BWCALLHISTORY - 1; i++) {
-	btpd.bwrate[i] = btpd.bwrate[i + 1];
-	sum += btpd.bwrate[i];
-    }
-    btpd.bwrate[BWCALLHISTORY - 1] = btpd.bwcalls;
-    sum += btpd.bwrate[BWCALLHISTORY - 1];
-    btpd.bwcalls = 0;
-    btpd.bw_hz_avg = sum / 5.0;
+    long wait = 1000000 / net_bw_hz;
+    struct timeval now;
+    gettimeofday(&now, NULL);
+    wait = wait - now.tv_usec % wait;
+    evtimer_add(&m_bw_timer, (& (struct timeval) { 0, wait}));
 }
 
 void
@@ -442,58 +450,50 @@ net_bw_cb(int sd, short type, void *arg)
 {
     struct peer *p;
 
-    btpd.bwcalls++;
-
-    double avg_hz;
-    if (btpd.seconds < BWCALLHISTORY)
-	avg_hz = btpd.bw_hz;
-    else
-	avg_hz = btpd.bw_hz_avg;
+    m_bw_bytes_out = net_bw_limit_out / net_bw_hz;
+    m_bw_bytes_in = net_bw_limit_in / net_bw_hz;
 
-    btpd.obw_left = btpd.obwlim / avg_hz;
-    btpd.ibw_left = btpd.ibwlim / avg_hz;
-
-    if (btpd.ibwlim > 0) {
-	while ((p = BTPDQ_FIRST(&btpd.readq)) != NULL && btpd.ibw_left > 0) {
-	    BTPDQ_REMOVE(&btpd.readq, p, rq_entry);
+    if (net_bw_limit_in > 0) {
+	while ((p = BTPDQ_FIRST(&net_bw_readq)) != NULL && m_bw_bytes_in > 0) {
+	    BTPDQ_REMOVE(&net_bw_readq, p, rq_entry);
 	    p->flags &= ~PF_ON_READQ;
-	    btpd.ibw_left -= net_read(p, btpd.ibw_left);
+	    m_bw_bytes_in -= net_read(p, m_bw_bytes_in);
 	}
     } else {
-	while ((p = BTPDQ_FIRST(&btpd.readq)) != NULL) {
-	    BTPDQ_REMOVE(&btpd.readq, p, rq_entry);
+	while ((p = BTPDQ_FIRST(&net_bw_readq)) != NULL) {
+	    BTPDQ_REMOVE(&net_bw_readq, p, rq_entry);
 	    p->flags &= ~PF_ON_READQ;
 	    net_read(p, 0);
 	}
     }
 
-    if (btpd.obwlim) {
-	while ((p = BTPDQ_FIRST(&btpd.writeq)) != NULL && btpd.obw_left > 0) {
-	    BTPDQ_REMOVE(&btpd.writeq, p, wq_entry);
+    if (net_bw_limit_out) {
+	while ((p = BTPDQ_FIRST(&net_bw_writeq)) != NULL && m_bw_bytes_out > 0) {
+	    BTPDQ_REMOVE(&net_bw_writeq, p, wq_entry);
 	    p->flags &= ~PF_ON_WRITEQ;
-	    btpd.obw_left -=  net_write(p, btpd.obw_left);
+	    m_bw_bytes_out -=  net_write(p, m_bw_bytes_out);
 	}
     } else {
-	while ((p = BTPDQ_FIRST(&btpd.writeq)) != NULL) {
-	    BTPDQ_REMOVE(&btpd.writeq, p, wq_entry);
+	while ((p = BTPDQ_FIRST(&net_bw_writeq)) != NULL) {
+	    BTPDQ_REMOVE(&net_bw_writeq, p, wq_entry);
 	    p->flags &= ~PF_ON_WRITEQ;
 	    net_write(p, 0);
 	}
     }
-    event_add(&btpd.bwlim, (& (struct timeval) { 0, 1000000 / btpd.bw_hz }));
+    add_bw_timer();
 }
 
 void
 net_read_cb(int sd, short type, void *arg)
 {
     struct peer *p = (struct peer *)arg;
-    if (btpd.ibwlim == 0)
+    if (net_bw_limit_in == 0)
 	net_read(p, 0);
-    else if (btpd.ibw_left > 0)
-	btpd.ibw_left -= net_read(p, btpd.ibw_left);
+    else if (m_bw_bytes_in > 0)
+	m_bw_bytes_in -= net_read(p, m_bw_bytes_in);
     else {
 	p->flags |= PF_ON_READQ;
-	BTPDQ_INSERT_TAIL(&btpd.readq, p, rq_entry);
+	BTPDQ_INSERT_TAIL(&net_bw_readq, p, rq_entry);
     }
 }
 
@@ -506,12 +506,53 @@ net_write_cb(int sd, short type, void *arg)
 	peer_kill(p);
 	return;
     }
-    if (btpd.obwlim == 0) {
+    if (net_bw_limit_out == 0) {
 	net_write(p, 0);
-    } else if (btpd.obw_left > 0) {
-	btpd.obw_left -= net_write(p, btpd.obw_left);
+    } else if (m_bw_bytes_out > 0) {
+	m_bw_bytes_out -= net_write(p, m_bw_bytes_out);
     } else {
 	p->flags |= PF_ON_WRITEQ;
-	BTPDQ_INSERT_TAIL(&btpd.writeq, p, wq_entry);
+	BTPDQ_INSERT_TAIL(&net_bw_writeq, p, wq_entry);
     }
 }
+
+void
+net_init(void)
+{
+    m_bw_bytes_out = net_bw_limit_out / net_bw_hz;
+    m_bw_bytes_in = net_bw_limit_in / net_bw_hz;
+
+    int nfiles = getdtablesize();
+    if (nfiles <= 20)
+	btpd_err("Too few open files allowed (%d). "
+		 "Check \"ulimit -n\"\n", nfiles);
+    else if (nfiles < 64)
+	btpd_log(BTPD_L_BTPD,
+		 "You have restricted the number of open files to %d. "
+		 "More could be beneficial to the download performance.\n",
+		 nfiles);
+    net_max_peers = nfiles - 20;
+
+    int sd;
+    int flag = 1;
+    struct sockaddr_in addr;
+    addr.sin_family = AF_INET;
+    addr.sin_addr.s_addr = htonl(INADDR_ANY);
+    addr.sin_port = htons(net_port);
+
+    if ((sd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
+	btpd_err("socket: %s\n", strerror(errno));
+    setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag));
+    if (bind(sd, (struct sockaddr *)&addr, sizeof(addr)) == -1)
+	btpd_err("bind: %s\n", strerror(errno));
+    listen(sd, 10);
+    set_nonblocking(sd);
+
+    event_set(&m_net_incoming, sd, EV_READ | EV_PERSIST,
+        net_connection_cb, NULL);
+    event_add(&m_net_incoming, NULL);
+
+    evtimer_set(&m_bw_timer, net_bw_cb, NULL);
+    if (net_bw_limit_out > 0 || net_bw_limit_in > 0)
+	add_bw_timer();
+}
diff --git a/btpd/net.h b/btpd/net.h
index 33b5424..b2ede1d 100644
--- a/btpd/net.h
+++ b/btpd/net.h
@@ -13,7 +13,10 @@
 
 #define WRITE_TIMEOUT (& (struct timeval) { 60, 0 })
 
-#define SHAKE_LEN 68
+extern struct peer_tq net_unattached;
+extern struct peer_tq net_bw_readq;
+extern struct peer_tq net_bw_writeq;
+extern unsigned net_npeers;
 
 enum net_state {
     SHAKE_PSTR,
@@ -27,8 +30,7 @@ enum net_state {
 
 void net_set_state(struct peer *p, enum net_state state, size_t size);
 
-void net_connection_cb(int sd, short type, void *arg);
-void net_bw_rate(void);
+void net_init(void);
 void net_bw_cb(int sd, short type, void *arg);
 
 void net_read_cb(int sd, short type, void *arg);
diff --git a/btpd/net_buf.c b/btpd/net_buf.c
index c879e10..06289df 100644
--- a/btpd/net_buf.c
+++ b/btpd/net_buf.c
@@ -3,10 +3,14 @@
 
 #include "btpd.h"
 
+static struct net_buf *m_choke;
+static struct net_buf *m_unchoke;
+static struct net_buf *m_interest;
+static struct net_buf *m_uninterest;
+
 static void
 kill_buf_no(char *buf, size_t len)
 {
-
 }
 
 static void
@@ -15,6 +19,12 @@ kill_buf_free(char *buf, size_t len)
     free(buf);
 }
 
+static void
+kill_buf_abort(char *buf, size_t len)
+{
+    abort();
+}
+
 static struct net_buf *
 nb_create_alloc(short type, size_t len)
 {
@@ -47,6 +57,13 @@ nb_create_onesized(char mtype, int btype)
     return out;
 }
 
+static struct net_buf *nb_singleton(struct net_buf *nb)
+{
+    nb_hold(nb);
+    nb->kill_buf = kill_buf_abort;
+    return nb;
+}
+
 struct net_buf *
 nb_create_piece(uint32_t index, uint32_t begin, size_t blen)
 {
@@ -119,25 +136,35 @@ nb_create_multihave(struct torrent *tp)
 struct net_buf *
 nb_create_unchoke(void)
 {
-    return nb_create_onesized(MSG_UNCHOKE, NB_UNCHOKE);
+    if (m_unchoke == NULL)
+	m_unchoke = nb_singleton(nb_create_onesized(MSG_UNCHOKE, NB_UNCHOKE));
+    return m_unchoke;
 }
 
 struct net_buf *
 nb_create_choke(void)
 {
-    return nb_create_onesized(MSG_CHOKE, NB_CHOKE);
+    if (m_choke == NULL)
+	m_choke = nb_singleton(nb_create_onesized(MSG_CHOKE, NB_CHOKE));
+    return m_choke;
 }
 
 struct net_buf *
 nb_create_uninterest(void)
 {
-    return nb_create_onesized(MSG_UNINTEREST, NB_UNINTEREST);
+    if (m_uninterest == NULL)
+	m_uninterest =
+	    nb_singleton(nb_create_onesized(MSG_UNINTEREST, NB_UNINTEREST));
+    return m_uninterest;
 }
 
 struct net_buf *
 nb_create_interest(void)
 {
-    return nb_create_onesized(MSG_INTEREST, NB_INTEREST);
+    if (m_interest == NULL)
+	m_interest =
+	    nb_singleton(nb_create_onesized(MSG_INTEREST, NB_INTEREST));
+    return m_interest;
 }
 
 struct net_buf *
@@ -166,7 +193,7 @@ nb_create_shake(struct torrent *tp)
     struct net_buf *out = nb_create_alloc(NB_SHAKE, 68);
     bcopy("\x13""BitTorrent protocol\0\0\0\0\0\0\0\0", out->buf, 28);
     bcopy(tp->meta.info_hash, out->buf + 28, 20);
-    bcopy(btpd.peer_id, out->buf + 48, 20);
+    bcopy(btpd_get_peer_id(), out->buf + 48, 20);
     return out;
 }
 
diff --git a/btpd/opts.c b/btpd/opts.c
new file mode 100644
index 0000000..e72daf9
--- /dev/null
+++ b/btpd/opts.c
@@ -0,0 +1,14 @@
+#include <btpd.h>
+
+short btpd_daemon = 1;
+const char *btpd_dir;
+#ifdef DEBUG
+uint32_t btpd_logmask = BTPD_L_ALL;
+#else
+uint32_t btpd_logmask =  BTPD_L_BTPD | BTPD_L_ERROR;
+#endif
+unsigned net_max_peers;
+unsigned net_bw_limit_in;
+unsigned net_bw_limit_out;
+short net_bw_hz = 8;
+int net_port = 6881;
diff --git a/btpd/opts.h b/btpd/opts.h
new file mode 100644
index 0000000..c24b13b
--- /dev/null
+++ b/btpd/opts.h
@@ -0,0 +1,8 @@
+extern short btpd_daemon;
+extern const char *btpd_dir;
+extern uint32_t btpd_logmask;
+extern unsigned net_max_peers;
+extern unsigned net_bw_limit_in;
+extern unsigned net_bw_limit_out;
+extern short net_bw_hz;
+extern int net_port;
diff --git a/btpd/peer.c b/btpd/peer.c
index 358523c..85a1666 100644
--- a/btpd/peer.c
+++ b/btpd/peer.c
@@ -28,11 +28,11 @@ peer_kill(struct peer *p)
     if (p->flags & PF_ATTACHED)
 	cm_on_lost_peer(p);
     else
-	BTPDQ_REMOVE(&btpd.unattached, p, cm_entry);
+	BTPDQ_REMOVE(&net_unattached, p, cm_entry);
     if (p->flags & PF_ON_READQ)
-	BTPDQ_REMOVE(&btpd.readq, p, rq_entry);
+	BTPDQ_REMOVE(&net_bw_readq, p, rq_entry);
     if (p->flags & PF_ON_WRITEQ)
-	BTPDQ_REMOVE(&btpd.writeq, p, wq_entry);
+	BTPDQ_REMOVE(&net_bw_writeq, p, wq_entry);
 
     close(p->sd);
     event_del(&p->in_ev);
@@ -51,7 +51,7 @@ peer_kill(struct peer *p)
     if (p->piece_field != NULL)
         free(p->piece_field);
     free(p);
-    btpd.npeers--;
+    net_npeers--;
 }
 
 void
@@ -88,7 +88,7 @@ peer_unsend(struct peer *p, struct nb_link *nl)
 	free(nl);
 	if (BTPDQ_EMPTY(&p->outq)) {
 	    if (p->flags & PF_ON_WRITEQ) {
-		BTPDQ_REMOVE(&btpd.writeq, p, wq_entry);
+		BTPDQ_REMOVE(&net_bw_writeq, p, wq_entry);
 		p->flags &= ~PF_ON_WRITEQ;
 	    } else
 		event_del(&p->out_ev);
@@ -194,7 +194,7 @@ void
 peer_unchoke(struct peer *p)
 {
     p->flags &= ~PF_I_CHOKE;
-    peer_send(p, btpd.unchoke_msg);
+    peer_send(p, nb_create_unchoke());
 }
 
 void
@@ -213,7 +213,7 @@ peer_choke(struct peer *p)
     }
 
     p->flags |= PF_I_CHOKE;
-    peer_send(p, btpd.choke_msg);
+    peer_send(p, nb_create_choke());
 }
 
 void
@@ -228,7 +228,7 @@ peer_want(struct peer *p, uint32_t index)
 	    if (nl != NULL && nl->nb->type == NB_UNINTEREST)
 		unsent = peer_unsend(p, nl);
 	    if (!unsent)
-		peer_send(p, btpd.interest_msg);
+		peer_send(p, nb_create_interest());
 	}
 	p->flags |= PF_I_WANT;
     }
@@ -242,7 +242,7 @@ peer_unwant(struct peer *p, uint32_t index)
     if (p->nwant == 0) {
 	p->flags &= ~PF_I_WANT;
 	if (p->nreqs_out == 0)
-	    peer_send(p, btpd.uninterest_msg);
+	    peer_send(p, nb_create_uninterest());
     }
 }
 
@@ -262,8 +262,8 @@ peer_create_common(int sd)
     event_set(&p->in_ev, p->sd, EV_READ, net_read_cb, p);
     event_add(&p->in_ev, NULL);
 
-    BTPDQ_INSERT_TAIL(&btpd.unattached, p, cm_entry);
-    btpd.npeers++;
+    BTPDQ_INSERT_TAIL(&net_unattached, p, cm_entry);
+    net_npeers++;
     return p;
 }
 
@@ -312,7 +312,7 @@ void
 peer_on_no_reqs(struct peer *p)
 {
     if (p->nwant == 0)
-	peer_send(p, btpd.uninterest_msg);
+	peer_send(p, nb_create_uninterest());
 }
 
 void
@@ -461,8 +461,8 @@ peer_on_request(struct peer *p, uint32_t index, uint32_t begin,
 	peer_send(p, nb_create_torrentdata(content, length));
 	p->npiece_msgs++;
 	if (p->npiece_msgs >= MAXPIECEMSGS) {
-	    peer_send(p, btpd.choke_msg);
-	    peer_send(p, btpd.unchoke_msg);
+	    peer_send(p, nb_create_choke());
+	    peer_send(p, nb_create_unchoke());
 	    p->flags |= PF_NO_REQUESTS;
 	}
     }
diff --git a/btpd/policy_choke.c b/btpd/policy_choke.c
index 64a2b0d..dccb1ea 100644
--- a/btpd/policy_choke.c
+++ b/btpd/policy_choke.c
@@ -67,7 +67,7 @@ choke_alg(struct torrent *tp)
 	}
     }
 
-    tp->choke_time = btpd.seconds + 10;
+    tp->choke_time = btpd_seconds + 10;
 }
 
 void
@@ -86,5 +86,5 @@ next_optimistic(struct torrent *tp, struct peer *np)
     }
     assert(tp->optimistic != NULL);
     choke_alg(tp);
-    tp->opt_time = btpd.seconds + 30;
+    tp->opt_time = btpd_seconds + 30;
 }
diff --git a/btpd/policy_if.c b/btpd/policy_if.c
index bcb0176..e660928 100644
--- a/btpd/policy_if.c
+++ b/btpd/policy_if.c
@@ -7,17 +7,17 @@
 void
 cm_by_second(struct torrent *tp)
 {
-    if (btpd.seconds == tp->tracker_time)
+    if (btpd_seconds == tp->tracker_time)
 	tracker_req(tp, TR_EMPTY);
 
-    if (btpd.seconds == tp->opt_time)
+    if (btpd_seconds == tp->opt_time)
 	next_optimistic(tp, NULL);
 
-    if (btpd.seconds == tp->choke_time)
+    if (btpd_seconds == tp->choke_time)
 	choke_alg(tp);
 
     struct peer *p;
-    int ri = btpd.seconds % RATEHISTORY;
+    int ri = btpd_seconds % RATEHISTORY;
 
     BTPDQ_FOREACH(p, &tp->peers, cm_entry) {
 	p->rate_to_me[ri] = 0;
@@ -192,7 +192,7 @@ cm_on_new_peer(struct peer *p)
 
     tp->npeers++;
     p->flags |= PF_ATTACHED;
-    BTPDQ_REMOVE(&btpd.unattached, p, cm_entry);
+    BTPDQ_REMOVE(&net_unattached, p, cm_entry);
 
     if (tp->npeers == 1) {
 	BTPDQ_INSERT_HEAD(&tp->peers, p, cm_entry);
diff --git a/btpd/torrent.c b/btpd/torrent.c
index 3712b5b..6fe5a35 100644
--- a/btpd/torrent.c
+++ b/btpd/torrent.c
@@ -62,10 +62,9 @@ torrent_load3(const char *file, struct metainfo *mi, char *mem, size_t memsiz)
     tp->meta = *mi;
     free(mi);
 
-    BTPDQ_INSERT_TAIL(&btpd.cm_list, tp, entry);
+    btpd_add_torrent(tp);
 
     tracker_req(tp, TR_STARTED);
-    btpd.ntorrents++;
 
     return 0;
 }
@@ -128,7 +127,7 @@ torrent_load(const char *file)
 	return error;
     }
 
-    if (torrent_get_by_hash(mi->info_hash) != NULL) {
+    if (btpd_get_torrent(mi->info_hash) != NULL) {
 	btpd_log(BTPD_L_BTPD, "%s has same hash as an already loaded torrent.\n", file);
 	error = EEXIST;
     }
@@ -158,12 +157,12 @@ torrent_unload(struct torrent *tp)
     while (peer != NULL) {
         struct peer *next = BTPDQ_NEXT(peer, cm_entry);
 	BTPDQ_REMOVE(&tp->peers, peer, cm_entry);
-	BTPDQ_INSERT_TAIL(&btpd.unattached, peer, cm_entry);
+	BTPDQ_INSERT_TAIL(&net_unattached, peer, cm_entry);
         peer->flags &= ~PF_ATTACHED;
         peer = next;
     }
 
-    peer = BTPDQ_FIRST(&btpd.unattached);
+    peer = BTPDQ_FIRST(&net_unattached);
     while (peer != NULL) {
 	struct peer *next = BTPDQ_NEXT(peer, cm_entry);
 	if (peer->tp == tp)
@@ -181,9 +180,8 @@ torrent_unload(struct torrent *tp)
 
     munmap(tp->imem, tp->isiz);
 
-    BTPDQ_REMOVE(&btpd.cm_list, tp, entry);
+    btpd_del_torrent(tp);
     free(tp);
-    btpd.ntorrents--;
 }
 
 off_t
@@ -241,15 +239,6 @@ torrent_has_peer(struct torrent *tp, const uint8_t *id)
     return has;
 }
 
-struct torrent *
-torrent_get_by_hash(const uint8_t *hash)
-{
-    struct torrent *tp = BTPDQ_FIRST(&btpd.cm_list);
-    while (tp != NULL && bcmp(hash, tp->meta.info_hash, 20) != 0)
-	tp = BTPDQ_NEXT(tp, entry);
-    return tp;
-}
-
 off_t
 torrent_piece_size(struct torrent *tp, uint32_t index)
 {
diff --git a/btpd/torrent.h b/btpd/torrent.h
index e5b7b2c..f535b4c 100644
--- a/btpd/torrent.h
+++ b/btpd/torrent.h
@@ -80,8 +80,6 @@ void torrent_unload(struct torrent *tp);
 
 int torrent_has_peer(struct torrent *tp, const uint8_t *id);
 
-struct torrent *torrent_get_by_hash(const uint8_t *hash);
-
 off_t torrent_piece_size(struct torrent *tp, uint32_t index);
 uint32_t torrent_block_size(struct piece *pc, uint32_t index);
 
diff --git a/btpd/tracker_req.c b/btpd/tracker_req.c
index 0e4fe57..7f0a74b 100644
--- a/btpd/tracker_req.c
+++ b/btpd/tracker_req.c
@@ -43,7 +43,7 @@ maybe_connect_to(struct torrent *tp, const char *pinfo)
     if (benc_dget_str(pinfo, "peer id", &pid, &len) != 0 || len != 20)
 	return;
 
-    if (bcmp(btpd.peer_id, pid, 20) == 0)
+    if (bcmp(btpd_get_peer_id(), pid, 20) == 0)
 	return;
 
     if (torrent_has_peer(tp, pid))
@@ -63,16 +63,16 @@ out:
 }
 
 static void
-tracker_done(struct child *child)
+tracker_done(pid_t pid, void *arg)
 {
-    struct tracker_req *req = child->data;
+    struct tracker_req *req = arg;
     int failed = 0;
     char *buf;
     const char *peers;
     uint32_t interval;
     struct torrent *tp;
 
-    if ((tp = torrent_get_by_hash(req->info_hash)) == NULL)
+    if ((tp = btpd_get_torrent(req->info_hash)) == NULL)
 	goto out;
 
     if (benc_validate(req->res->buf, req->res->buf_off) != 0
@@ -100,14 +100,14 @@ tracker_done(struct child *child)
 	goto out;
     }
 
-    tp->tracker_time = btpd.seconds + interval;
+    tp->tracker_time = btpd_seconds + interval;
 
     int error = 0;
     size_t length;
 
     if ((error = benc_dget_lst(req->res->buf, "peers", &peers)) == 0) {
 	for (peers = benc_first(peers);
-	     peers != NULL && btpd.npeers < btpd.maxpeers;
+	     peers != NULL && net_npeers < net_max_peers;
 	     peers = benc_next(peers))
 	    maybe_connect_to(tp, peers);
     }
@@ -116,7 +116,7 @@ tracker_done(struct child *child)
 	error = benc_dget_str(req->res->buf, "peers", &peers, &length);
 	if (error == 0 && length % 6 == 0) {
             size_t i;
-            for (i = 0; i < length && btpd.npeers < btpd.maxpeers; i += 6)
+            for (i = 0; i < length && net_npeers < net_max_peers; i += 6)
 		peer_create_out_compact(tp, peers + i);
 	}
     }
@@ -134,7 +134,7 @@ out:
 	        "Start request failed for %s.\n", tp->relpath);
 	    torrent_unload(tp);
 	} else
-	    tp->tracker_time = btpd.seconds + 10;
+	    tp->tracker_time = btpd_seconds + 10;
     }
     munmap(req->res, REQ_SIZE);
     free(req);
@@ -162,6 +162,7 @@ static int
 create_url(struct tracker_req *req, struct torrent *tp, char **url)
 {
     char e_hash[61], e_id[61];
+    const uint8_t *peer_id = btpd_get_peer_id();
     char qc;
     int i;
     uint64_t left;
@@ -175,7 +176,7 @@ create_url(struct tracker_req *req, struct torrent *tp, char **url)
 	snprintf(e_hash + i * 3, 4, "%%%.2x", tp->meta.info_hash[i]);
 
     for (i = 0; i < 20; i++)
-	snprintf(e_id + i * 3, 4, "%%%.2x", btpd.peer_id[i]);
+	snprintf(e_id + i * 3, 4, "%%%.2x", peer_id[i]);
 
     left = torrent_bytes_left(tp);
 
@@ -187,7 +188,7 @@ create_url(struct tracker_req *req, struct torrent *tp, char **url)
 		 "&left=%" PRIu64
 		 "&compact=1"
 		 "%s%s",
-		 tp->meta.announce, qc, e_hash, e_id, btpd.port,
+		 tp->meta.announce, qc, e_hash, e_id, net_port,
 		 tp->uploaded, tp->downloaded, left,
 		 req->tr_event == TR_EMPTY ? "" : "&event=",
 		 event);
@@ -231,7 +232,7 @@ http_helper(struct tracker_req *req, struct torrent *tp)
 
     err = curl_easy_setopt(handle, CURLOPT_URL, url);
     if (err == 0)
-	err = curl_easy_setopt(handle, CURLOPT_USERAGENT, btpd.version);
+	err = curl_easy_setopt(handle, CURLOPT_USERAGENT, BTPD_VERSION);
     if (err == 0)
 	err = curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, http_cb);
     if (err == 0)
@@ -276,13 +277,13 @@ void
 tracker_req(struct torrent *tp, enum tr_event tr_event)
 {
     struct tracker_req *req;
-    struct child *child;
+    pid_t pid;
 
     btpd_log(BTPD_L_TRACKER,
         "request for %s, event: %s.\n",
 	tp->relpath, event2str(tr_event));
 
-    req = (struct tracker_req *)btpd_calloc(1, sizeof(*req) + sizeof(*child));
+    req = (struct tracker_req *)btpd_calloc(1, sizeof(*req));
 
     req->res = mmap(NULL, REQ_SIZE, PROT_READ | PROT_WRITE,
         MAP_ANON | MAP_SHARED, -1, 0);
@@ -299,18 +300,14 @@ tracker_req(struct torrent *tp, enum tr_event tr_event)
 
     fflush(NULL);
 
-    child = (struct child *)(req + 1);
-    child->data = req;
-    child->child_done = tracker_done;
-    BTPDQ_INSERT_TAIL(&btpd.kids, child, entry);
-
-    child->pid = fork();
-    if (child->pid < 0) {
+    pid = fork();
+    if (pid < 0) {
 	btpd_err("Couldn't fork (%s).\n", strerror(errno));
-    } else if (child->pid == 0) { // Child
+    } else if (pid == 0) { // Child
 	int nfiles = getdtablesize();
 	for (int i = 0; i < nfiles; i++)
 	    close(i);
 	http_helper(req, tp);
-    }
+    } else
+	btpd_add_child(pid, tracker_done, req);
 }
diff --git a/btpd/util.c b/btpd/util.c
new file mode 100644
index 0000000..1cf6526
--- /dev/null
+++ b/btpd/util.c
@@ -0,0 +1,70 @@
+#include <stdarg.h>
+#include <stdio.h>
+
+#include "btpd.h"
+
+void *
+btpd_malloc(size_t size)
+{
+    void *a;
+    if ((a = malloc(size)) == NULL)
+	btpd_err("Failed to allocate %d bytes.\n", (int)size);
+    return a;
+}
+
+void *
+btpd_calloc(size_t nmemb, size_t size)
+{
+    void *a;
+    if ((a = calloc(nmemb, size)) == NULL)
+	btpd_err("Failed to allocate %d bytes.\n", (int)(nmemb * size));
+    return a;
+}
+
+static const char *
+logtype_str(uint32_t type)
+{
+    if (type & BTPD_L_BTPD)
+	return "btpd";
+    else if (type & BTPD_L_ERROR)
+	return "error";
+    else if (type & BTPD_L_CONN)
+	return "conn";
+    else if (type & BTPD_L_TRACKER)
+	return "tracker";
+    else if (type & BTPD_L_MSG)
+	return "msg";
+    else
+	return "";
+}
+
+void
+btpd_err(const char *fmt, ...)
+{
+    va_list ap;
+    va_start(ap, fmt);
+    if (BTPD_L_ERROR & btpd_logmask) {
+	char tbuf[20];
+	time_t tp = time(NULL);
+	strftime(tbuf, 20, "%b %e %T", localtime(&tp));
+	printf("%s %s: ", tbuf, logtype_str(BTPD_L_ERROR));
+	vprintf(fmt, ap);
+    }
+    va_end(ap);
+    exit(1);
+}
+
+void
+btpd_log(uint32_t type, const char *fmt, ...)
+{
+    va_list ap;
+    va_start(ap, fmt);
+    if (type & btpd_logmask) {
+	char tbuf[20];
+	time_t tp = time(NULL);
+	strftime(tbuf, 20, "%b %e %T", localtime(&tp));
+	printf("%s %s: ", tbuf, logtype_str(type));
+	vprintf(fmt, ap);
+    }
+    va_end(ap);
+}