about summary refs log tree commit diff
diff options
context:
space:
mode:
authorRichard Nyberg <rnyberg@murmeldjur.se>2005-11-07 14:57:39 +0000
committerRichard Nyberg <rnyberg@murmeldjur.se>2005-11-07 14:57:39 +0000
commit71b986e549d17919dfcf4e5e19f3a6b7337d255e (patch)
tree3351dd82afb1a0ef460bf50c83cd41701093e6f7
parent8033ec33a1f74afdd9968f815b4fb96c7b2e4391 (diff)
downloadbtpd-71b986e549d17919dfcf4e5e19f3a6b7337d255e.tar.gz
btpd-71b986e549d17919dfcf4e5e19f3a6b7337d255e.zip
Step one of many to make the sub systems more modular. This commit gets
rid of the global btpd struct. Some fields in the struct got a corresponding
global variable whereas some was made static and moved to a module.

The bandwidht algorithm also got tweaked. It now tries to fire the event
at specific times. This was to make the code simpler. It'll probably have
to be tweaked again :P

First step to make btpd run from a directory where it'll keep the log,
socket, configuration, data on the known torrents, etc. Btpd now uses
flock on the pidfile instead of connecting to the socket in order to
be reasonably sure that no other btpd runs in the same directory.

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