summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--COPYRIGHT54
-rw-r--r--Makefile.am2
-rw-r--r--README59
-rw-r--r--TODO21
-rw-r--r--btpd/Makefile.am15
-rw-r--r--btpd/btpd.c386
-rw-r--r--btpd/btpd.h91
-rw-r--r--btpd/cli_if.c225
-rw-r--r--btpd/net.c960
-rw-r--r--btpd/net.h97
-rw-r--r--btpd/peer.c178
-rw-r--r--btpd/peer.h64
-rw-r--r--btpd/policy.c706
-rw-r--r--btpd/policy.h22
-rw-r--r--btpd/queue.h81
-rw-r--r--btpd/torrent.c244
-rw-r--r--btpd/torrent.h67
-rw-r--r--btpd/tracker_req.c300
-rw-r--r--btpd/tracker_req.h13
-rw-r--r--cli/Makefile.am11
-rw-r--r--cli/btcli.c577
-rw-r--r--cli/btinfo.c53
-rw-r--r--cli/btpd_if.c221
-rw-r--r--cli/btpd_if.h21
-rw-r--r--configure.ac65
-rw-r--r--hack.mk4
-rw-r--r--misc/Makefile.am8
-rw-r--r--misc/benc.c345
-rw-r--r--misc/benc.h37
-rw-r--r--misc/iobuf.c64
-rw-r--r--misc/iobuf.h15
-rw-r--r--misc/metainfo.c275
-rw-r--r--misc/metainfo.h28
-rw-r--r--misc/stream.c241
-rw-r--r--misc/stream.h36
-rw-r--r--misc/subr.c141
-rw-r--r--misc/subr.h21
37 files changed, 5748 insertions, 0 deletions
diff --git a/COPYRIGHT b/COPYRIGHT
new file mode 100644
index 0000000..d8489df
--- /dev/null
+++ b/COPYRIGHT
@@ -0,0 +1,54 @@
+The btpd software is distributed under the following terms:
+
+Copyright (c) 2005 Richard Nyberg <rnyberg@gmail.com>. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
+
+Additionally, the file btpd/queue.h is distributed under the following
+terms:
+
+Copyright (c) 1991, 1993
+The Regents of the University of California.  All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+4. Neither the name of the University nor the names of its contributors
+   may be used to endorse or promote products derived from this software
+   without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..615eb32
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,2 @@
+SUBDIRS=misc btpd cli
+EXTRA_DIST=COPYRIGHT
diff --git a/README b/README
new file mode 100644
index 0000000..2cc1a60
--- /dev/null
+++ b/README
@@ -0,0 +1,59 @@
++ PROGRAMS
+
+btpd consists of the following programs:
+* btpd     - The BitTorrent client.
+* btcli    - Command line interface to btpd.
+* btinfo   - Shows information from a torrent file.
+
+All programs takes the "--help" option.
+
++ DIRECTORY STRUCTURE
+
+foo.torrent
+        The torrent metainfo file.
+
+foo.torrent.d
+        Content will be downloaded to, and uploaded from, this dir.
+        It and its subdirectories and files will be created by
+        btpd as it downloads them.
+
+foo.torrent.i
+        Created by 'btcli add'. Contains info on downloaded pieces.
+
++ SAMPLE USAGE
+
+NOTE: Don't start one instance of btpd per torrent. You should only
+need one instance regardless of how many torrents you want to share.
+
+Start btpd:
+# btpd
+
+Start downloading or seeding bar.torrent:
+# btcli add /path/to/bar.torrent
+
+List active torrents (only bar.torrent atm):
+# btcli list
+
+Show some stats:
+# btcli stat
+
+Stop downloading/seeding bar.torrent:
+# btcli del /path/to/bar.torrent
+
+Shut down btpd (Why would you do such a thing?):
+# btcli die
+
++ BUILDING
+
+Make sure you have recent versions of the following software:
+* curl      - Get at <URL:http://curl.haxx.se/>
+* openssl   - Get at <URL:http://www.openssl.org/>
+* libevent  - Get at <URL:http://www.monkey.org/~provos/libevent/>
+
+You also need a c99 compiler. A non antique GCC should do.
+
+# ./configure
+# make
+# make install
+
+See ./configure --help for options if it fails.
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..f4f372c
--- /dev/null
+++ b/TODO
@@ -0,0 +1,21 @@
+1.0 - TODO
+
+Better code for missing c99 int types. Evil OgreBSD!
+Fix spelling errors
+Should be able to listen on IPv6 too
+Calculate number of bytes left better
+Should pick random pieces, not rarest, at download start
+Man pages and other documentation.
+Do checksums in child process.
+Send keep alives
+Convert file names from UTF-8 to user locale
+Much, much,  better cli. Both code and usage wise.
+Close connections to seeders if we are seeding
+Better data structures
+General code cleanup, esp. the cli
+Improve build scripts
+Intelligent logging
+Better handling of unresponsive trackers
+Send multiple have messages instead of bitfield, when it's better
+Bitfields could be handled as a whole in policy.c.
+Other temporarily or permanently forgotten things...
diff --git a/btpd/Makefile.am b/btpd/Makefile.am
new file mode 100644
index 0000000..c227bea
--- /dev/null
+++ b/btpd/Makefile.am
@@ -0,0 +1,15 @@
+bin_PROGRAMS=btpd
+btpd_SOURCES=\
+	btpd.c btpd.h\
+	cli_if.c\
+	net.c net.h\
+	queue.h \
+	peer.c peer.h\
+	policy.c policy.h\
+	torrent.c torrent.h\
+	tracker_req.c tracker_req.h
+
+btpd_LDADD=../misc/libmisc.a -levent -lcrypto -lm
+btpd_CPPFLAGS=-I$(top_srcdir)/misc @event_CPPFLAGS@ @openssl_CPPFLAGS@
+btpd_CFLAGS=@CURL_CFLAGS@
+btpd_LDFLAGS=@event_LDFLAGS@ @openssl_LDFLAGS@ @CURL_LDFLAGS@
diff --git a/btpd/btpd.c b/btpd/btpd.c
new file mode 100644
index 0000000..db069d5
--- /dev/null
+++ b/btpd/btpd.c
@@ -0,0 +1,386 @@
+#include <sys/types.h>
+
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+
+#include <ctype.h>
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <math.h>
+#include <locale.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#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;
+
+    btpd.logmask = BTPD_L_BTPD | BTPD_L_ERROR;
+
+    TAILQ_INIT(&btpd.kids);
+
+    btpd.ntorrents = 0;
+    TAILQ_INIT(&btpd.cm_list);
+
+    TAILQ_INIT(&btpd.readq);
+    TAILQ_INIT(&btpd.writeq);
+
+    btpd.port = 6881;
+
+    btpd.obwlim = 0;
+    btpd.ibwlim = 0;
+    btpd.obw_left = 0;
+    btpd.ibw_left = 0;
+
+    btpd.npeers = 0;
+
+    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;
+}
+
+void
+btpd_shutdown(void)
+{
+    struct torrent *tp;
+
+    tp = TAILQ_FIRST(&btpd.cm_list);
+    while (tp != NULL) {
+        struct torrent *next = TAILQ_NEXT(tp, entry);
+        torrent_unload(tp);
+        tp = next;
+    }
+    btpd_log(BTPD_L_BTPD, "Exiting.\n");
+    exit(0);
+}
+
+static void
+signal_cb(int signal, short type, void *arg)
+{
+    btpd_log(BTPD_L_BTPD, "Got signal %d.\n", signal);
+    btpd_shutdown();
+}
+
+static void
+child_cb(int signal, short type, void *arg)
+{
+    int status;
+    pid_t pid;
+
+    while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
+	if (WIFEXITED(status) || WIFSIGNALED(status)) {
+	    struct child *kid = TAILQ_FIRST(&btpd.kids);
+	    while (kid != NULL && kid->pid != pid)
+		kid = TAILQ_NEXT(kid, entry);
+	    assert(kid != NULL);
+	    TAILQ_REMOVE(&btpd.kids, kid, entry);
+	    kid->child_done(kid);
+	}
+    }
+}
+
+static void
+heartbeat_cb(int sd, short type, void *arg)
+{
+    struct torrent *tp;
+    struct timeval begin, end, wadj;
+    gettimeofday(&begin, NULL);
+
+    btpd.seconds++;
+
+    TAILQ_FOREACH(tp, &btpd.cm_list, entry)
+	cm_by_second(tp);
+
+    net_by_second();
+
+    gettimeofday(&end, NULL);
+    timersub(&end, &begin, &wadj);
+    evtimer_add(&btpd.heartbeat,
+        (& (struct timeval) { 0, 1000000 - wadj.tv_usec }));
+}
+
+static void
+usage()
+{
+    printf("Usage: btpd [options]\n"
+	   "\n"
+	   "Options:\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"
+	   "--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-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)
+{
+    int error, ch;
+    char *logfile = NULL, *ipc = NULL;
+    
+    setlocale(LC_ALL, "");
+    btpd_init();
+
+    while ((ch = getopt_long(argc, argv, "p:", longopts, NULL)) != -1) {
+	switch (ch) {
+	case 'p':
+	    btpd.port = atoi(optarg);
+	    break;
+	case 0:
+	    switch (longval) {
+	    case 1:
+		btpd.ibwlim = atoi(optarg) * 1024;
+		btpd.ibw_left = btpd.ibwlim;
+		break;
+	    case 2:
+		btpd.obwlim = atoi(optarg) * 1024;
+		btpd.obw_left = btpd.obwlim;
+		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();
+	    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);
+
+	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;
+    }
+
+    //ipc_init();
+    {
+	int sd;
+	struct sockaddr_un addr;
+	size_t psiz = sizeof(addr.sun_path);
+
+	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());
+
+	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;
+    }
+
+    freopen("/dev/null", "r", stdin);
+    if (logfile == NULL)
+	logfile = "btpd.log";
+    freopen(logfile, "w", stdout);
+    freopen(logfile, "w", stderr);
+    setlinebuf(stdout);
+    setlinebuf(stderr);
+
+    daemon(1, 1);
+
+    event_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);
+
+    error = event_dispatch();
+    btpd_err("Returned from dispatch. Error = %d.\n", error);
+
+    return error;
+}
diff --git a/btpd/btpd.h b/btpd/btpd.h
new file mode 100644
index 0000000..438fae6
--- /dev/null
+++ b/btpd/btpd.h
@@ -0,0 +1,91 @@
+#ifndef BTPD_H
+#define BTPD_H
+
+#include <sys/types.h>
+#include <sys/time.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <event.h>
+#include <inttypes.h>
+#include <stddef.h>
+#include <stdlib.h>
+
+#include "queue.h"
+
+#include "benc.h"
+#include "metainfo.h"
+#include "iobuf.h"
+#include "net.h"
+#include "peer.h"
+#include "torrent.h"
+#include "policy.h"
+#include "subr.h"
+
+#define BTPD_VERSION (PACKAGE_NAME "/" PACKAGE_VERSION)
+
+struct child {
+    pid_t pid;
+    void (*child_done)(struct child *child);
+    TAILQ_ENTRY(child) entry;
+};
+
+TAILQ_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;
+
+    int port;
+    int peer4_sd;
+    int ipc_sd;
+
+    unsigned long obwlim, ibwlim;
+    unsigned long ibw_left, obw_left;
+    
+    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;
+};
+
+extern struct btpd btpd;
+
+#define BTPD_L_ALL	0xffffffff
+#define BTPD_L_ERROR	0x00000001
+#define BTPD_L_TRACKER	0x00000002
+#define BTPD_L_CONN	0x00000004
+#define BTPD_L_MSG	0x00000008
+#define BTPD_L_BTPD	0x00000010
+#define BTPD_L_POL	0x00000020
+
+void btpd_log(uint32_t type, const char *fmt, ...);
+
+void btpd_err(const char *fmt, ...);
+
+void *btpd_malloc(size_t size);
+void *btpd_calloc(size_t nmemb, size_t size);
+
+void btpd_shutdown(void);
+
+#endif
diff --git a/btpd/cli_if.c b/btpd/cli_if.c
new file mode 100644
index 0000000..8b94f97
--- /dev/null
+++ b/btpd/cli_if.c
@@ -0,0 +1,225 @@
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+
+#include <inttypes.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "btpd.h"
+
+#ifndef PRIu64
+#define PRIu64 "llu"
+#endif
+
+#define buf_swrite(iob, s) buf_write(iob, s, sizeof(s) - 1)
+
+static void
+errdie(int error)
+{
+    if (error != 0)
+	btpd_err("io_buf: %s.\n", strerror(error));
+}
+
+static void
+cmd_stat(int argc, const char *args, FILE *fp)
+{
+    struct torrent *tp;
+    struct io_buffer iob;
+    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_swrite(&iob, "8:torrentsl"));
+    TAILQ_FOREACH(tp, &btpd.cm_list, entry) {
+        uint32_t seen_npieces = 0;
+        for (uint32_t i = 0; i < tp->meta.npieces; i++)
+            if (tp->piece_count[i] > 0)
+                seen_npieces++;
+	errdie(buf_print(&iob, "d4:downi%" PRIu64 "e", tp->downloaded));
+	errdie(buf_swrite(&iob, "4:hash20:"));
+	errdie(buf_write(&iob, tp->meta.info_hash, 20));
+	errdie(buf_print(&iob, "12:have npiecesi%ue", tp->have_npieces));
+	errdie(buf_print(&iob, "6:npeersi%ue", tp->npeers));
+        errdie(buf_print(&iob, "7:npiecesi%ue", tp->meta.npieces));
+        errdie(buf_print(&iob, "4:path%d:%s",
+			 (int)strlen(tp->relpath), tp->relpath));
+	errdie(buf_print(&iob, "12:seen npiecesi%ue", seen_npieces));
+	errdie(buf_print(&iob, "2:upi%" PRIu64 "ee", tp->uploaded));
+    }
+    errdie(buf_swrite(&iob, "ee"));
+
+    uint32_t len = iob.buf_off;
+    fwrite(&len, sizeof(len), 1, fp);
+    fwrite(iob.buf, 1, iob.buf_off, fp);
+    free(iob.buf);
+}
+
+static void
+cmd_add(int argc, const char *args, FILE *fp)
+{
+    struct io_buffer iob;
+    errdie(buf_init(&iob, (1 << 10)));
+
+    errdie(buf_write(&iob, "l", 1));
+    while (args != NULL) {
+	size_t plen;
+	char path[PATH_MAX];
+	const char *pathp;
+
+	if (!benc_isstr(args)) {
+	    free(iob.buf);
+	    return;
+	}
+
+	benc_str(args, &pathp, &plen, &args);
+
+	if (plen >= PATH_MAX) {
+	    errdie(buf_print(&iob, "d4:codei%dee", ENAMETOOLONG));
+	    continue;
+	}
+	
+	bcopy(pathp, path, plen);
+	path[plen] = '\0';
+	btpd_log(BTPD_L_BTPD, "add request for %s.\n", path);
+	errdie(buf_print(&iob, "d4:codei%dee", torrent_load(path)));
+    }
+    errdie(buf_write(&iob, "e", 1));
+
+    uint32_t len = iob.buf_off;
+    fwrite(&len, sizeof(len), 1, fp);
+    fwrite(iob.buf, 1, iob.buf_off, fp);
+    free(iob.buf);    
+}
+
+static void
+cmd_del(int argc, const char *args, FILE *fp)
+{
+    struct io_buffer iob;
+    errdie(buf_init(&iob, (1 << 10)));
+    
+    errdie(buf_swrite(&iob, "l"));
+
+    while (args != NULL) {
+        size_t len;
+        const char *hash;
+        struct torrent *tp;
+
+        if (!benc_isstr(args) ||
+	    benc_str(args, &hash, &len, &args) != 0 || len != 20) {
+	    free(iob.buf);
+            return;
+        }
+
+        tp = torrent_get_by_hash(hash);
+        if (tp != NULL) {
+	    btpd_log(BTPD_L_BTPD, "del request for %s.\n", tp->relpath);
+            torrent_unload(tp);
+            errdie(buf_swrite(&iob, "d4:codei0ee"));
+        } else {
+	    btpd_log(BTPD_L_BTPD, "del request didn't match.\n");
+            errdie(buf_print(&iob, "d4:codei%dee", ENOENT));
+        }
+    }
+    errdie(buf_swrite(&iob, "e"));
+
+    uint32_t len = iob.buf_off;
+    fwrite(&len, sizeof(len), 1, fp);
+    fwrite(iob.buf, 1, iob.buf_off, fp);
+    free(iob.buf);
+}
+
+static void
+cmd_die(int argc, const char *args, FILE *fp)
+{
+    char res[] = "d4:codei0ee";
+    uint32_t len = sizeof(res) - 1;
+    fwrite(&len, sizeof(len), 1, fp);
+    fwrite(res, 1, len, fp);
+    fflush(fp);
+    btpd_log(BTPD_L_BTPD, "Someone wants me dead.\n");
+    btpd_shutdown();
+}
+
+static struct {
+    const char *name;
+    int nlen;
+    void (*fun)(int, const char *, FILE *);
+} cmd_table[] = {
+    { "add",	3, cmd_add },
+    { "del",	3, cmd_del },
+    { "die",	3, cmd_die },
+    { "stat",	4, cmd_stat }
+};
+
+static int ncmds = sizeof(cmd_table) / sizeof(cmd_table[0]);
+
+static void
+cmd_dispatch(const char *buf, FILE *fp)
+{
+    size_t cmdlen;
+    const char *cmd;
+    const char *args;
+    int found = 0;
+
+    benc_str(benc_first(buf), &cmd, &cmdlen, &args);
+
+    for (int i = 0; !found && i < ncmds; i++) {
+        if (cmdlen == cmd_table[i].nlen &&
+            strncmp(cmd_table[i].name, cmd, cmdlen) == 0) {
+            cmd_table[i].fun(benc_nelems(buf) - 1, args, fp);
+            found = 1;
+        }
+    }
+}
+
+static void
+do_ipc(FILE *fp)
+{
+    uint32_t cmdlen, nread;
+    char *buf;
+
+    if (fread(&cmdlen, sizeof(cmdlen), 1, fp) != 1)
+        return;
+
+    buf = btpd_malloc(cmdlen);
+
+    if ((nread = fread(buf, 1, cmdlen, fp)) == cmdlen) {
+        if (benc_validate(buf, cmdlen) == 0 && benc_islst(buf) &&
+            benc_first(buf) != NULL && benc_isstr(benc_first(buf)))
+            cmd_dispatch(buf, fp);
+    }
+
+    free(buf);
+}
+
+void
+client_connection_cb(int sd, short type, void *arg)
+{
+    int nsd;
+    FILE *fp;
+
+    if ((nsd = accept(sd, NULL, NULL)) < 0) {
+	if (errno == EWOULDBLOCK || errno == ECONNABORTED)
+	    return;
+	else
+            btpd_err("client accept: %s\n", strerror(errno));
+    }
+
+    if ((errno = set_blocking(nsd)) != 0)
+	btpd_err("set_blocking: %s.\n", strerror(errno));
+
+    if ((fp = fdopen(nsd, "r+")) == NULL) {
+	close(nsd);
+	return;
+    }
+   
+    do_ipc(fp);
+
+    fclose(fp);
+}
diff --git a/btpd/net.c b/btpd/net.c
new file mode 100644
index 0000000..458c591
--- /dev/null
+++ b/btpd/net.c
@@ -0,0 +1,960 @@
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+
+#include <sys/mman.h>
+#include <sys/wait.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "btpd.h"
+
+#define min(x, y) ((x) <= (y) ? (x) : (y))
+
+static unsigned long
+net_write(struct peer *p, unsigned long wmax);
+
+void
+net_read_cb(int sd, short type, void *arg)
+{
+    struct peer *p = (struct peer *)arg;
+    if (btpd.ibwlim == 0) {
+	p->reader->read(p, 0);
+    } else if (btpd.ibw_left > 0) {
+	btpd.ibw_left -= p->reader->read(p, btpd.ibw_left);
+    } else {
+	p->flags |= PF_ON_READQ;
+	TAILQ_INSERT_TAIL(&btpd.readq, p, rq_entry);
+    }
+}
+
+void
+net_write_cb(int sd, short type, void *arg)
+{
+    struct peer *p = (struct peer *)arg;
+    if (btpd.obwlim == 0) {
+	net_write(p, 0);
+    } else if (btpd.obw_left > 0) {
+	btpd.obw_left -= net_write(p, btpd.obw_left);
+    } else {
+	p->flags |= PF_ON_WRITEQ;
+	TAILQ_INSERT_TAIL(&btpd.writeq, p, wq_entry);
+    }
+}
+
+static void
+nokill_iob(struct io_buffer *iob)
+{
+    //Nothing
+}
+
+static void
+kill_free_buf(struct io_buffer *iob)
+{
+    free(iob->buf);
+}
+
+static struct iob_link *
+malloc_liob(size_t len)
+{
+    struct iob_link *iol;
+    iol = (struct iob_link *)btpd_malloc(sizeof(*iol) + len);
+    iol->iob.buf = (char *)iol + sizeof(*iol);
+    iol->iob.buf_len = len;
+    iol->iob.buf_off = 0;
+    iol->kill_buf = nokill_iob;
+    return iol;
+}
+
+static struct iob_link *
+salloc_liob(char *buf, size_t len, void (*kill_buf)(struct io_buffer *))
+{
+    struct iob_link *iol;
+    iol = (struct iob_link *)btpd_malloc(sizeof(*iol));
+    iol->iob.buf = buf;
+    iol->iob.buf_len = len;
+    iol->iob.buf_off = 0;
+    iol->kill_buf = kill_buf;
+    return iol;
+}
+
+void
+net_unsend_piece(struct peer *p, struct piece_req *req)
+{
+    struct iob_link *piece;
+
+    TAILQ_REMOVE(&p->p_reqs, req, entry);
+
+    piece = TAILQ_NEXT(req->head, entry);
+    TAILQ_REMOVE(&p->outq, piece, entry);
+    piece->kill_buf(&piece->iob);
+    free(piece);
+
+    TAILQ_REMOVE(&p->outq, req->head, entry);
+    req->head->kill_buf(&req->head->iob);
+    free(req->head);
+    free(req);
+
+    if (TAILQ_EMPTY(&p->outq)) {
+	if (p->flags & PF_ON_WRITEQ) {
+	    TAILQ_REMOVE(&btpd.writeq, p, wq_entry);
+	    p->flags &= ~PF_ON_WRITEQ;
+	} else
+	    event_del(&p->out_ev);
+    }
+}
+
+void
+kill_shake(struct input_reader *reader)
+{
+    free(reader);
+}
+
+#define NIOV 16
+
+static unsigned long
+net_write(struct peer *p, unsigned long wmax)
+{
+    struct iob_link *iol;
+    struct piece_req *req;
+    struct iovec iov[NIOV];
+    int niov;
+    int limited;
+    ssize_t nwritten;
+    unsigned long bcount;
+
+    limited = wmax > 0;
+
+    niov = 0;
+    assert((iol = TAILQ_FIRST(&p->outq)) != NULL);
+    while (niov < NIOV && iol != NULL
+	   && (!limited || (limited && wmax > 0))) {
+	iov[niov].iov_base = iol->iob.buf + iol->iob.buf_off;
+	iov[niov].iov_len = iol->iob.buf_len - iol->iob.buf_off;
+	if (limited) {
+	    if (iov[niov].iov_len > wmax)
+		iov[niov].iov_len = wmax;
+	    wmax -= iov[niov].iov_len;
+	}
+	niov++;
+	iol = TAILQ_NEXT(iol, entry);
+    }
+
+again:
+    nwritten = writev(p->sd, iov, niov);
+    if (nwritten < 0) {
+	if (errno == EINTR)
+	    goto again;
+	else if (errno == EAGAIN) {
+	    event_add(&p->out_ev, NULL);
+	    return 0;
+	} else {
+	    btpd_log(BTPD_L_CONN, "write error: %s\n", strerror(errno));
+	    peer_kill(p);
+	    return 0;
+	}
+    }
+
+    bcount = nwritten;
+    p->rate_from_me[btpd.seconds % RATEHISTORY] += nwritten;
+
+    req = TAILQ_FIRST(&p->p_reqs);
+    iol = TAILQ_FIRST(&p->outq);
+    while (bcount > 0) {
+	if (req != NULL && req->head == iol) {
+	    struct iob_link *piece = TAILQ_NEXT(req->head, entry);
+	    struct piece_req *next = TAILQ_NEXT(req, entry);
+	    TAILQ_REMOVE(&p->p_reqs, req, entry);
+	    free(req);
+	    req = next;
+	    p->tp->uploaded += piece->iob.buf_len;
+	}
+	if (bcount >= iol->iob.buf_len - iol->iob.buf_off) {
+	    bcount -= iol->iob.buf_len - iol->iob.buf_off;
+	    TAILQ_REMOVE(&p->outq, iol, entry);
+	    iol->kill_buf(&iol->iob);
+	    free(iol);
+	    iol = TAILQ_FIRST(&p->outq);
+	} else {
+	    iol->iob.buf_off += bcount;
+	    bcount = 0;
+	}
+    }
+    if (!TAILQ_EMPTY(&p->outq))
+	event_add(&p->out_ev, NULL);
+    else if (p->flags & PF_WRITE_CLOSE) {
+	btpd_log(BTPD_L_CONN, "Closed because of write flag.\n");
+	peer_kill(p);
+    }
+
+    return nwritten;
+}
+
+void
+net_send(struct peer *p, struct iob_link *iol)
+{
+    if (TAILQ_EMPTY(&p->outq))
+	event_add(&p->out_ev, NULL);
+    TAILQ_INSERT_TAIL(&p->outq, iol, entry);
+}
+
+void
+net_write32(void *buf, uint32_t num)
+{
+    *(uint32_t *)buf = htonl(num);
+}
+
+uint32_t
+net_read32(void *buf)
+{
+    return ntohl(*(uint32_t *)buf);
+}
+
+void
+net_send_piece(struct peer *p, uint32_t index, uint32_t begin,
+	       char *block, size_t blen)
+{
+    struct iob_link *head, *piece;
+    struct piece_req *req;
+
+    btpd_log(BTPD_L_MSG, "send piece: %u, %u, %u\n", index, begin, blen);
+
+    head = malloc_liob(13);
+    net_write32(head->iob.buf, 9 + blen);
+    head->iob.buf[4] = MSG_PIECE;
+    net_write32(head->iob.buf + 5, index);
+    net_write32(head->iob.buf + 9, begin);
+    net_send(p, head);
+
+    piece = salloc_liob(block, blen, kill_free_buf);
+    net_send(p, piece);
+
+    req = btpd_malloc(sizeof(*req));
+    req->index = index;
+    req->begin = begin;
+    req->length = blen;
+    req->head = head;
+    TAILQ_INSERT_TAIL(&p->p_reqs, req, entry);
+}
+
+void
+net_send_request(struct peer *p, struct piece_req *req)
+{
+    struct iob_link *out;
+    out = malloc_liob(17);
+    net_write32(out->iob.buf, 13);
+    out->iob.buf[4] = MSG_REQUEST;
+    net_write32(out->iob.buf + 5, req->index);
+    net_write32(out->iob.buf + 9, req->begin);
+    net_write32(out->iob.buf + 13, req->length);
+    net_send(p, out);
+}
+
+void
+net_send_cancel(struct peer *p, struct piece_req *req)
+{
+    struct iob_link *out;
+    out = malloc_liob(17);
+    net_write32(out->iob.buf, 13);
+    out->iob.buf[4] = MSG_CANCEL;
+    net_write32(out->iob.buf + 5, req->index);
+    net_write32(out->iob.buf + 9, req->begin);
+    net_write32(out->iob.buf + 13, req->length);
+    net_send(p, out);
+}
+
+void
+net_send_have(struct peer *p, uint32_t index)
+{
+    struct iob_link *out;
+    out = malloc_liob(9);
+    net_write32(out->iob.buf, 5);
+    out->iob.buf[4] = MSG_HAVE;
+    net_write32(out->iob.buf + 5, index);
+    net_send(p, out);
+}
+
+void
+net_send_onesized(struct peer *p, char type)
+{
+    struct iob_link *out;
+    out = malloc_liob(5);
+    net_write32(out->iob.buf, 1);
+    out->iob.buf[4] = type;
+    net_send(p, out);    
+}
+
+void
+net_send_unchoke(struct peer *p)
+{
+    net_send_onesized(p, MSG_UNCHOKE);
+}
+
+void
+net_send_choke(struct peer *p)
+{
+    net_send_onesized(p, MSG_CHOKE);
+}
+
+void
+net_send_uninterest(struct peer *p)
+{
+    net_send_onesized(p, MSG_UNINTEREST);
+}
+
+void
+net_send_interest(struct peer *p)
+{
+    net_send_onesized(p, MSG_INTEREST);
+}
+
+void
+net_send_bitfield(struct peer *p)
+{
+    struct iob_link *out;
+    uint32_t plen;
+
+    plen = (uint32_t)ceil(p->tp->meta.npieces / 8.0);
+    out = malloc_liob(5);
+    net_write32(out->iob.buf, plen + 1);
+    out->iob.buf[4] = MSG_BITFIELD;
+    net_send(p, out);
+    
+    out = salloc_liob(p->tp->piece_field, plen, nokill_iob);
+    net_send(p, out);
+}
+
+void
+net_send_shake(struct peer *p)
+{
+    struct iob_link *out;
+    out = malloc_liob(68);
+    bcopy("\x13""BitTorrent protocol\0\0\0\0\0\0\0\0", out->iob.buf, 28);
+    bcopy(p->tp->meta.info_hash, out->iob.buf + 28, 20);
+    bcopy(btpd.peer_id, out->iob.buf + 48, 20);
+    net_send(p, out);
+
+    if (p->tp->have_npieces > 0)
+	net_send_bitfield(p);
+}
+
+static void
+kill_generic(struct input_reader *reader)
+{
+    free(reader);
+}
+
+static size_t
+net_read(struct peer *p, char *buf, size_t len)
+{
+    ssize_t nread = read(p->sd, buf, len);
+    if (nread < 0) {
+	if (errno == EINTR || errno == EAGAIN) {
+	    event_add(&p->in_ev, NULL);
+	    return 0;
+	} else {
+	    btpd_log(BTPD_L_CONN, "read error: %s\n", strerror(errno));
+	    peer_kill(p);
+	    return 0;
+	}
+    } else if (nread == 0) {
+	btpd_log(BTPD_L_CONN, "conn closed by other side.\n");
+	if (!TAILQ_EMPTY(&p->outq))
+	    p->flags |= PF_WRITE_CLOSE;
+	else
+	    peer_kill(p);
+	return 0;
+    } else 
+	return nread;
+}
+
+void
+kill_bitfield(struct input_reader *rd)
+{
+    free(rd);
+}
+
+static void net_generic_reader(struct peer *p);
+
+static unsigned long
+read_bitfield(struct peer *p, unsigned long rmax)
+{
+    ssize_t nread;
+    struct bitfield_reader *rd = (struct bitfield_reader *)p->reader;
+    if (rmax == 0)
+	rmax = rd->iob.buf_len - rd->iob.buf_off;
+    else
+	rmax = min(rmax, rd->iob.buf_len - rd->iob.buf_off);
+
+    if ((nread = net_read(p, rd->iob.buf + rd->iob.buf_off, rmax)) == 0)
+	return 0;
+
+    rd->iob.buf_off += nread;
+    if (rd->iob.buf_off == rd->iob.buf_len) {
+	bcopy(rd->iob.buf, p->piece_field, rd->iob.buf_len);
+	for (unsigned i = 0; i < p->tp->meta.npieces; i++)
+	    if (has_bit(p->piece_field, i)) {
+		p->npieces++;
+		cm_on_piece_ann(p, i);
+	    }
+	free(rd);
+	net_generic_reader(p);
+    } else
+	event_add(&p->in_ev, NULL);
+
+    return nread;
+}
+
+void
+kill_piece(struct input_reader *rd)
+{
+    free(rd);
+}
+
+static unsigned long
+read_piece(struct peer *p, unsigned long rmax)
+{
+    ssize_t nread;
+    struct piece_reader *rd = (struct piece_reader *)p->reader;
+    if (rmax == 0)
+	rmax = rd->iob.buf_len - rd->iob.buf_off;
+    else
+	rmax = min(rmax, rd->iob.buf_len - rd->iob.buf_off);
+
+    if ((nread = net_read(p, rd->iob.buf + rd->iob.buf_off, rmax)) == 0)
+	return 0;
+
+    rd->iob.buf_off += nread;
+    p->rate_to_me[btpd.seconds % RATEHISTORY] += nread;
+    p->tp->downloaded += nread;
+    if (rd->iob.buf_off == rd->iob.buf_len) {
+	struct piece_req *req = TAILQ_FIRST(&p->my_reqs);
+	if (req != NULL &&
+	    req->index == rd->index &&
+	    req->begin == rd->begin &&
+	    req->length == rd->iob.buf_len) {
+	    //
+	    off_t cbegin = rd->index * p->tp->meta.piece_length + rd->begin;
+	    torrent_put_bytes(p->tp, rd->iob.buf, cbegin, rd->iob.buf_len);
+	    cm_on_block(p);
+	}
+	free(rd);
+	net_generic_reader(p);
+    } else
+	event_add(&p->in_ev, NULL);
+
+    return nread;
+}
+
+#define GRBUFLEN (1 << 15)
+
+static unsigned long
+net_generic_read(struct peer *p, unsigned long rmax)
+{
+    char buf[GRBUFLEN];
+    struct generic_reader *gr = (struct generic_reader *)p->reader;
+    ssize_t nread;
+    size_t off, len;
+    int got_part;
+
+    len = 0;
+    if (gr->iob.buf_off > 0) {
+	len = gr->iob.buf_off;
+	gr->iob.buf_off = 0;
+	bcopy(gr->iob.buf, buf, len);
+    }
+    
+    if (rmax == 0)
+	rmax = GRBUFLEN - len;
+    else
+	rmax = min(rmax, GRBUFLEN - len);
+
+    if ((nread = net_read(p, buf + len, rmax)) == 0)
+	return 0;
+
+    len += nread;
+    off = 0;
+
+    got_part = 0;
+    while (!got_part && len - off >= 4) {
+	size_t msg_len = net_read32(buf + off);
+
+	if (msg_len == 0) {	/* Keep alive */
+	    off += 4;
+	    continue;
+	}
+	if (len - off < 5) {
+	    got_part = 1;
+	    break;
+	}
+
+	switch (buf[off + 4]) {
+	case MSG_CHOKE:
+	    btpd_log(BTPD_L_MSG, "choke.\n");
+	    if (msg_len != 1)
+		goto bad_data;
+	    if ((p->flags & (PF_P_CHOKE|PF_I_WANT)) == PF_I_WANT) {
+		p->flags |= PF_P_CHOKE;
+		cm_on_undownload(p);
+	    } else
+		p->flags |= PF_P_CHOKE;
+	    break;
+	case MSG_UNCHOKE:
+	    btpd_log(BTPD_L_MSG, "unchoke.\n");
+	    if (msg_len != 1)
+		goto bad_data;
+	    if ((p->flags & (PF_P_CHOKE|PF_I_WANT))
+		== (PF_P_CHOKE|PF_I_WANT)) {
+		p->flags &= ~PF_P_CHOKE;
+		cm_on_download(p);
+	    } else
+		p->flags &= ~PF_P_CHOKE;
+	    break;
+	case MSG_INTEREST:
+	    btpd_log(BTPD_L_MSG, "interested.\n");
+	    if (msg_len != 1)
+		goto bad_data;
+	    if ((p->flags & (PF_P_WANT|PF_I_CHOKE)) == 0) {
+		p->flags |= PF_P_WANT;
+		cm_on_upload(p);
+	    } else 
+		p->flags |= PF_P_WANT;
+	    break;
+	case MSG_UNINTEREST:
+	    btpd_log(BTPD_L_MSG, "uninterested.\n");
+	    if (msg_len != 1)
+		goto bad_data;
+	    if ((p->flags & (PF_P_WANT|PF_I_CHOKE)) == PF_P_WANT) {
+		p->flags &= ~PF_P_WANT;
+		cm_on_unupload(p);
+	    } else
+		p->flags &= ~PF_P_WANT;
+	    break;
+	case MSG_HAVE:
+	    btpd_log(BTPD_L_MSG, "have.\n");
+	    if (msg_len != 5)
+		goto bad_data;
+	    else if (len - off >= msg_len + 4) {
+		unsigned long piece = net_read32(buf + off + 5);
+		if (!has_bit(p->piece_field, piece)) {
+		    set_bit(p->piece_field, piece);
+		    p->npieces++;
+		    cm_on_piece_ann(p, piece);
+		}
+	    } else
+		got_part = 1;
+	    break;
+	case MSG_BITFIELD:
+	    btpd_log(BTPD_L_MSG, "bitfield.\n");
+	    if (msg_len != (size_t)ceil(p->tp->meta.npieces / 8.0) + 1)
+		goto bad_data;
+	    else if (p->npieces != 0)
+		goto bad_data;
+	    else if (len - off >= msg_len + 4) {
+		bcopy(buf + off + 5, p->piece_field, msg_len - 1);
+		for (unsigned i = 0; i < p->tp->meta.npieces; i++)
+		    if (has_bit(p->piece_field, i)) {
+			p->npieces++;
+			cm_on_piece_ann(p, i);
+		    }
+	    } else {
+		struct bitfield_reader *rp;
+		size_t mem = sizeof(*rp) + msg_len - 1;
+		p->reader->kill(p->reader);
+		rp = btpd_calloc(1, mem);
+		rp->rd.kill = kill_bitfield;
+		rp->rd.read = read_bitfield;
+		rp->iob.buf = (char *)rp + sizeof(*rp);
+		rp->iob.buf_len = msg_len - 1;
+		rp->iob.buf_off = len - off - 5;
+		bcopy(buf + off + 5, rp->iob.buf, rp->iob.buf_off);
+		p->reader = (struct input_reader *)rp;
+		event_add(&p->in_ev, NULL);
+		return nread;
+	    }
+	    break;
+	case MSG_REQUEST:
+	    btpd_log(BTPD_L_MSG, "request.\n");
+	    if (msg_len != 13)
+		goto bad_data;
+	    else if (len - off >= msg_len + 4) {
+		if ((p->flags & (PF_P_WANT|PF_I_CHOKE)) != PF_P_WANT)
+		    break;
+		uint32_t index, begin, length;
+		off_t cbegin;
+		char *content;
+		index = net_read32(buf + off + 5);
+		begin = net_read32(buf + off + 9);
+		length = net_read32(buf + off + 13);
+		if (length > (1 << 15))
+		    goto bad_data;
+		if (index >= p->tp->meta.npieces
+		    || !has_bit(p->tp->piece_field, index))
+		    goto bad_data;
+		if (begin + length > p->tp->meta.piece_length)
+		    goto bad_data;
+		cbegin = index * p->tp->meta.piece_length + begin;
+		if (cbegin + length > p->tp->meta.total_length)
+		    goto bad_data;
+		content = torrent_get_bytes(p->tp, cbegin, length);
+		net_send_piece(p, index, begin, content, length);
+	    } else
+		got_part = 1;
+	    break;
+	case MSG_PIECE:
+	    btpd_log(BTPD_L_MSG, "piece.\n");
+	    if (msg_len < 10)
+		goto bad_data;
+	    else if (len - off >= 13) {
+		uint32_t index = net_read32(buf + off + 5);
+		uint32_t begin = net_read32(buf + off + 9);
+		uint32_t length = msg_len - 9;
+#if 0
+		struct piece_req *req = TAILQ_FIRST(&p->my_reqs);
+		if (req == NULL)
+		    goto bad_data;
+		if (!(index == req->index &&
+		      begin == req->begin &&
+		      length == req->length))
+		    goto bad_data;
+#endif
+		if (len - off >= msg_len + 4) {
+		    off_t cbegin = index * p->tp->meta.piece_length + begin;
+		    p->tp->downloaded += length;
+		    p->rate_to_me[btpd.seconds % RATEHISTORY] += length;
+		    struct piece_req *req = TAILQ_FIRST(&p->my_reqs);
+		    if (req != NULL &&
+			req->index == index &&
+			req->begin == begin &&
+			req->length == length) {
+			//
+			torrent_put_bytes(p->tp, buf + off + 13, cbegin, length);
+			cm_on_block(p);
+		    }
+		} else {
+		    struct piece_reader *rp;
+		    size_t mem = sizeof(*rp) + length;
+		    p->reader->kill(p->reader);
+		    rp = btpd_calloc(1, mem);
+		    rp->rd.kill = kill_piece;
+		    rp->rd.read = read_piece;
+		    rp->index = index;
+		    rp->begin = begin;
+		    rp->iob.buf = (char *)rp + sizeof(*rp);
+		    rp->iob.buf_len = length;
+		    rp->iob.buf_off = len - off - 13;
+		    bcopy(buf + off + 13, rp->iob.buf, rp->iob.buf_off);
+		    p->reader = (struct input_reader *)rp;
+		    event_add(&p->in_ev, NULL);
+		    p->tp->downloaded += rp->iob.buf_off;
+		    p->rate_to_me[btpd.seconds % RATEHISTORY] +=
+			rp->iob.buf_off;
+		    return nread;
+		}
+	    } else
+		got_part = 1;
+	    break;
+	case MSG_CANCEL:
+	    if (msg_len != 13)
+		goto bad_data;
+	    else if (len - off >= msg_len + 4) {
+		struct piece_req *req;
+		uint32_t index, begin, length;
+
+		index = net_read32(buf + off + 5);
+		begin = net_read32(buf + off + 9);
+		length = net_read32(buf + off + 13);
+
+		btpd_log(BTPD_L_MSG, "cancel: %u, %u, %u\n",
+		    index, begin, length);
+
+		req = TAILQ_FIRST(&p->p_reqs);
+		while (req != NULL) {
+		    if (req->index == index &&
+			req->begin == begin &&
+			req->length == length) {
+			btpd_log(BTPD_L_MSG, "cancel matched.\n");
+			net_unsend_piece(p, req);
+			break;
+		    }
+		    req = TAILQ_NEXT(req, entry);
+		}
+	    } else
+		got_part = 1;
+	    break;
+	default:
+	    goto bad_data;
+	}
+	if (!got_part)
+	    off += 4 + msg_len;
+    }
+    if (off != len) {
+	gr->iob.buf_off = len - off;
+	bcopy(buf + off, gr->iob.buf, gr->iob.buf_off);
+    }
+    event_add(&p->in_ev, NULL);
+    return nread;
+
+bad_data:
+    btpd_log(BTPD_L_MSG, "bad data\n");
+    peer_kill(p);
+    return nread;
+}
+
+static void
+net_generic_reader(struct peer *p)
+{
+    struct generic_reader *gr;
+    gr = btpd_calloc(1, sizeof(*gr));
+
+    gr->rd.read = net_generic_read;
+    gr->rd.kill = kill_generic;
+
+    gr->iob.buf = gr->_io_buf;
+    gr->iob.buf_len = MAX_INPUT_LEFT;
+    gr->iob.buf_off = 0;
+
+    p->reader = (struct input_reader *)gr;
+
+    event_add(&p->in_ev, NULL);
+}
+
+static unsigned long
+net_shake_read(struct peer *p, unsigned long rmax)
+{
+    ssize_t nread;
+    struct handshake *hs = (struct handshake *)p->reader;
+    struct io_buffer *in = &hs->in;
+
+    if (rmax == 0)
+	rmax = in->buf_len - in->buf_off;
+    else
+	rmax = min(rmax, in->buf_len - in->buf_off);
+
+    nread = net_read(p, in->buf + in->buf_off, rmax);
+    if (nread == 0)
+	return 0;
+
+    in->buf_off += nread;
+
+    switch (hs->state) {
+    case SHAKE_INIT:
+	if (in->buf_off < 20)
+	    break;
+	else if (bcmp(in->buf, "\x13""BitTorrent protocol", 20) == 0)
+	    hs->state = SHAKE_PSTR;
+	else
+	    goto bad_shake;
+    case SHAKE_PSTR:
+	if (in->buf_off < 28)
+	    break;
+	else
+	    hs->state = SHAKE_RESERVED;
+#if 0
+	else if (bcmp(in->buf + 20, "\0\0\0\0\0\0\0\0", 8) == 0)
+	    hs->state = SHAKE_RESERVED;
+	else
+	    goto bad_shake;
+#endif
+    case SHAKE_RESERVED:
+	if (in->buf_off < 48)
+	    break;
+	else if (hs->incoming) {
+	    struct torrent *tp = torrent_get_by_hash(in->buf + 28);
+#if 0
+	    tp = TAILQ_FIRST(&btpd.cm_list);
+	    while (tp != NULL) {
+		if (bcmp(in->buf + 28, tp->meta.info_hash, 20) == 0)
+		    break;
+		else
+		    tp = TAILQ_NEXT(tp, entry);
+	    }
+#endif
+	    if (tp != NULL) {
+		hs->state = SHAKE_INFO;
+		p->tp = tp;
+		net_send_shake(p);
+	    } else
+		goto bad_shake;
+	} else {
+	    if (bcmp(in->buf + 28, p->tp->meta.info_hash, 20) == 0)
+		hs->state = SHAKE_INFO;
+	    else
+		goto bad_shake;
+	}
+    case SHAKE_INFO:
+	if (in->buf_off < 68)
+	    break;
+	else {
+	    if (!hs->incoming && bcmp(in->buf + 48, p->id, 20) != 0)
+		goto bad_shake;
+	    else if (hs->incoming && torrent_has_peer(p->tp, in->buf + 48))
+		goto bad_shake; // Not really, but we are already connected
+	    if (hs->incoming)
+		bcopy(in->buf + 48, p->id, 20);
+	    hs->state = SHAKE_ID;
+	}
+    default:
+	assert(hs->state == SHAKE_ID);
+    }
+    if (hs->state == SHAKE_ID) {
+	btpd_log(BTPD_L_CONN, "Got whole shake.\n");
+	free(hs);
+	p->piece_field = btpd_calloc(1, (int)ceil(p->tp->meta.npieces / 8.0));
+	cm_on_new_peer(p);
+	net_generic_reader(p);
+    } else
+	event_add(&p->in_ev, NULL);
+
+    return nread;
+
+bad_shake:
+    btpd_log(BTPD_L_CONN, "Bad shake(%d)\n", hs->state);
+    peer_kill(p);
+    return nread;
+}
+
+
+void
+net_handshake(struct peer *p, int incoming)
+{
+    struct handshake *hs;
+
+    hs = calloc(1, sizeof(*hs));
+    hs->incoming = incoming;
+    hs->state = SHAKE_INIT;
+
+    hs->in.buf_len = SHAKE_LEN;
+    hs->in.buf_off = 0;
+    hs->in.buf = hs->_io_buf;
+
+    p->reader = (struct input_reader *)hs;
+    hs->rd.read = net_shake_read;
+    hs->rd.kill = kill_shake;
+
+    if (!incoming)
+	net_send_shake(p);
+}
+
+int
+net_connect(const char *ip, int port, int *sd)
+{
+    struct addrinfo hints, *res;
+    char portstr[6];
+    
+    assert(btpd.npeers < btpd.maxpeers);
+
+    if (snprintf(portstr, sizeof(portstr), "%d", port) >= sizeof(portstr))
+	return EINVAL;
+    bzero(&hints, sizeof(hints));
+    hints.ai_family = AF_UNSPEC;
+    hints.ai_flags = AI_NUMERICHOST;
+    hints.ai_socktype = SOCK_STREAM;
+    if (getaddrinfo(ip, portstr, &hints, &res) != 0)
+	return errno;
+
+    if (res) {
+	if ((*sd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
+	    btpd_log(BTPD_L_CONN, "Botched connection %s.", strerror(errno));
+	    freeaddrinfo(res);
+	    return errno;
+	}
+	set_nonblocking(*sd);
+	if (connect(*sd, res->ai_addr, res->ai_addrlen) == -1 &&
+	    errno != EINPROGRESS) {
+	    btpd_log(BTPD_L_CONN, "Botched connection %s.", strerror(errno));
+	    close(*sd);
+	    freeaddrinfo(res);
+	    return errno;
+	}
+    }
+	
+    freeaddrinfo(res);
+    btpd.npeers++;
+    return 0;
+}
+
+void
+net_connection_cb(int sd, short type, void *arg)
+{
+    int nsd;
+
+    nsd = accept(sd, NULL, NULL);
+    if (nsd < 0) {
+	if (errno == EWOULDBLOCK || errno == ECONNABORTED || errno == EINTR)
+	    return;
+	else
+	    btpd_err("accept4: %s\n", strerror(errno));
+    }
+
+    if (set_nonblocking(nsd) != 0) {
+	close(nsd);
+	return;
+    }
+
+    assert(btpd.npeers <= btpd.maxpeers);
+    if (btpd.npeers == btpd.maxpeers) {
+	close(nsd);
+	return;
+    }
+
+    btpd.npeers++;
+    peer_create_in(nsd);
+
+    btpd_log(BTPD_L_CONN, "got connection.\n");
+}
+
+void
+net_by_second(void)
+{
+    struct peer *p;
+    struct torrent *tp;
+    int ri = btpd.seconds % RATEHISTORY;
+
+    TAILQ_FOREACH(tp, &btpd.cm_list, entry) {
+	TAILQ_FOREACH(p, &tp->peers, cm_entry) {
+	    p->rate_to_me[ri] = 0;
+	    p->rate_from_me[ri] = 0;
+	}
+    }
+
+    btpd.obw_left = btpd.obwlim;
+    btpd.ibw_left = btpd.ibwlim;
+
+    if (btpd.ibwlim > 0) {
+	while ((p = TAILQ_FIRST(&btpd.readq)) != NULL && btpd.ibw_left > 0) {
+	    TAILQ_REMOVE(&btpd.readq, p, rq_entry);
+	    p->flags &= ~PF_ON_READQ;
+	    btpd.ibw_left -= p->reader->read(p, btpd.ibw_left);
+	}
+    } else {
+	while ((p = TAILQ_FIRST(&btpd.readq)) != NULL) {
+	    TAILQ_REMOVE(&btpd.readq, p, rq_entry);
+	    p->flags &= ~PF_ON_READQ;
+	    p->reader->read(p, 0);
+	}
+    }
+
+    if (btpd.obwlim) {
+	while ((p = TAILQ_FIRST(&btpd.writeq)) != NULL && btpd.obw_left > 0) {
+	    TAILQ_REMOVE(&btpd.writeq, p, wq_entry);
+	    p->flags &= ~PF_ON_WRITEQ;
+	    btpd.obw_left -=  net_write(p, btpd.obw_left);
+	}
+    } else {
+	while ((p = TAILQ_FIRST(&btpd.writeq)) != NULL) {
+	    TAILQ_REMOVE(&btpd.writeq, p, wq_entry);
+	    p->flags &= ~PF_ON_WRITEQ;
+	    net_write(p, 0);
+	}
+    }
+}
diff --git a/btpd/net.h b/btpd/net.h
new file mode 100644
index 0000000..e9b6b3b
--- /dev/null
+++ b/btpd/net.h
@@ -0,0 +1,97 @@
+#ifndef BTPD_NET_H
+#define BTPD_NET_H
+
+#define MSG_CHOKE	0
+#define MSG_UNCHOKE	1
+#define MSG_INTEREST	2
+#define MSG_UNINTEREST	3
+#define MSG_HAVE	4
+#define MSG_BITFIELD	5
+#define MSG_REQUEST	6
+#define MSG_PIECE	7
+#define MSG_CANCEL	8
+
+struct iob_link {
+    TAILQ_ENTRY(iob_link) entry;
+    void (*kill_buf)(struct io_buffer *);
+    struct io_buffer iob;
+};
+
+TAILQ_HEAD(io_tq, iob_link);
+
+struct peer;
+
+struct input_reader {
+    unsigned long (*read)(struct peer *, unsigned long);
+    void (*kill)(struct input_reader *);
+};
+
+struct bitfield_reader {
+    struct input_reader rd;
+    struct io_buffer iob;
+};
+
+struct piece_reader {
+    struct input_reader rd;
+    struct io_buffer iob;
+    uint32_t index;
+    uint32_t begin;
+};
+
+#define SHAKE_LEN 68
+
+enum shake_state {
+    SHAKE_INIT,
+    SHAKE_PSTR,
+    SHAKE_RESERVED,
+    SHAKE_INFO,
+    SHAKE_ID
+};
+
+struct handshake {
+    struct input_reader rd;
+    enum shake_state state;
+    int incoming;
+    struct io_buffer in;
+    char _io_buf[SHAKE_LEN];
+};
+
+#define MAX_INPUT_LEFT 12
+
+struct generic_reader {
+    struct input_reader rd;
+    struct io_buffer iob;
+    char _io_buf[MAX_INPUT_LEFT];
+};
+
+struct piece_req {
+    uint32_t index, begin, length;
+    struct iob_link *head; /* Pointer to outgoing piece. */
+    TAILQ_ENTRY(piece_req) entry;
+};
+
+TAILQ_HEAD(piece_req_tq, piece_req);
+
+void net_connection_cb(int sd, short type, void *arg);
+void net_by_second(void);
+
+struct peer;
+
+void net_send_uninterest(struct peer *p);
+void net_send_interest(struct peer *p);
+void net_send_unchoke(struct peer *p);
+void net_send_choke(struct peer *p);
+
+void net_send_have(struct peer *p, uint32_t index);
+void net_send_request(struct peer *p, struct piece_req *req);
+void net_send_cancel(struct peer *p, struct piece_req *req);
+
+void net_handshake(struct peer *p, int incoming);
+
+void net_read_cb(int sd, short type, void *arg);
+void net_write_cb(int sd, short type, void *arg);
+int net_connect(const char *ip, int port, int *sd);
+
+void net_unsend_piece(struct peer *p, struct piece_req *req);
+
+#endif
diff --git a/btpd/peer.c b/btpd/peer.c
new file mode 100644
index 0000000..18bfda5
--- /dev/null
+++ b/btpd/peer.c
@@ -0,0 +1,178 @@
+#include <sys/types.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "btpd.h"
+
+unsigned long
+peer_get_rate(unsigned long *rates)
+{
+    unsigned long ret = 0;
+    for (int i = 0; i < RATEHISTORY; i++)
+	ret += rates[i];
+    return ret;
+}
+
+void
+peer_kill(struct peer *p)
+{
+    struct iob_link *iol;
+    struct piece_req *req;
+
+    btpd_log(BTPD_L_CONN, "killed peer.\n");
+
+    if (p->flags & PF_ATTACHED)
+	cm_on_lost_peer(p);
+    if (p->flags & PF_ON_READQ)
+	TAILQ_REMOVE(&btpd.readq, p, rq_entry);
+    if (p->flags & PF_ON_WRITEQ)
+	TAILQ_REMOVE(&btpd.writeq, p, wq_entry);
+
+    close(p->sd);
+    event_del(&p->in_ev);
+    event_del(&p->out_ev);
+
+    iol = TAILQ_FIRST(&p->outq);
+    while (iol != NULL) {
+	struct iob_link *next = TAILQ_NEXT(iol, entry);
+	iol->kill_buf(&iol->iob);
+	free(iol);
+	iol = next;
+    }
+    req = TAILQ_FIRST(&p->p_reqs);
+    while (req != NULL) {
+	struct piece_req *next = TAILQ_NEXT(req, entry);
+	free(req);
+	req = next;
+    }
+    req = TAILQ_FIRST(&p->my_reqs);
+    while (req != NULL) {
+	struct piece_req *next = TAILQ_NEXT(req, entry);
+	free(req);
+	req = next;
+    }
+
+    p->reader->kill(p->reader);
+    if (p->piece_field != NULL)
+        free(p->piece_field);
+    free(p);
+    btpd.npeers--;
+}
+
+void
+peer_request(struct peer *p, uint32_t index, uint32_t begin, uint32_t len)
+{
+    struct piece_req *req = btpd_calloc(1, sizeof(*req));
+    req->index = index;
+    req->begin = begin;
+    req->length = len;
+    TAILQ_INSERT_TAIL(&p->my_reqs, req, entry);
+    net_send_request(p, req);
+}
+
+void
+peer_cancel(struct peer *p, uint32_t index, uint32_t begin, uint32_t len)
+{
+    struct piece_req *req;
+again:
+    req = TAILQ_FIRST(&p->my_reqs);
+    while (req != NULL &&
+	   !(index == req->index &&
+	     begin == req->begin &&
+	     len == req->length))
+	req = TAILQ_NEXT(req, entry);
+    if (req != NULL) {
+	net_send_cancel(p, req);
+	TAILQ_REMOVE(&p->my_reqs, req, entry);
+	free(req);
+	goto again;
+    }
+}
+
+void
+peer_have(struct peer *p, uint32_t index)
+{
+    net_send_have(p, index);
+}
+
+void
+peer_unchoke(struct peer *p)
+{
+    p->flags &= ~PF_I_CHOKE;
+    net_send_unchoke(p);
+}
+
+void
+peer_choke(struct peer *p)
+{
+    struct piece_req *req;
+
+    while ((req = TAILQ_FIRST(&p->p_reqs)) != NULL)
+	net_unsend_piece(p, req);
+
+    p->flags |= PF_I_CHOKE;
+    net_send_choke(p);    
+}
+
+void
+peer_want(struct peer *p, uint32_t index)
+{
+    p->nwant++;
+    if (p->nwant == 1) {
+	p->flags |= PF_I_WANT;
+	net_send_interest(p);
+    }
+}
+
+void
+peer_unwant(struct peer *p, uint32_t index)
+{
+    p->nwant--;
+    if (p->nwant == 0) {
+	p->flags &= ~PF_I_WANT;
+	net_send_uninterest(p);
+    }
+}
+
+static struct peer *
+peer_create_common(int sd)
+{
+    struct peer *p = btpd_calloc(1, sizeof(*p));
+
+    p->sd = sd;
+    p->flags = PF_I_CHOKE | PF_P_CHOKE;
+    TAILQ_INIT(&p->p_reqs);
+    TAILQ_INIT(&p->my_reqs);
+    TAILQ_INIT(&p->outq);
+
+    event_set(&p->out_ev, p->sd, EV_WRITE, net_write_cb, p);
+    event_set(&p->in_ev, p->sd, EV_READ, net_read_cb, p);
+    event_add(&p->in_ev, NULL);
+
+    return p;
+}
+
+void
+peer_create_in(int sd)
+{
+    struct peer *p = peer_create_common(sd);
+    net_handshake(p, 1);
+}
+
+void
+peer_create_out(struct torrent *tp,
+		const uint8_t *id,
+		const char *ip,
+		int port)
+{
+    int sd;
+    struct peer *p;
+
+    if (net_connect(ip, port, &sd) != 0)
+	return;
+
+    p = peer_create_common(sd);
+    p->tp = tp;
+    bcopy(id, p->id, 20);
+    net_handshake(p, 0);
+}
diff --git a/btpd/peer.h b/btpd/peer.h
new file mode 100644
index 0000000..dd89d1b
--- /dev/null
+++ b/btpd/peer.h
@@ -0,0 +1,64 @@
+#ifndef BTPD_PEER_H
+#define BTPD_PEER_H
+
+#define PF_I_WANT	0x01	/* We want to download from the peer */
+#define PF_I_CHOKE	0x02	/* We choke the peer */
+#define	PF_P_WANT	0x04	/* The peer wants to download from us */
+#define	PF_P_CHOKE	0x08	/* The peer is choking us */
+#define PF_ON_READQ	0x10
+#define PF_ON_WRITEQ	0x20
+#define PF_ATTACHED	0x40
+#define PF_WRITE_CLOSE	0x80	/* Close connection after writing all data */
+
+#define RATEHISTORY 20
+
+struct peer {
+    int sd;
+    uint8_t flags;
+    uint8_t *piece_field;
+    uint32_t npieces;
+    unsigned nwant;
+
+    uint8_t id[20];
+
+    struct torrent *tp;
+
+    struct piece_req_tq p_reqs, my_reqs;
+
+    struct io_tq outq;
+
+    struct event in_ev;
+    struct event out_ev;
+
+    struct input_reader *reader;
+
+    unsigned long rate_to_me[RATEHISTORY];
+    unsigned long rate_from_me[RATEHISTORY];
+
+    TAILQ_ENTRY(peer) cm_entry;
+
+    TAILQ_ENTRY(peer) rq_entry;
+    TAILQ_ENTRY(peer) wq_entry;
+};
+
+TAILQ_HEAD(peer_tq, peer);
+
+void peer_unchoke(struct peer *p);
+void peer_choke(struct peer *p);
+void peer_unwant(struct peer *p, uint32_t index);
+void peer_want(struct peer *p, uint32_t index);
+void peer_request(struct peer *p, uint32_t index,
+		  uint32_t begin, uint32_t len);
+void peer_cancel(struct peer *p, uint32_t index, uint32_t begin, uint32_t len);
+
+void peer_have(struct peer *p, uint32_t index);
+
+unsigned long peer_get_rate(unsigned long *rates);
+
+void peer_create_in(int sd);
+void peer_create_out(struct torrent *tp, const uint8_t *id,
+		     const char *ip, int port);
+
+void peer_kill(struct peer *p);
+
+#endif
diff --git a/btpd/policy.c b/btpd/policy.c
new file mode 100644
index 0000000..1b53a37
--- /dev/null
+++ b/btpd/policy.c
@@ -0,0 +1,706 @@
+#include <sys/types.h>
+#include <sys/mman.h>
+
+#include <openssl/sha.h>
+#include <fcntl.h>
+#include <math.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "btpd.h"
+#include "stream.h"
+#include "tracker_req.h"
+
+#define BLOCKLEN (1 << 14)
+
+static void cm_on_piece(struct torrent *tp, struct piece *piece);
+
+static void
+assign_piece_requests_eg(struct piece *piece, struct peer *peer)
+{
+    for (unsigned i = 0; i < piece->nblocks; i++) {
+	if (!has_bit(piece->have_field, i)) {
+	    uint32_t start = i * BLOCKLEN;
+	    uint32_t len;
+	    if (i < piece->nblocks - 1)
+		len = BLOCKLEN;
+	    else if (piece->index < peer->tp->meta.npieces - 1)
+		len = peer->tp->meta.piece_length - i * BLOCKLEN;
+	    else {
+		off_t piece_len =
+		    peer->tp->meta.total_length -
+		    peer->tp->meta.piece_length *
+		    (peer->tp->meta.npieces - 1);
+		len = piece_len - i * BLOCKLEN;
+	    }
+	    peer_request(peer, piece->index, start, len);
+	}
+    }
+}
+
+static void
+cm_assign_requests_eg(struct peer *peer)
+{
+    struct piece *piece;
+    TAILQ_FOREACH(piece, &peer->tp->getlst, entry) {
+	if (has_bit(peer->piece_field, piece->index)) {
+	    peer_want(peer, piece->index);
+	    if ((peer->flags & PF_P_CHOKE) == 0)
+		assign_piece_requests_eg(piece, peer);
+	}
+    }
+}
+
+static void
+cm_unassign_requests_eg(struct peer *peer)
+{
+    struct piece_req *req = TAILQ_FIRST(&peer->my_reqs);
+    while (req != NULL) {
+	struct piece_req *next = TAILQ_NEXT(req, entry);
+	free(req);
+	req = next;
+    }
+    TAILQ_INIT(&peer->my_reqs);
+}
+
+static void
+cm_enter_endgame(struct torrent *tp)
+{
+    struct peer *peer;
+    btpd_log(BTPD_L_POL, "Entering end game\n");
+    tp->endgame = 1;
+    TAILQ_FOREACH(peer, &tp->peers, cm_entry)
+	cm_assign_requests_eg(peer);
+}
+
+static int
+piece_full(struct piece *p)
+{
+    return p->ngot + p->nbusy == p->nblocks;
+}
+
+static int
+cm_should_schedule(struct torrent *tp)
+{
+    if (!tp->endgame) {
+	int should = 1;
+	struct piece *p = TAILQ_FIRST(&tp->getlst);
+	while (p != NULL) {
+	    if (!piece_full(p)) {
+		should = 0;
+		break;
+	    }
+	    p = TAILQ_NEXT(p, entry);
+	}
+	return should;
+    } else
+	return 0;
+}
+
+static void
+cm_on_peerless_piece(struct torrent *tp, struct piece *piece)
+{
+    if (!tp->endgame) {
+	assert(tp->piece_count[piece->index] == 0);
+	btpd_log(BTPD_L_POL, "peerless piece %u\n", piece->index);
+	msync(tp->imem, tp->isiz, MS_ASYNC);
+	TAILQ_REMOVE(&tp->getlst, piece, entry);
+	free(piece);
+	if (cm_should_schedule(tp))
+	    cm_schedule_piece(tp);
+    }
+}
+
+static int
+rate_cmp(unsigned long rate1, unsigned long rate2)
+{
+    if (rate1 < rate2)
+	return -1;
+    else if (rate1 == rate2)
+	return 0;
+    else
+	return 1;
+}
+
+static int
+dwnrate_cmp(const void *p1, const void *p2)
+{
+    unsigned long rate1 = peer_get_rate((*(struct peer **)p1)->rate_to_me);
+    unsigned long rate2 = peer_get_rate((*(struct peer **)p2)->rate_to_me);
+    return rate_cmp(rate1, rate2);
+}
+
+static int
+uprate_cmp(const void *p1, const void *p2)
+{
+    unsigned long rate1 = peer_get_rate((*(struct peer **)p1)->rate_from_me);
+    unsigned long rate2 = peer_get_rate((*(struct peer **)p2)->rate_from_me);
+    return rate_cmp(rate1, rate2);
+}
+
+static void
+choke_alg(struct torrent *tp)
+{
+    int i;
+    struct peer *p;
+    struct peer **psort;
+
+    assert(tp->npeers > 0);
+
+    psort = (struct peer **)btpd_malloc(tp->npeers * sizeof(p));
+    i = 0;
+    TAILQ_FOREACH(p, &tp->peers, cm_entry)
+	psort[i++] = p;
+    
+    if (tp->have_npieces == tp->meta.npieces)
+	qsort(psort, tp->npeers, sizeof(p), uprate_cmp);
+    else
+	qsort(psort, tp->npeers, sizeof(p), dwnrate_cmp);
+    
+    tp->ndown = 0;
+    if (tp->optimistic != NULL) {
+	if (tp->optimistic->flags & PF_I_CHOKE)
+	    peer_unchoke(tp->optimistic);
+	if (tp->optimistic->flags & PF_P_WANT)
+	    tp->ndown = 1;
+    }
+
+    for (i = tp->npeers - 1; i >= 0; i--) {
+	if (psort[i] == tp->optimistic)
+	    continue;
+	if (tp->ndown < 4) {
+	    if (psort[i]->flags & PF_P_WANT)
+		tp->ndown++;
+	    if (psort[i]->flags & PF_I_CHOKE)
+		peer_unchoke(psort[i]);
+	} else {
+	    if ((psort[i]->flags & PF_I_CHOKE) == 0)
+		peer_choke(psort[i]);
+	}
+    }
+    free(psort);
+
+    tp->choke_time = btpd.seconds + 10;
+}
+
+static void
+next_optimistic(struct torrent *tp, struct peer *np)
+{
+    if (np != NULL)
+	tp->optimistic = np;
+    else if (tp->optimistic == NULL)
+	tp->optimistic = TAILQ_FIRST(&tp->peers);
+    else {
+	np = TAILQ_NEXT(tp->optimistic, cm_entry);
+	if (np != NULL)
+	    tp->optimistic = np;
+	else
+	    tp->optimistic = TAILQ_FIRST(&tp->peers);
+    }
+    assert(tp->optimistic != NULL);
+    choke_alg(tp);
+    tp->opt_time = btpd.seconds + 30;
+}
+
+void
+cm_on_upload(struct peer *peer)
+{
+    choke_alg(peer->tp);
+}
+
+void
+cm_on_unupload(struct peer *peer)
+{
+    choke_alg(peer->tp);
+}
+
+void
+cm_by_second(struct torrent *tp)
+{
+    if (btpd.seconds == tp->tracker_time)
+	tracker_req(tp, TR_EMPTY);
+
+    if (btpd.seconds == tp->opt_time)
+	next_optimistic(tp, NULL);
+
+    if (btpd.seconds == tp->choke_time)
+	choke_alg(tp);
+}
+
+void
+cm_on_download(struct peer *peer)
+{
+    if (!peer->tp->endgame)
+	assert(cm_assign_requests(peer, 5) != 0);
+    else
+	cm_assign_requests_eg(peer);
+}
+
+void
+cm_on_undownload(struct peer *peer)
+{
+    if (!peer->tp->endgame)
+	cm_unassign_requests(peer);
+    else
+	cm_unassign_requests_eg(peer);
+}
+
+void
+cm_on_piece_ann(struct peer *peer, uint32_t piece)
+{
+    struct piece *p;
+    struct torrent *tp = peer->tp;
+
+    tp->piece_count[piece]++;
+
+    if (has_bit(tp->piece_field, piece))
+	return;
+
+    p = TAILQ_FIRST(&tp->getlst);
+    while (p != NULL && p->index != piece)
+	p = TAILQ_NEXT(p, entry);
+
+    if (p != NULL && tp->endgame) {
+	peer_want(peer, p->index);
+	if ((peer->flags & PF_P_CHOKE) == 0)
+	    cm_on_download(peer);
+    } else if (p != NULL && !piece_full(p)) {
+	peer_want(peer, p->index);
+	if ((peer->flags & PF_P_CHOKE) == 0 && TAILQ_EMPTY(&peer->my_reqs))
+	    cm_on_download(peer);
+    } else if (p == NULL && cm_should_schedule(tp))
+	cm_schedule_piece(tp);
+}
+
+void
+cm_on_lost_peer(struct peer *peer)
+{
+    struct torrent *tp = peer->tp;
+    struct piece *piece;
+
+    tp->npeers--;
+    peer->flags &= ~PF_ATTACHED;
+    if (tp->npeers == 0) {
+	TAILQ_REMOVE(&tp->peers, peer, cm_entry);
+	tp->optimistic = NULL;
+	tp->choke_time = tp->opt_time = 0;
+    } else if (tp->optimistic == peer) {
+	struct peer *next = TAILQ_NEXT(peer, cm_entry);
+	TAILQ_REMOVE(&tp->peers, peer, cm_entry);
+	next_optimistic(peer->tp, next);
+    } else if ((peer->flags & (PF_P_WANT|PF_I_CHOKE)) == PF_P_WANT) {
+	TAILQ_REMOVE(&tp->peers, peer, cm_entry);
+	cm_on_unupload(peer);
+    } else {
+	TAILQ_REMOVE(&tp->peers, peer, cm_entry);
+    }
+
+    for (size_t i = 0; i < peer->tp->meta.npieces; i++)
+	if (has_bit(peer->piece_field, i))
+	    tp->piece_count[i]--;
+
+    if ((peer->flags & (PF_I_WANT|PF_P_CHOKE)) == PF_I_WANT)
+	cm_on_undownload(peer);
+
+    for (piece = TAILQ_FIRST(&tp->getlst); piece;
+	 piece = TAILQ_NEXT(piece, entry)) {
+	if (has_bit(peer->piece_field, piece->index) &&
+	    tp->piece_count[piece->index] == 0)
+	    cm_on_peerless_piece(tp, piece);
+    }
+}
+
+void
+cm_on_new_peer(struct peer *peer)
+{
+    struct torrent *tp = peer->tp;
+
+    tp->npeers++;
+    peer->flags |= PF_ATTACHED;
+
+    if (tp->npeers == 1) {
+	TAILQ_INSERT_HEAD(&tp->peers, peer, cm_entry);
+	next_optimistic(peer->tp, peer);
+    } else {
+	if (random() > RAND_MAX / 3)
+	    TAILQ_INSERT_AFTER(&tp->peers, tp->optimistic, peer, cm_entry);
+	else
+	    TAILQ_INSERT_TAIL(&tp->peers, peer, cm_entry);
+    }
+}
+
+static int
+missing_piece(struct torrent *tp, uint32_t index)
+{
+    struct piece *p;
+    if (has_bit(tp->piece_field, index))
+	return 0;
+    TAILQ_FOREACH(p, &tp->getlst, entry)
+	if (p->index == index)
+	    return 0;
+    return 1;
+}
+
+static struct piece *
+alloc_piece(struct torrent *tp, uint32_t piece)
+{
+    struct piece *res;
+    size_t mem, field;
+    unsigned long nblocks;
+    off_t piece_length = tp->meta.piece_length;
+
+    if (piece == tp->meta.npieces - 1) {
+	off_t totl = tp->meta.total_length;
+	off_t npm1 = tp->meta.npieces - 1;
+	piece_length = totl - npm1 * piece_length;
+    }
+
+    nblocks = (unsigned)ceil((double)piece_length / BLOCKLEN);
+    field = (size_t)ceil(nblocks / 8.0);
+    mem = sizeof(*res) + field;
+
+    res = btpd_calloc(1, mem);
+    res->down_field = (uint8_t *)res + sizeof(*res);
+    res->have_field =
+	tp->block_field +
+	(size_t)ceil(piece * tp->meta.piece_length / (double)(1 << 17));
+    res->nblocks = nblocks;
+    res->index = piece;
+
+    for (unsigned i = 0; i < nblocks; i++)
+	if (has_bit(res->have_field, i))
+	    res->ngot++;
+
+    return res;
+}
+
+static void
+activate_piece_peers(struct torrent *tp, struct piece *piece)
+{
+    struct peer *peer;
+    assert(!piece_full(piece) && tp->endgame == 0);
+    TAILQ_FOREACH(peer, &tp->peers, cm_entry)
+	if (has_bit(peer->piece_field, piece->index))
+	    peer_want(peer, piece->index);
+    peer = TAILQ_FIRST(&tp->peers);
+    while (peer != NULL && !piece_full(piece)) {
+	if ((peer->flags & (PF_P_CHOKE|PF_I_WANT)) == PF_I_WANT &&
+	    TAILQ_EMPTY(&peer->my_reqs)) {
+	    //
+	    cm_on_download(peer);
+	}
+	peer = TAILQ_NEXT(peer, cm_entry);
+    }
+}
+
+void
+cm_schedule_piece(struct torrent *tp)
+{
+    uint32_t i;
+    uint32_t min_i;
+    unsigned min_c;
+    struct piece *piece;
+    int enter_end_game = 1;
+
+    assert(tp->endgame == 0);
+
+    for (i = 0; i < tp->meta.npieces; i++)
+	if (missing_piece(tp, i)) {
+	    enter_end_game = 0;
+	    if (tp->piece_count[i] > 0)
+		break;
+	}
+
+    if (i == tp->meta.npieces) {
+	if (enter_end_game)
+	    cm_enter_endgame(tp);
+	return;
+    }
+
+    min_i = i;
+    min_c = 1;
+    for(i++; i < tp->meta.npieces; i++) {
+	if (missing_piece(tp, i) && tp->piece_count[i] > 0) {
+	    if (tp->piece_count[i] == tp->piece_count[min_i])
+		min_c++;
+	    else if (tp->piece_count[i] < tp->piece_count[min_i]) {
+		min_i = i;
+		min_c = 1;
+	    }
+	}
+    }
+    if (min_c > 1) {
+	min_c = 1 + rint((double)random() * (min_c - 1) / RAND_MAX);
+	for (i = min_i; min_c > 0; i++) {
+	    if (missing_piece(tp, i) &&
+		tp->piece_count[i] == tp->piece_count[min_i]) {
+		//
+		min_c--;
+		min_i = i;
+	    }
+	}
+    }
+
+    btpd_log(BTPD_L_POL, "scheduled piece: %u.\n", min_i);
+    piece = alloc_piece(tp, min_i);
+    TAILQ_INSERT_HEAD(&tp->getlst, piece, entry);
+    if (piece->ngot == piece->nblocks) {
+	cm_on_piece(tp, piece);
+	if (cm_should_schedule(tp))
+	    cm_schedule_piece(tp);
+    } else
+	activate_piece_peers(tp, piece);
+}
+
+static void
+cm_on_piece_unfull(struct torrent *tp, struct piece *piece)
+{
+    activate_piece_peers(tp, piece);
+}
+
+static void
+cm_on_piece_full(struct torrent *tp, struct piece *piece)
+{
+    struct peer *p;
+
+    if (cm_should_schedule(tp))
+	cm_schedule_piece(tp);
+    TAILQ_FOREACH(p, &tp->peers, cm_entry) {
+	if (has_bit(p->piece_field, piece->index))
+	    peer_unwant(p, piece->index);
+    }
+}
+
+static int
+cm_assign_request(struct peer *peer)
+{
+    struct piece *piece;
+    unsigned i;
+    uint32_t start, len;
+
+    piece = TAILQ_FIRST(&peer->tp->getlst);
+    while (piece != NULL) {
+	if (!piece_full(piece) && has_bit(peer->piece_field, piece->index))
+	    break;
+	piece = TAILQ_NEXT(piece, entry);
+    }
+
+    if (piece == NULL)
+	return 0;
+
+    i = 0;
+    while(has_bit(piece->have_field, i) || has_bit(piece->down_field, i))
+	i++;
+
+    start = i * BLOCKLEN;
+
+    if (i < piece->nblocks - 1)
+	len = BLOCKLEN;
+    else if (piece->index < peer->tp->meta.npieces - 1)
+	len = peer->tp->meta.piece_length - i * BLOCKLEN;
+    else {
+	off_t piece_len =
+	    peer->tp->meta.total_length -
+	    peer->tp->meta.piece_length * (peer->tp->meta.npieces - 1);
+	len = piece_len - i * BLOCKLEN;
+    }
+
+    peer_request(peer, piece->index, start, len);
+    set_bit(piece->down_field, i);
+    piece->nbusy++;
+    
+    if (piece_full(piece))
+	cm_on_piece_full(peer->tp, piece);
+
+    return 1;
+}
+
+int
+cm_assign_requests(struct peer *peer, int nreqs)
+{
+    int onreqs = nreqs;
+
+    while (nreqs > 0 && cm_assign_request(peer))
+	nreqs--;
+
+    return onreqs - nreqs;
+}
+
+void
+cm_unassign_requests(struct peer *peer)
+{
+    struct torrent *tp = peer->tp;
+    struct piece *piece = TAILQ_FIRST(&tp->getlst);
+
+    while (piece != NULL) {
+	int was_full = piece_full(piece);
+
+	struct piece_req *req = TAILQ_FIRST(&peer->my_reqs);
+	while (req != NULL) {
+	    struct piece_req *next = TAILQ_NEXT(req, entry);
+
+	    if (piece->index == req->index) {
+		assert(has_bit(piece->down_field, req->begin / BLOCKLEN));
+		clear_bit(piece->down_field, req->begin / BLOCKLEN);
+		piece->nbusy--;
+		TAILQ_REMOVE(&peer->my_reqs, req, entry);
+		free(req);
+	    }
+	    
+	    req = next;
+	}
+	
+	if (was_full && !piece_full(piece))
+	    cm_on_piece_unfull(tp, piece);
+
+	piece = TAILQ_NEXT(piece, entry);
+    }
+
+    assert(TAILQ_EMPTY(&peer->my_reqs));
+}
+
+static int
+test_hash(struct torrent *tp, uint8_t *hash, unsigned long index)
+{
+    if (tp->meta.piece_hash != NULL)
+	return memcmp(hash, tp->meta.piece_hash[index], SHA_DIGEST_LENGTH);
+    else {
+	char piece_hash[SHA_DIGEST_LENGTH];
+	int fd;
+	int bufi;
+	int err;
+
+	err = vopen(&fd, O_RDONLY, "%s", tp->relpath);
+	if (err != 0)
+	    btpd_err("test_hash: %s\n", strerror(err));
+
+	err = lseek(fd, tp->meta.pieces_off + index * SHA_DIGEST_LENGTH,
+	    SEEK_SET);
+	if (err < 0)
+	    btpd_err("test_hash: %s\n", strerror(errno));
+
+	bufi = 0;
+	while (bufi < SHA_DIGEST_LENGTH) {
+	    ssize_t nread =
+		read(fd, piece_hash + bufi, SHA_DIGEST_LENGTH - bufi);
+	    bufi += nread;
+	}
+	close(fd);
+
+	return memcmp(hash, piece_hash, SHA_DIGEST_LENGTH);
+    }
+}
+
+static int
+ro_fd_cb(const char *path, int *fd, void *arg)
+{
+    struct torrent *tp = arg;
+    return vopen(fd, O_RDONLY, "%s.d/%s", tp->relpath, path);
+}
+
+static void
+cm_on_piece(struct torrent *tp, struct piece *piece)
+{
+    int err;
+    uint8_t hash[20];
+    struct bt_stream_ro *bts;
+    off_t plen = tp->meta.piece_length;
+    if (piece->index == tp->meta.npieces - 1) {
+	plen =
+	    tp->meta.total_length -
+	    tp->meta.piece_length * (tp->meta.npieces - 1);
+    }
+    if ((bts = bts_open_ro(&tp->meta, piece->index * tp->meta.piece_length,
+			   ro_fd_cb, tp)) == NULL)
+	btpd_err("Out of memory.\n");
+
+
+    if ((err = bts_sha(bts, plen, hash)) != 0)
+	btpd_err("Ouch! %s\n", strerror(err));
+
+    bts_close_ro(bts);
+
+    if (test_hash(tp, hash, piece->index) == 0) {
+	btpd_log(BTPD_L_POL, "Got piece: %u.\n", piece->index);
+	struct peer *p;
+	set_bit(tp->piece_field, piece->index);
+	tp->have_npieces++;
+	if (tp->have_npieces == tp->meta.npieces) {
+	    btpd_log(BTPD_L_BTPD, "Finished: %s.\n", tp->relpath);
+	    tracker_req(tp, TR_COMPLETED);
+	}
+	msync(tp->imem, tp->isiz, MS_ASYNC);
+	TAILQ_FOREACH(p, &tp->peers, cm_entry)
+	    peer_have(p, piece->index);
+	if (tp->endgame)
+	    TAILQ_FOREACH(p, &tp->peers, cm_entry)
+		peer_unwant(p, piece->index);
+	TAILQ_REMOVE(&tp->getlst, piece, entry);
+	free(piece);
+    } else if (tp->endgame) {
+	struct peer *p;
+	btpd_log(BTPD_L_ERROR, "Bad hash for piece %u of %s.\n",
+	    piece->index, tp->relpath);
+	for (unsigned i = 0; i < piece->nblocks; i++)
+	    clear_bit(piece->have_field, i);
+	piece->ngot = 0;
+	TAILQ_FOREACH(p, &tp->peers, cm_entry)
+	    if (has_bit(p->piece_field, piece->index) &&
+		(p->flags & PF_P_CHOKE) == 0) {
+		//
+		assign_piece_requests_eg(piece, p);
+	    }
+    } else {
+	btpd_log(BTPD_L_ERROR, "Bad hash for piece %u of %s.\n",
+	    piece->index, tp->relpath);
+	for (unsigned i = 0; i < piece->nblocks; i++) {
+	    clear_bit(piece->have_field, i);
+	    assert(!has_bit(piece->down_field, i));
+	}
+	msync(tp->imem, tp->isiz, MS_ASYNC);
+	TAILQ_REMOVE(&tp->getlst, piece, entry);
+	free(piece);
+	if (cm_should_schedule(tp))
+	    cm_schedule_piece(tp);
+    }
+}
+
+void
+cm_on_block(struct peer *peer)
+{
+    struct torrent *tp = peer->tp;
+    struct piece_req *req = TAILQ_FIRST(&peer->my_reqs);
+    struct piece *piece = TAILQ_FIRST(&tp->getlst);
+    unsigned block = req->begin / BLOCKLEN;
+    while (piece != NULL && piece->index != req->index)
+        piece = TAILQ_NEXT(piece, entry);
+    set_bit(piece->have_field, block);
+    clear_bit(piece->down_field, block);
+    piece->ngot++;
+    piece->nbusy--;
+    if (tp->endgame) {
+	uint32_t index = req->index;
+	uint32_t begin = req->begin;
+	uint32_t length = req->length;
+	struct peer *p;
+
+	TAILQ_REMOVE(&peer->my_reqs, req, entry);
+	free(req);
+
+	TAILQ_FOREACH(p, &tp->peers, cm_entry) {
+	    if (has_bit(p->piece_field, index) &&
+		(peer->flags & PF_P_CHOKE) == 0)
+		peer_cancel(p, index, begin, length);
+	}
+	if (piece->ngot == piece->nblocks)
+	    cm_on_piece(tp, piece);
+    } else {
+	TAILQ_REMOVE(&peer->my_reqs, req, entry);
+	free(req);
+	if (piece->ngot == piece->nblocks)
+	    cm_on_piece(tp, piece);
+	if ((peer->flags & (PF_I_WANT|PF_P_CHOKE)) == PF_I_WANT)
+	    cm_assign_requests(peer, 1);
+    }
+}
diff --git a/btpd/policy.h b/btpd/policy.h
new file mode 100644
index 0000000..6ef850a
--- /dev/null
+++ b/btpd/policy.h
@@ -0,0 +1,22 @@
+#ifndef BTPD_POLICY_H
+#define BTPD_POLICY_H
+
+void cm_by_second(struct torrent *tp);
+
+void cm_on_new_peer(struct peer *peer);
+void cm_on_lost_peer(struct peer *peer);
+
+void cm_on_upload(struct peer *peer);
+void cm_on_unupload(struct peer *peer);
+
+void cm_on_download(struct peer *peer);
+void cm_on_undownload(struct peer *peer);
+void cm_on_piece_ann(struct peer *peer, uint32_t piece);
+void cm_on_block(struct peer *peer);
+
+void cm_schedule_piece(struct torrent *tp);
+int cm_assign_requests(struct peer *peer, int nreqs);
+	    
+void cm_unassign_requests(struct peer *peer);
+
+#endif
diff --git a/btpd/queue.h b/btpd/queue.h
new file mode 100644
index 0000000..e05a35a
--- /dev/null
+++ b/btpd/queue.h
@@ -0,0 +1,81 @@
+/*
+ *	@(#)queue.h	8.5 (Berkeley) 8/20/94
+ * $FreeBSD: src/sys/sys/queue.h,v 1.58.2.1 2005/01/31 23:26:57 imp Exp $
+ */
+
+#ifndef BTPD_QUEUE_H
+#define BTPD_QUEUE_H
+
+/*
+ * Tail queue declarations.
+ */
+#define	TAILQ_HEAD(name, type)						\
+struct name {								\
+	struct type *tqh_first;	/* first element */			\
+	struct type **tqh_last;	/* addr of last next element */		\
+}
+
+#define	TAILQ_HEAD_INITIALIZER(head)					\
+	{ NULL, &(head).tqh_first }
+
+#define	TAILQ_ENTRY(type)						\
+struct {								\
+	struct type *tqe_next;	/* next element */			\
+	struct type **tqe_prev;	/* address of previous next element */	\
+}
+
+#define	TAILQ_EMPTY(head)	((head)->tqh_first == NULL)
+
+#define	TAILQ_FIRST(head)	((head)->tqh_first)
+
+#define	TAILQ_NEXT(elm, field) ((elm)->field.tqe_next)
+
+#define	TAILQ_FOREACH(var, head, field)					\
+	for ((var) = TAILQ_FIRST((head));				\
+	    (var);							\
+	    (var) = TAILQ_NEXT((var), field))
+
+#define	TAILQ_INIT(head) do {						\
+	TAILQ_FIRST((head)) = NULL;					\
+	(head)->tqh_last = &TAILQ_FIRST((head));			\
+} while (0)
+
+#define	TAILQ_INSERT_AFTER(head, listelm, elm, field) do {		\
+	if ((TAILQ_NEXT((elm), field) = TAILQ_NEXT((listelm), field)) != NULL)\
+		TAILQ_NEXT((elm), field)->field.tqe_prev = 		\
+		    &TAILQ_NEXT((elm), field);				\
+	else {								\
+		(head)->tqh_last = &TAILQ_NEXT((elm), field);		\
+	}								\
+	TAILQ_NEXT((listelm), field) = (elm);				\
+	(elm)->field.tqe_prev = &TAILQ_NEXT((listelm), field);		\
+} while (0)
+
+#define	TAILQ_INSERT_HEAD(head, elm, field) do {			\
+	if ((TAILQ_NEXT((elm), field) = TAILQ_FIRST((head))) != NULL)	\
+		TAILQ_FIRST((head))->field.tqe_prev =			\
+		    &TAILQ_NEXT((elm), field);				\
+	else								\
+		(head)->tqh_last = &TAILQ_NEXT((elm), field);		\
+	TAILQ_FIRST((head)) = (elm);					\
+	(elm)->field.tqe_prev = &TAILQ_FIRST((head));			\
+} while (0)
+
+#define	TAILQ_INSERT_TAIL(head, elm, field) do {			\
+	TAILQ_NEXT((elm), field) = NULL;				\
+	(elm)->field.tqe_prev = (head)->tqh_last;			\
+	*(head)->tqh_last = (elm);					\
+	(head)->tqh_last = &TAILQ_NEXT((elm), field);			\
+} while (0)
+
+#define	TAILQ_REMOVE(head, elm, field) do {				\
+	if ((TAILQ_NEXT((elm), field)) != NULL)				\
+		TAILQ_NEXT((elm), field)->field.tqe_prev = 		\
+		    (elm)->field.tqe_prev;				\
+	else {								\
+		(head)->tqh_last = (elm)->field.tqe_prev;		\
+	}								\
+	*(elm)->field.tqe_prev = TAILQ_NEXT((elm), field);		\
+} while (0)
+
+#endif
diff --git a/btpd/torrent.c b/btpd/torrent.c
new file mode 100644
index 0000000..a45f15a
--- /dev/null
+++ b/btpd/torrent.c
@@ -0,0 +1,244 @@
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <math.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <openssl/sha.h>
+
+#include "btpd.h"
+#include "tracker_req.h"
+#include "stream.h"
+
+static int
+ro_fd_cb(const char *path, int *fd, void *arg)
+{
+    struct torrent *tp = arg;
+    return vopen(fd, O_RDONLY, "%s.d/%s", tp->relpath, path);
+}
+
+static int
+wo_fd_cb(const char *path, int *fd, void *arg)
+{
+    struct torrent *tp = arg;
+    return vopen(fd, O_WRONLY|O_CREAT, "%s.d/%s", tp->relpath, path);
+}
+
+static int
+torrent_load3(const char *file, struct metainfo *mi, char *mem, size_t memsiz)
+{
+    struct torrent *tp = btpd_calloc(1, sizeof(*tp));
+
+    tp->relpath = strdup(file);
+    if (tp->relpath == NULL)
+	btpd_err("Out of memory.\n");
+
+    tp->piece_count = btpd_calloc(mi->npieces, sizeof(tp->piece_count[0]));
+ 
+    TAILQ_INIT(&tp->peers);
+    TAILQ_INIT(&tp->getlst);
+
+    tp->imem = mem;
+    tp->isiz = memsiz;
+
+    tp->piece_field = tp->imem;
+    tp->block_field =
+	(uint8_t *)tp->imem + (size_t)ceil(mi->npieces / 8.0);
+
+    for (uint32_t i = 0; i < mi->npieces; i++)
+	if (has_bit(tp->piece_field, i))
+	    tp->have_npieces++;
+
+    tp->meta = *mi;
+    free(mi);
+
+    TAILQ_INSERT_TAIL(&btpd.cm_list, tp, entry);
+
+    tracker_req(tp, TR_STARTED);
+    btpd.ntorrents++;
+
+    return 0;
+}
+
+static int
+torrent_load2(const char *file, struct metainfo *mi)
+{
+    int error, ifd;
+    struct stat sb;
+    char *mem;
+    size_t memsiz;
+
+    if ((error = vopen(&ifd, O_RDWR, "%s.i", file)) != 0) {
+	btpd_log(BTPD_L_ERROR, "Error opening %s.i: %s.\n",
+	    file, strerror(error));
+	return error;
+    }
+
+    if (fstat(ifd, &sb) == -1) {
+	error = errno;
+	btpd_log(BTPD_L_ERROR, "Error stating %s.i: %s.\n",
+	    file, strerror(error));
+	close(ifd);
+	return error;
+    }
+
+    memsiz =
+	ceil(mi->npieces / 8.0) +
+	ceil(mi->npieces * mi->piece_length / (double)(1 << 17));
+
+    if (sb.st_size != memsiz) {
+	btpd_log(BTPD_L_ERROR, "File has wrong size: %s.i.\n", file);
+	close(ifd);
+	return EINVAL;
+    }
+
+    mem = mmap(NULL, memsiz, PROT_READ|PROT_WRITE, MAP_SHARED, ifd, 0);
+    if (mem == MAP_FAILED)
+	btpd_err("Error mmap'ing %s.i: %s.\n", file, strerror(errno));
+
+    close(ifd);
+
+    if ((error = torrent_load3(file, mi, mem, memsiz) != 0)) {
+	munmap(mem, memsiz);
+	return error;
+    }
+
+    return 0;
+}
+
+int
+torrent_load(const char *file)
+{
+    struct metainfo *mi;
+    int error;
+
+    if ((error = load_metainfo(file, -1, 0, &mi)) != 0) {
+	btpd_log(BTPD_L_ERROR, "Couldn't load metainfo file %s: %s.\n",
+	    file, strerror(error));
+	return error;
+    }
+
+    if (torrent_get_by_hash(mi->info_hash) != NULL) {
+	btpd_log(BTPD_L_BTPD, "%s has same hash as an already loaded torrent.\n", file);
+	error = EEXIST;
+    }
+
+    if (error == 0)
+	error = torrent_load2(file, mi);
+
+    if (error != 0) {
+	clear_metainfo(mi);
+	free(mi);
+    }
+
+    return error;
+}
+
+void
+torrent_unload(struct torrent *tp)
+{
+    struct peer *peer;
+    struct piece *piece;
+
+    btpd_log(BTPD_L_BTPD, "Unloading %s.\n", tp->relpath);
+
+    tracker_req(tp, TR_STOPPED);
+
+    peer = TAILQ_FIRST(&tp->peers);
+    while (peer != NULL) {
+        struct peer *next = TAILQ_NEXT(peer, cm_entry);
+        peer->flags &= ~PF_ATTACHED;
+        peer_kill(peer);
+        peer = next;
+    }
+
+    piece = TAILQ_FIRST(&tp->getlst);
+    while (piece != NULL) {
+	struct piece *next = TAILQ_NEXT(piece, entry);
+	free(piece);
+	piece = next;
+    }
+
+    free(tp->piece_count);
+    free((void *)tp->relpath);
+    clear_metainfo(&tp->meta);
+
+    munmap(tp->imem, tp->isiz);
+
+    TAILQ_REMOVE(&btpd.cm_list, tp, entry);
+    free(tp);
+    btpd.ntorrents--;
+}
+
+off_t
+torrent_bytes_left(struct torrent *tp)
+{
+    if (tp->have_npieces == 0)
+	return tp->meta.total_length;
+    else if (has_bit(tp->piece_field, tp->meta.npieces - 1)) {
+	return tp->meta.total_length - 
+	    ((tp->have_npieces - 1) * tp->meta.piece_length +
+	    tp->meta.total_length % tp->meta.piece_length);
+    } else
+	return tp->meta.total_length -
+	    tp->have_npieces * tp->meta.piece_length;
+}
+
+char *
+torrent_get_bytes(struct torrent *tp, off_t start, size_t len)
+{
+    char *buf = btpd_malloc(len);
+    struct bt_stream_ro *bts;
+    if ((bts = bts_open_ro(&tp->meta, start, ro_fd_cb, tp)) == NULL)
+	btpd_err("Out of memory.\n");
+    if (bts_read_ro(bts, buf, len) != 0)
+	btpd_err("Io error.\n");
+    bts_close_ro(bts);
+    return buf;
+}
+
+void
+torrent_put_bytes(struct torrent *tp, const char *buf, off_t start, size_t len)
+{
+    int err;
+    struct bt_stream_wo *bts;
+    if ((bts = bts_open_wo(&tp->meta, start, wo_fd_cb, tp)) == NULL)
+	btpd_err("Out of memory.\n");
+    if ((err = bts_write_wo(bts, buf, len)) != 0)
+	btpd_err("Io error1: %s\n", strerror(err));
+    if ((err = bts_close_wo(bts)) != 0)
+	btpd_err("Io error2: %s\n", strerror(err));
+}
+
+int
+torrent_has_peer(struct torrent *tp, const uint8_t *id)
+{
+    int has = 0;
+    struct peer *p = TAILQ_FIRST(&tp->peers);
+    while (p != NULL) {
+	if (bcmp(p->id, id, 20) == 0) {
+	    has = 1;
+	    break;
+	}
+	p = TAILQ_NEXT(p, cm_entry);
+    }
+    return has;
+}
+
+struct torrent *
+torrent_get_by_hash(const uint8_t *hash)
+{
+    struct torrent *tp = TAILQ_FIRST(&btpd.cm_list);
+    while (tp != NULL && bcmp(hash, tp->meta.info_hash, 20) != 0)
+	tp = TAILQ_NEXT(tp, entry);
+    return tp;
+}
diff --git a/btpd/torrent.h b/btpd/torrent.h
new file mode 100644
index 0000000..66d5512
--- /dev/null
+++ b/btpd/torrent.h
@@ -0,0 +1,67 @@
+#ifndef BTPD_TORRENT_H
+#define BTPD_TORRENT_H
+
+struct piece {
+    uint32_t index;
+    unsigned nblocks;
+
+    unsigned ngot;
+    unsigned nbusy;
+
+    uint8_t *have_field;
+    uint8_t *down_field;
+
+    TAILQ_ENTRY(piece) entry;
+};
+
+TAILQ_HEAD(piece_tq, piece);
+
+struct torrent {
+    const char *relpath;
+    struct metainfo meta;
+
+    TAILQ_ENTRY(torrent) entry;
+
+    void *imem;
+    size_t isiz;
+
+    uint8_t *piece_field;
+    uint8_t *block_field;
+
+    uint32_t have_npieces;
+ 
+    unsigned long *piece_count;
+
+    uint64_t uploaded, downloaded;
+    
+    unsigned long choke_time;
+    unsigned long opt_time;
+    unsigned long tracker_time;    
+
+    short ndown;
+    struct peer *optimistic;
+    
+    unsigned npeers;
+    struct peer_tq peers;
+
+    int endgame;
+    struct piece_tq getlst;
+};
+
+TAILQ_HEAD(torrent_tq, torrent);
+
+off_t torrent_bytes_left(struct torrent *tp);
+
+char *torrent_get_bytes(struct torrent *tp, off_t start, size_t len);
+void torrent_put_bytes(struct torrent *tp, const char *buf,
+		       off_t start, size_t len);
+
+int torrent_load(const char *metafile);
+
+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);
+
+#endif
diff --git a/btpd/tracker_req.c b/btpd/tracker_req.c
new file mode 100644
index 0000000..aa56331
--- /dev/null
+++ b/btpd/tracker_req.c
@@ -0,0 +1,300 @@
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+
+#include <sys/wait.h>
+#include <sys/mman.h>
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <inttypes.h>
+
+#include <curl/curl.h>
+
+#include "btpd.h"
+#include "tracker_req.h"
+
+#ifndef PRIu64
+#define PRIu64 "llu"
+#endif
+
+#define REQ_SIZE (getpagesize() * 2)
+
+struct tracker_req {
+    struct child child;
+    enum tr_event tr_event;
+    uint8_t info_hash[20];
+    struct io_buffer *res;
+};
+
+static void
+maybe_connect_to(struct torrent *tp, const char *pinfo)
+{
+    const char *pid = NULL;
+    char *ip = NULL;
+    int64_t port;
+    size_t len;
+
+    if (!benc_isdct(pinfo))
+	return;
+
+    if (benc_dget_str(pinfo, "peer id", &pid, &len) != 0 || len != 20)
+	return;
+
+    if (bcmp(btpd.peer_id, pid, 20) == 0)
+	return;
+
+    if (torrent_has_peer(tp, pid))
+	return;
+
+    if (benc_dget_strz(pinfo, "ip", &ip, NULL) != 0)
+	goto out;
+
+    if (benc_dget_int64(pinfo, "port", &port) != 0)
+	goto out;
+
+    peer_create_out(tp, pid, ip, port);
+
+out:
+    if (ip != NULL)
+	free(ip);
+}
+
+static void
+tracker_done(struct child *child)
+{
+    struct tracker_req *req = (struct tracker_req *)child;
+    int failed = 0;
+    char *buf;
+    const char *peers;
+    uint32_t interval;
+    struct torrent *tp;
+
+    if ((tp = torrent_get_by_hash(req->info_hash)) == NULL)
+	goto out;
+
+    if (benc_validate(req->res->buf, req->res->buf_off) != 0
+	|| !benc_isdct(req->res->buf)) {
+	if (req->res->buf_off != 0) {
+	    fwrite(req->res->buf, 1, req->res->buf_off, (stdout));
+	    putchar('\n');
+	}
+
+	btpd_log(BTPD_L_ERROR, "Bad data from tracker.\n");
+	failed = 1;
+	goto out;
+    }
+
+    if ((benc_dget_strz(req->res->buf, "failure reason", &buf, NULL)) == 0) {
+	btpd_log(BTPD_L_ERROR, "Tracker failure: %s.\n", buf);
+	free(buf);
+	failed = 1;
+	goto out;
+    }
+
+    if ((benc_dget_uint32(req->res->buf, "interval", &interval)) != 0) {
+	btpd_log(BTPD_L_ERROR, "Bad data from tracker.\n");
+	failed = 1;
+	goto out;
+    }
+
+    tp->tracker_time = btpd.seconds + interval;
+
+    if ((benc_dget_lst(req->res->buf, "peers", &peers)) != 0) {
+	btpd_log(BTPD_L_TRACKER, "Bad data from tracker.\n");
+	failed = 1;
+	goto out;
+    }
+
+    for (peers = benc_first(peers);
+	 peers != NULL && btpd.npeers < btpd.maxpeers;
+	 peers = benc_next(peers))
+	maybe_connect_to(tp, peers);
+
+out:
+    if (failed) {
+	if (req->tr_event == TR_STARTED) {
+	    btpd_log(BTPD_L_BTPD,
+	        "Start request failed for %s.\n", tp->relpath);
+	    torrent_unload(tp);
+	} else
+	    tp->tracker_time = btpd.seconds + 10;
+    }
+    munmap(req->res, REQ_SIZE);
+    free(req);
+}
+
+static const char *
+event2str(enum tr_event ev)
+{
+    switch (ev) {
+    case TR_STARTED:
+	return "started";
+    case TR_STOPPED:
+	return "stopped";
+    case TR_COMPLETED:
+	return "completed";
+    case TR_EMPTY:
+	return "";
+    default:
+	btpd_err("Bad tracker event %d.\n", ev);
+	return ""; // Shut GCC up!
+    }
+}
+
+static int
+create_url(struct tracker_req *req, struct torrent *tp, char **url)
+{
+    char e_hash[61], e_id[61];
+    char qc;
+    int i;
+    uint64_t left;
+    const char *event;
+
+    event = event2str(req->tr_event);
+
+    qc = (strchr(tp->meta.announce, '?') == NULL) ? '?' : '&';
+
+    for (i = 0; i < 20; i++)
+	sprintf(e_hash + i * 3, "%%%.2x", tp->meta.info_hash[i]);
+    e_hash[61] = '\0';
+
+    for (i = 0; i < 20; i++)
+	sprintf(e_id + i * 3, "%%%.2x", btpd.peer_id[i]);
+    e_id[61] = '\0';
+
+    left = torrent_bytes_left(tp);
+
+    i = asprintf(url, "%s%cinfo_hash=%s&peer_id=%s&port=%d"
+		 "&uploaded=%" PRIu64
+		 "&downloaded=%" PRIu64
+		 "&left=%" PRIu64
+		 "%s%s",
+		 tp->meta.announce, qc, e_hash, e_id, btpd.port,
+		 tp->uploaded, tp->downloaded, left,
+		 req->tr_event == TR_EMPTY ? "" : "&event=",
+		 event);
+
+    if (i < 0)
+	return ENOMEM;
+    return 0;
+}
+
+static size_t
+http_cb(void *ptr, size_t size, size_t nmemb, void *stream)
+{
+    struct tracker_req *req = (struct tracker_req *)stream;
+    size_t nbytes = size * nmemb;
+    if (nbytes <=  req->res->buf_len - req->res->buf_off) {
+	memcpy(req->res->buf + req->res->buf_off, ptr, nbytes);
+	req->res->buf_off += nbytes;
+	return nbytes;
+    }
+    else
+	return 0;
+}
+
+static void
+http_helper(struct tracker_req *req, struct torrent *tp)
+{
+    char cerror[CURL_ERROR_SIZE];
+    char fr[] = "failure reason";
+    CURL *handle;
+    char *url;
+    int err;
+
+    if (create_url(req, tp, &url) != 0)
+	goto memory_error;
+
+    if (curl_global_init(0) != 0)
+	goto libcurl_error;
+	
+    if ((handle = curl_easy_init()) == NULL)
+	goto libcurl_error;
+
+    err = curl_easy_setopt(handle, CURLOPT_URL, url);
+    if (err == 0)
+	err = curl_easy_setopt(handle, CURLOPT_USERAGENT, btpd.version);
+    if (err == 0)
+	err = curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, http_cb);
+    if (err == 0)
+	err = curl_easy_setopt(handle, CURLOPT_WRITEDATA, req);
+    if (err == 0)
+	err = curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, cerror);
+    if (err != 0) {
+	strncpy(cerror, curl_easy_strerror(err), CURL_ERROR_SIZE - 1);
+	goto handle_error;
+    }
+
+    req->res->buf_off = 0;
+    if (curl_easy_perform(handle) != 0)
+	goto handle_error;
+
+#if 0
+    curl_easy_cleanup(handle);
+    curl_global_cleanup();
+    free(url);
+#endif
+    exit(0);
+
+memory_error:
+    strncpy(cerror, "Out of memory", CURL_ERROR_SIZE - 1);
+    goto handle_error;
+
+libcurl_error:
+    strncpy(cerror, "Generic libcurl error", CURL_ERROR_SIZE - 1);
+    goto handle_error;
+
+handle_error:    
+    req->res->buf_off =
+	snprintf(req->res->buf, req->res->buf_len,
+	    "d%d:%s%d:%se", (int)strlen(fr), fr, (int)strlen(cerror), cerror);
+    if (req->res->buf_off >= req->res->buf_len)
+	req->res->buf_off = 0;
+
+    exit(1);
+}
+
+void
+tracker_req(struct torrent *tp, enum tr_event tr_event)
+{
+    struct tracker_req *req;
+
+    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));
+
+    req->res = mmap(NULL, REQ_SIZE, PROT_READ | PROT_WRITE,
+        MAP_ANON | MAP_SHARED, -1, 0);
+
+    if (req->res == MAP_FAILED) {
+	free(req);
+	btpd_err("Failed mmap: %s\n", strerror(errno));
+    }
+
+    req->res->buf_len = REQ_SIZE - sizeof(*req->res);
+    req->res->buf_off = 0;
+    req->res->buf = (char *)req->res + sizeof(*req->res);
+
+    req->tr_event = tr_event;
+    bcopy(tp->meta.info_hash, req->info_hash, 20);
+
+    fflush(NULL);
+
+    req->child.child_done = tracker_done;
+    TAILQ_INSERT_TAIL(&btpd.kids, &req->child, entry);
+    req->child.pid = fork();
+    if (req->child.pid < 0) {
+	btpd_err("Couldn't fork (%s).\n", strerror(errno));
+    } else if (req->child.pid == 0) { // Child
+	int nfiles = getdtablesize();
+	for (int i = 0; i < nfiles; i++)
+	    close(i);
+	http_helper(req, tp);
+    }
+}
diff --git a/btpd/tracker_req.h b/btpd/tracker_req.h
new file mode 100644
index 0000000..2d2a8c6
--- /dev/null
+++ b/btpd/tracker_req.h
@@ -0,0 +1,13 @@
+#ifndef TRACKER_REQ_H
+#define TRACKER_REQ_H
+
+enum tr_event {
+    TR_STARTED = 1,
+    TR_STOPPED,
+    TR_COMPLETED,
+    TR_EMPTY
+};
+
+void tracker_req(struct torrent *tp, enum tr_event tr_event);
+
+#endif
diff --git a/cli/Makefile.am b/cli/Makefile.am
new file mode 100644
index 0000000..84e18f9
--- /dev/null
+++ b/cli/Makefile.am
@@ -0,0 +1,11 @@
+bin_PROGRAMS=btinfo btcli
+
+btinfo_SOURCES=btinfo.c
+btinfo_LDADD=../misc/libmisc.a -lcrypto -lm
+btinfo_CPPFLAGS=-I$(top_srcdir)/misc @openssl_CPPFLAGS@
+btinfo_LDFLAGS=@openssl_LDFLAGS@
+
+btcli_SOURCES=btcli.c btpd_if.c btpd_if.h
+btcli_LDADD=../misc/libmisc.a -lcrypto -lm
+btcli_CPPFLAGS=-I$(top_srcdir)/misc @openssl_CPPFLAGS@
+btcli_LDFLAGS=@openssl_LDFLAGS@
diff --git a/cli/btcli.c b/cli/btcli.c
new file mode 100644
index 0000000..2e266a1
--- /dev/null
+++ b/cli/btcli.c
@@ -0,0 +1,577 @@
+#include <sys/types.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <inttypes.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <openssl/sha.h>
+
+#include "benc.h"
+#include "metainfo.h"
+#include "stream.h"
+#include "subr.h"
+#include "btpd_if.h"
+
+static void
+usage()
+{
+    printf("Usage: btcli command [options] [files]\n"
+	   "Commands:\n"
+	   "add <file_1> ... [file_n]\n"
+	   "\tAdd the given torrents to btpd.\n"
+	   "\n"
+	   "del <file_1> ... [file_n]\n"
+	   "\tRemove the given torrents from btpd.\n"
+	   "\n"
+	   "die\n"
+	   "\tShut down btpd.\n"
+	   "\n"
+           "list\n"
+           "\tList active torrents.\n"
+           "\n"
+           "stat [-i] [-w n] [file_1] ... [file_n]\n"
+           "\tShow stats for either all active or the given torrents.\n"
+           "\tThe stats displayed are:\n"
+           "\t%% of pieces seen, %% of pieces verified, \n"
+           "\tMB down, rate down, MB up, rate up, no peers\n"
+           "-i\n"
+           "\tShow stats per torrent in addition to total stats.\n"
+           "-w n\n"
+           "\tRepeat every n seconds.\n"
+           "\n"
+	   "Common options:\n"
+	   "--ipc key\n"
+	   "\tTalk to the btpd started with the same key.\n"
+	   "\n"
+	   "--help\n"
+	   "\tShow this help.\n"
+	   "\n");
+    exit(1);
+}
+
+static void
+handle_error(int error)
+{
+    switch (error) {
+    case 0:
+	break;
+    case ENOENT:
+    case ECONNREFUSED:
+	errx(1, "Couldn't connect. Check that btpd is running.");
+    default:
+	errx(1, "%s", strerror(error));
+    }
+}
+
+static void
+do_ipc_open(char *ipctok, struct ipc **ipc)
+{
+    switch (ipc_open(ipctok, ipc)) {
+    case 0:
+	break;
+    case EINVAL:
+	errx(1, "--ipc argument only takes letters and digits.");
+    case ENAMETOOLONG:
+	errx(1, "--ipc argument is too long.");
+    }
+}
+
+struct cb {
+    char *path;
+    uint8_t *piece_field;
+    uint32_t have;
+    struct metainfo *meta;
+};
+
+static void
+hash_cb(uint32_t index, uint8_t *hash, void *arg)
+{
+    struct cb *cb = arg;
+    if (hash != NULL)
+	if (bcmp(hash, cb->meta->piece_hash[index], SHA_DIGEST_LENGTH) == 0) {
+	    set_bit(cb->piece_field, index);
+            cb->have++;
+	}
+    printf("\rTested: %5.1f%%", 100.0 * (index + 1) / cb->meta->npieces);
+    fflush(stdout);
+}
+
+static int
+fd_cb(const char *path, int *fd, void *arg)
+{
+    struct cb *fp = arg;
+    return vopen(fd, O_RDONLY, "%s.d/%s", fp->path, path);
+}
+
+static void
+gen_ifile(char *path)
+{
+    int fd;
+    struct cb cb;
+    struct metainfo *mi;
+    size_t field_len;
+
+    if ((errno = load_metainfo(path, -1, 1, &mi)) != 0)
+	err(1, "load_metainfo: %s", path);
+
+    field_len = ceil(mi->npieces / 8.0);
+    cb.path = path;
+    cb.piece_field = calloc(1, field_len);
+    cb.have = 0;
+    cb.meta = mi;
+
+    if (cb.piece_field == NULL)
+	errx(1, "Out of memory.\n");
+
+    if ((errno = bts_hashes(mi, fd_cb, hash_cb, &cb)) != 0)
+	err(1, "bts_hashes");
+    printf("\nHave: %5.1f%%\n", 100.0 * cb.have / cb.meta->npieces);
+
+    if ((errno = vopen(&fd, O_WRONLY|O_CREAT, "%s.i", path)) != 0)
+	err(1, "opening %s.i", path);
+
+    if (ftruncate(fd,
+            field_len +
+	    (off_t)ceil(mi->npieces * mi->piece_length / (double)(1<<17))) < 0)
+	err(1, "ftruncate: %s", path);
+
+    if (write(fd, cb.piece_field, field_len) != field_len)
+	err(1, "write %s.i", path);
+
+    if (close(fd) < 0)
+	err(1, "close %s.i", path);
+
+    clear_metainfo(mi);
+    free(mi);
+}
+
+static struct option add_opts[] = {
+    { "ipc", required_argument, NULL, 1 },
+    { "help", required_argument, NULL, 2},
+    {NULL, 0, NULL, 0}
+};
+
+static void
+do_add(char *ipctok, char **paths, int npaths, char **out)
+{
+    struct ipc *ipc;
+    do_ipc_open(ipctok, &ipc);
+    handle_error(btpd_add(ipc, paths, npaths, out));
+    ipc_close(ipc);
+}
+
+static void
+cmd_add(int argc, char **argv)
+{
+    int ch;
+    char *ipctok = NULL;
+    while ((ch = getopt_long(argc, argv, "", add_opts, NULL)) != -1) {
+	switch(ch) {
+	case 1:
+	    ipctok = optarg;
+	    break;
+	default:
+	    usage();
+	}
+    }
+    argc -= optind;
+    argv += optind;
+
+    if (argc < 1)
+	usage();
+
+    for (int i = 0; i < argc; i++) {
+	int64_t code;
+	char *res;
+	int fd;
+	char *path;
+	errno = vopen(&fd, O_RDONLY, "%s.i", argv[i]);
+	if (errno == ENOENT) {
+	    printf("Testing %s for content.\n", argv[i]);
+	    gen_ifile(argv[i]);
+	} else if (errno != 0)
+	    err(1, "open %s.i", argv[i]);
+	else
+	    close(fd);
+
+	if ((errno = canon_path(argv[i], &path)) != 0)
+	    err(1, "canon_path");
+	do_add(ipctok, &path, 1, &res);
+	free(path);
+	benc_dget_int64(benc_first(res), "code", &code);
+	if (code == EEXIST)
+	    printf("btpd already had %s.\n", argv[i]);
+	else if (code != 0) {
+	    printf("btpd indicates error: %s for %s.\n",
+	        strerror(code), argv[i]);
+	}
+        free(res);
+    }
+}
+
+static struct option del_opts[] = {
+    { "ipc", required_argument, NULL, 1 },
+    { "help", required_argument, NULL, 2},
+    {NULL, 0, NULL, 0}
+};
+
+static void
+do_del(char *ipctok, uint8_t (*hashes)[20], int nhashes, char **out)
+{
+    struct ipc *ipc;
+    do_ipc_open(ipctok, &ipc);
+    handle_error(btpd_del(ipc, hashes, nhashes, out));
+    ipc_close(ipc);
+}
+
+static void
+cmd_del(int argc, char **argv)
+{
+    int ch;
+    char *ipctok = NULL;
+    while ((ch = getopt_long(argc, argv, "", del_opts, NULL)) != -1) {
+	switch(ch) {
+	case 1:
+	    ipctok = optarg;
+	    break;
+	default:
+	    usage();
+	}
+    }
+    argc -= optind;
+    argv += optind;
+
+    if (argc < 1)
+	usage();
+
+    uint8_t hashes[argc][20];
+    char *res;
+    const char *d;
+
+    for (int i = 0; i < argc; i++) {
+	struct metainfo *mi;
+	if ((errno = load_metainfo(argv[i], -1, 0, &mi)) != 0)
+	    err(1, "load_metainfo: %s", argv[i]);
+	bcopy(mi->info_hash, hashes[i], 20);
+	clear_metainfo(mi);
+	free(mi);	
+    }
+    
+    do_del(ipctok, hashes, argc, &res);
+    d = benc_first(res);
+    for (int i = 0; i < argc; i++) {
+	int64_t code;
+	benc_dget_int64(d, "code", &code);
+	if (code == ENOENT)
+	    printf("btpd didn't have %s.\n", argv[i]);
+	else if (code != 0) {
+	    printf("btpd indicates error: %s for %s.\n",
+		   strerror(code), argv[i]);
+	}
+	d = benc_next(d);
+    }
+    free(res);
+}
+
+static struct option die_opts[] = {
+    { "ipc", required_argument, NULL, 1 },
+    { "help", no_argument, NULL, 2 },
+    {NULL, 0, NULL, 0}
+};
+
+static void
+do_die(char *ipctok)
+{
+    struct ipc *ipc;
+    do_ipc_open(ipctok, &ipc);
+    handle_error(btpd_die(ipc));
+    ipc_close(ipc);
+}
+
+static void
+cmd_die(int argc, char **argv)
+{
+    int ch;
+    char *ipctok = NULL;
+
+    while ((ch = getopt_long(argc, argv, "", die_opts, NULL)) != -1) {
+	switch (ch) {
+	case 1:
+	    ipctok = optarg;
+	    break;
+	default:
+	    usage();
+	}
+    }
+    do_die(ipctok);
+}
+
+static struct option stat_opts[] = {
+    { "ipc", required_argument, NULL, 1 },
+    { "help", no_argument, NULL, 2 },
+    {NULL, 0, NULL, 0}
+};
+
+static void
+do_stat(char *ipctok, char **out)
+{
+    struct ipc *ipc;
+    do_ipc_open(ipctok, &ipc);
+    handle_error(btpd_stat(ipc, out));
+    ipc_close(ipc);
+}
+
+struct tor {
+    char *path;
+    uint8_t hash[20];
+    uint64_t down;
+    uint64_t up;
+    uint64_t npeers;
+    uint64_t npieces;
+    uint64_t have_npieces;
+    uint64_t seen_npieces;
+};
+
+struct tor **parse_tors(char *res, uint8_t (*hashes)[20], int nhashes)
+{
+    struct tor **tors;
+    int64_t num;
+    const char *p;
+    benc_dget_int64(res, "ntorrents", &num);
+    benc_dget_lst(res, "torrents", &p);
+
+    tors = calloc(sizeof(*tors), num + 1);
+    int i = 0;
+    for (p = benc_first(p); p; p = benc_next(p)) {
+        int j;
+        const char *hash;
+        benc_dget_str(p, "hash", &hash, NULL);
+
+        for (j = 0; j < nhashes; j++) {
+            if (bcmp(hashes[i], hash, 20) == 0)
+                break;
+        }
+        if (j < nhashes || nhashes == 0) {
+            tors[i] = calloc(sizeof(*tors[i]), 1);
+            bcopy(hash, tors[i]->hash, 20);
+            benc_dget_int64(p, "down", &tors[i]->down);
+            benc_dget_int64(p, "up", &tors[i]->up);
+            benc_dget_int64(p, "npeers", &tors[i]->npeers);
+            benc_dget_int64(p, "npieces", &tors[i]->npieces);
+            benc_dget_int64(p, "have npieces", &tors[i]->have_npieces);
+            benc_dget_int64(p, "seen npieces", &tors[i]->seen_npieces);
+            benc_dget_strz(p, "path", &tors[i]->path, NULL);
+            i++;
+        }
+    }
+    return tors;
+}
+
+static void
+free_tors(struct tor **tors)
+{
+    for (int i = 0; tors[i] != NULL; i++) {
+        free(tors[i]->path);
+        free(tors[i]);
+    }
+    free(tors);
+}
+
+static void
+print_stat(struct tor *cur, struct tor *old, int wait)
+{
+    if (old == NULL) {
+        printf("%5.1f%% %5.1f%% %6.1fM      - kB/s %6.1fM      - kB/s %4u\n",
+               100 * cur->seen_npieces / (double)cur->npieces,
+               100 * cur->have_npieces / (double)cur->npieces,
+               cur->down / (double)(1 << 20),
+               cur->up / (double)(1 << 20),
+               (unsigned)cur->npeers);
+    } else {
+        printf("%5.1f%% %5.1f%% %6.1fM %7.2fkB/s %6.1fM %7.2fkB/s %4u\n",
+               100 * cur->seen_npieces / (double)cur->npieces,
+               100 * cur->have_npieces / (double)cur->npieces,
+               cur->down / (double)(1 << 20),
+               (cur->down - old->down) / (double)wait / (double)(1 << 10),
+               cur->up / (double)(1 << 20),
+               (cur->up - old->up) / (double)wait / (double)(1 << 10),
+               (unsigned)cur->npeers
+            );
+    }
+}
+
+static void
+grok_stat(char *ipctok, int iflag, int wait,
+          uint8_t (*hashes)[20], int nhashes)
+{
+    int i, j;
+    char *res;
+    struct tor **cur, **old = NULL;
+    struct tor curtot, oldtot;
+
+again:
+    do_stat(ipctok, &res);
+    cur = parse_tors(res, hashes, nhashes);
+    free(res);
+
+    if (iflag) {
+        for (i = 0; cur[i] != NULL; i++) {
+            if (old == NULL) {
+                printf("%s:\n", rindex(cur[i]->path, '/') + 1);
+                print_stat(cur[i], NULL, wait);
+            } else {
+                for (j = 0; old[j] != NULL; j++)
+                    if (bcmp(cur[i]->hash, old[j]->hash, 20) == 0)
+                        break;
+                printf("%s:\n", rindex(cur[i]->path, '/') + 1);
+                print_stat(cur[i], old[j], wait);
+            }
+        }
+    }
+
+    bzero(&curtot, sizeof(curtot));
+    for (i = 0; cur[i] != NULL; i++) {
+        curtot.down += cur[i]->down;
+        curtot.up += cur[i]->up;
+        curtot.npeers += cur[i]->npeers;
+        curtot.npieces += cur[i]->npieces;
+        curtot.have_npieces += cur[i]->have_npieces;
+        curtot.seen_npieces += cur[i]->seen_npieces;
+    }
+    if (iflag)
+        printf("Total:\n");
+    if (old != NULL)
+        print_stat(&curtot, &oldtot, wait);
+    else
+        print_stat(&curtot, NULL, wait);
+
+    if (wait) {
+        if (old != NULL)
+            free_tors(old);
+        old = cur;
+        oldtot = curtot;
+        sleep(wait);
+        goto again;
+    }
+    free_tors(cur);
+}
+
+static void
+cmd_stat(int argc, char **argv)
+{
+    int ch;
+    char *ipctok = NULL;
+    int wait = 0;
+    int iflag = 0;
+
+    while ((ch = getopt_long(argc, argv, "iw:", stat_opts, NULL)) != -1) {
+	switch (ch) {
+        case 'i':
+            iflag = 1;
+            break;
+        case 'w':
+            wait = atoi(optarg);
+            if (wait <= 0)
+                errx(1, "-w argument must be an integer > 0.");
+            break;
+	case 1:
+	    ipctok = optarg;
+	    break;
+	default:
+	    usage();
+	}
+    }
+    argc -= optind;
+    argv += optind;
+
+    if (argc > 0) {
+        uint8_t hashes[argc][20];
+        for (int i = 0; i < argc; i++) {
+            struct metainfo *mi;
+            if ((errno = load_metainfo(argv[i], -1, 0, &mi)) != 0)
+                err(1, "load_metainfo: %s", argv[i]);
+            bcopy(mi->info_hash, hashes[i], 20);
+            clear_metainfo(mi);
+            free(mi);
+        }
+        grok_stat(ipctok, iflag, wait, hashes, argc);
+    } else
+        grok_stat(ipctok, iflag, wait, NULL, 0);
+}
+
+static struct option list_opts[] = {
+    { "ipc", required_argument, NULL, 1 },
+    { "help", no_argument, NULL, 2 },
+    {NULL, 0, NULL, 0}
+};
+
+static void
+cmd_list(int argc, char **argv)
+{
+    int ch;
+    char *ipctok = NULL;
+
+    while ((ch = getopt_long(argc, argv, "", list_opts, NULL)) != -1) {
+	switch (ch) {
+	case 1:
+	    ipctok = optarg;
+	    break;
+	default:
+	    usage();
+	}
+    }
+    char *res;
+    const char *p;
+    char *path;
+    do_stat(ipctok, &res);
+
+    benc_dget_lst(res, "torrents", &p);
+    int count = 0;
+    for (p = benc_first(p); p; p = benc_next(p)) {
+        count++;
+        benc_dget_strz(p, "path", &path, NULL);
+        printf("%s\n", path);
+        free(path);
+    }
+    printf("%d torrents.\n", count);
+}
+
+static struct {
+    const char *name;
+    void (*fun)(int, char **);
+} cmd_table[] = {
+    { "add", cmd_add },
+    { "del", cmd_del },
+    { "die", cmd_die },
+    { "list", cmd_list},
+    { "stat", cmd_stat }
+};
+
+static int ncmds = sizeof(cmd_table) / sizeof(cmd_table[0]);
+
+int
+main(int argc, char **argv)
+{
+    if (argc < 2)
+	usage();
+    
+    int found = 0;
+    for (int i = 0; !found && i < ncmds; i++) {
+	if (strcmp(argv[1], cmd_table[i].name) == 0) {
+	    found = 1;
+	    cmd_table[i].fun(argc - 1, argv + 1);
+	}
+    }
+
+    if (!found)
+	usage();
+
+    return 0;
+}
diff --git a/cli/btinfo.c b/cli/btinfo.c
new file mode 100644
index 0000000..bd4a33b
--- /dev/null
+++ b/cli/btinfo.c
@@ -0,0 +1,53 @@
+#include <sys/types.h>
+
+#include <err.h>
+#include <errno.h>
+#include <getopt.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "metainfo.h"
+
+static void
+usage()
+{
+    fprintf(stderr, "Usage: btinfo file ...\n\n");
+    exit(1);
+}
+
+static struct option longopts[] = {
+    { "help", no_argument, NULL, 1 },
+    { NULL, 0, NULL, 0 }
+};
+
+int
+main(int argc, char **argv)
+{
+    int ch;
+
+    while ((ch = getopt_long(argc, argv, "", longopts, NULL)) != -1)
+	usage();
+
+    argc -= optind;
+    argv += optind;
+
+    if (argc < 1)
+	usage();
+
+    while (argc > 0) {
+	struct metainfo *mi;
+
+	if ((errno = load_metainfo(*argv, -1, 1, &mi)) != 0)
+	    err(1, "load_metainfo: %s", *argv);
+
+	print_metainfo(mi);
+	clear_metainfo(mi);
+	free(mi);
+
+	argc--;
+	argv++;
+    }
+
+    return 0;
+}
diff --git a/cli/btpd_if.c b/cli/btpd_if.c
new file mode 100644
index 0000000..9990446
--- /dev/null
+++ b/cli/btpd_if.c
@@ -0,0 +1,221 @@
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "benc.h"
+#include "iobuf.h"
+#include "btpd_if.h"
+
+int
+ipc_open(const char *key, struct ipc **out)
+{
+    size_t plen;
+    size_t keylen;
+    struct ipc *res;
+
+    if (key == NULL)
+	key = "default";
+    keylen = strlen(key);
+    for (int i = 0; i < keylen; i++)
+	if (!isalnum(key[i]))
+	    return EINVAL;
+
+    res = malloc(sizeof(*res));
+    if (res == NULL)
+	return ENOMEM;
+
+    plen = sizeof(res->addr.sun_path);
+    if (snprintf(res->addr.sun_path, plen,
+		 "/tmp/btpd_%u_%s", geteuid(), key) >= plen) {
+	free(res);
+	return ENAMETOOLONG;
+    }
+    res->addr.sun_family = AF_UNIX;
+    *out = res;
+    return 0;
+}
+
+int
+ipc_close(struct ipc *ipc)
+{
+    free(ipc);
+    return 0;
+}
+
+static int
+ipc_connect(struct ipc *ipc, FILE **out)
+{
+    FILE *fp;
+    int sd;
+    int error;
+
+    if ((sd = socket(PF_UNIX, SOCK_STREAM, 0)) == -1)
+	return errno;
+
+    if (connect(sd, (struct sockaddr *)&ipc->addr, sizeof(ipc->addr)) == -1)
+	goto error;
+
+    if ((fp = fdopen(sd, "r+")) == NULL)
+	goto error;
+
+    *out = fp;
+    return 0;
+error:
+    error = errno;
+    close(sd);
+    return error;
+}
+
+static int
+ipc_response(FILE *fp, char **out, uint32_t *len)
+{
+    uint32_t size;
+    char *buf;
+
+    if (fread(&size, sizeof(size), 1, fp) != 1) {
+	if (ferror(fp))
+	    return errno;
+	else
+	    return ECONNRESET;
+    }
+
+    if (size == 0)
+	return EINVAL;
+
+    if ((buf = malloc(size)) == NULL)
+	return ENOMEM;
+
+    if (fread(buf, 1, size, fp) != size) {
+	if (ferror(fp))
+	    return errno;
+	else
+	    return ECONNRESET;
+    }
+
+    *out = buf;
+    *len = size;
+    return 0;
+}
+
+static int
+ipc_req_res(struct ipc *ipc,
+	    const char *req, uint32_t qlen,
+	    char **res, uint32_t *rlen)
+{
+    FILE *fp;
+    int error;
+
+    if ((error = ipc_connect(ipc, &fp)) != 0)
+	return error;
+
+    if (fwrite(&qlen, sizeof(qlen), 1, fp) != 1)
+	goto error;
+    if (fwrite(req, 1, qlen, fp) != qlen)
+	goto error;
+    if (fflush(fp) != 0)
+	goto error;
+    if ((errno = ipc_response(fp, res, rlen)) != 0)
+	goto error;
+    if ((errno = benc_validate(*res, *rlen)) != 0)
+	goto error;
+
+    fclose(fp);
+    return 0;
+error:
+    error = errno;
+    fclose(fp);
+    return error;
+}
+
+int
+btpd_die(struct ipc *ipc)
+{
+    int error;
+    char *response = NULL;
+    const char shutdown[] = "l3:diee";
+    uint32_t size = sizeof(shutdown) - 1;
+    uint32_t rsiz;
+
+    if ((error = ipc_req_res(ipc, shutdown, size, &response, &rsiz)) != 0)
+	return error;
+
+    error = benc_validate(response, rsiz);
+
+    if (error == 0) {
+	int64_t tmp;
+        benc_dget_int64(response, "code", &tmp);
+	error = tmp;
+    }
+
+    free(response);
+    return error;
+}
+
+int
+btpd_add(struct ipc *ipc, char **paths, unsigned npaths, char **out)
+{
+    int error;
+    struct io_buffer iob;
+    char *res = NULL;
+    uint32_t reslen;
+
+    buf_init(&iob, 1024);
+    buf_print(&iob, "l3:add");
+    for (unsigned i = 0; i < npaths; i++) {
+        int plen = strlen(paths[i]);
+        buf_print(&iob, "%d:", plen);
+        buf_write(&iob, paths[i], plen);
+    }
+    buf_print(&iob, "e");
+    
+    error = ipc_req_res(ipc, iob.buf, iob.buf_off, &res, &reslen);
+    free(iob.buf);
+    if (error == 0)
+	*out = res;
+    
+    return error;
+}
+
+int
+btpd_stat(struct ipc *ipc, char **out)
+{
+    const char cmd[] = "l4:state";
+    uint32_t cmdlen = sizeof(cmd) - 1;
+    char *res;
+    uint32_t reslen;
+
+    if ((errno = ipc_req_res(ipc, cmd, cmdlen, &res, &reslen)) != 0)
+        return errno;
+    *out = res;
+    return 0;
+}
+
+int
+btpd_del(struct ipc *ipc, uint8_t (*hash)[20], unsigned nhashes, char **out)
+{
+    int error;
+    struct io_buffer iob;
+    char *res = NULL;
+    uint32_t reslen;
+
+    buf_init(&iob, 1024);
+    buf_write(&iob, "l3:del", 6);
+    for (unsigned i = 0; i < nhashes; i++) {
+        buf_write(&iob, "20:", 3);
+        buf_write(&iob, hash[i], 20);
+    }
+    buf_write(&iob, "e", 1);
+    
+    error = ipc_req_res(ipc, iob.buf, iob.buf_off, &res, &reslen);
+    free(iob.buf);
+    if (error != 0)
+	return error;
+
+    *out = res;
+    return 0;
+}
diff --git a/cli/btpd_if.h b/cli/btpd_if.h
new file mode 100644
index 0000000..bda9739
--- /dev/null
+++ b/cli/btpd_if.h
@@ -0,0 +1,21 @@
+#ifndef BTPD_IF_H
+#define BTPD_IF_H
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+struct ipc {
+    struct sockaddr_un addr;
+};
+
+int ipc_open(const char *key, struct ipc **out);
+int ipc_close(struct ipc *ipc);
+
+int btpd_add(struct ipc *ipc, char **path, unsigned npaths, char **out);
+int btpd_del(struct ipc *ipc, uint8_t (*hash)[20],
+	     unsigned nhashes, char **out);
+int btpd_die(struct ipc *ipc);
+int btpd_stat(struct ipc *ipc, char **out);
+
+#endif
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..6e6f887
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,65 @@
+AC_INIT(btpd, 0.1, rnyberg@gmail.com)
+
+AC_CANONICAL_TARGET
+
+AM_INIT_AUTOMAKE([foreign])
+
+AC_CONFIG_FILES([Makefile btpd/Makefile misc/Makefile cli/Makefile])
+
+AC_PROG_CC
+AC_PROG_RANLIB
+
+CFLAGS="$CFLAGS -std=c99 -Wall -Werror"
+
+case $target_os in
+     linux*)
+	CPPFLAGS="$CPPFLAGS -D_GNU_SOURCE=1 -D_FILE_OFFSET_BITS=64"
+	;;
+esac
+
+AC_ARG_WITH(event,
+[  --with-event=dir        use libevent installed in dir],
+[
+        AC_SUBST(event_LDFLAGS,["-L${withval}/lib -Wl,-rpath=${withval}/lib"])
+        AC_SUBST(event_CPPFLAGS,"-I${withval}/include")
+],
+[])
+
+AC_ARG_WITH(ssl,
+[  --with-ssl=dir          use openssl installed in dir],
+[
+        AC_SUBST(openssl_LDFLAGS,["-L${withval}/lib -Wl,-rpath=${withval}/lib"])
+        AC_SUBST(openssl_CPPFLAGS,"-I${withval}/include")
+],
+[])
+
+AC_ARG_WITH(curlconf,
+[  --with-curlconf=prog    use this curl-config],
+[
+    CURLCONF=$withval
+],
+[])
+
+old_LDFLAGS="$LDFLAGS"
+LDFLAGS="$LDFLAGS $event_LDFLAGS"
+AC_CHECK_LIB(event, event_init, :, echo Must have libevent; exit 1)
+LDFLAGS=$old_LDFLAGS
+
+old_LDFLAGS="$LDFLAGS"
+LDFLAGS="$LDFLAGS $openssl_LDFLAGS"
+AC_CHECK_LIB(crypto, SHA1_Final, :, echo Must have openssl; exit 1)
+LDFLAGS=$old_LDFLAGS
+
+if test x$CURLCONF == x; then
+   AC_PATH_PROG(CURLCONF, curl-config)
+fi
+
+if test x$CURLCONF == x ; then
+   echo Must have curl-config
+   exit 1
+else
+   AC_SUBST(CURL_CFLAGS, `$CURLCONF --cflags`)
+   AC_SUBST(CURL_LDFLAGS, `$CURLCONF --libs`)
+fi
+
+AC_OUTPUT
diff --git a/hack.mk b/hack.mk
new file mode 100644
index 0000000..88a7bfb
--- /dev/null
+++ b/hack.mk
@@ -0,0 +1,4 @@
+clean:
+	find . -name \*~ -print0 | xargs -0 rm
+	find . -name \*.in -print0 | xargs -0 rm
+	rm -rf aclocal.m4 autom4te.cache compile config.* configure depcomp install-sh missing btpd-*.tar.gz
diff --git a/misc/Makefile.am b/misc/Makefile.am
new file mode 100644
index 0000000..38af57a
--- /dev/null
+++ b/misc/Makefile.am
@@ -0,0 +1,8 @@
+noinst_LIBRARIES=libmisc.a
+libmisc_a_SOURCES=\
+	benc.c benc.h\
+	stream.c stream.h\
+	subr.c subr.h\
+	metainfo.c metainfo.h\
+	iobuf.c iobuf.h
+libmisc_a_CPPFLAGS=@openssl_CPPFLAGS@
diff --git a/misc/benc.c b/misc/benc.c
new file mode 100644
index 0000000..632b0fd
--- /dev/null
+++ b/misc/benc.c
@@ -0,0 +1,345 @@
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "benc.h"
+
+#define benc_safeset(out, val) if ((out) != NULL) *(out) = (val)
+
+static const char *benc_validate_aux(const char *p, const char *end);
+
+int
+benc_validate(const char *p, size_t len)
+{
+    const char *end = p + len - 1;
+
+    if (len <= 0)
+	return EINVAL;
+
+    return benc_validate_aux(p, end) == end ? 0 : EINVAL;
+}
+
+static const char *
+benc_validate_aux(const char *p, const char *end)
+{
+    size_t d = 0;
+    switch (*p) {
+    case 'd':
+	d = 1;
+    case 'l':
+	for (p++; p <= end && *p != 'e'; p++) {
+	    if (d != 0) {
+		if (d % 2 == 1 && !isdigit(*p))
+		    return NULL;
+		else
+		    d++;
+	    }
+	    if ((p = benc_validate_aux(p, end)) == NULL)
+		return NULL;
+	}
+	if (p > end || (d != 0 && d % 2 != 1))
+	    return NULL;
+	break;
+    case 'i':
+	p++;
+	if (p > end)
+	    return NULL;
+	if (*p == '-')
+	    p++;
+	if (p > end || !isdigit(*p))
+	    return NULL;
+	p++;
+	while (p <= end && isdigit(*p))
+	    p++;
+	if (p > end || *p != 'e')
+	    return NULL;
+	break;
+    default:
+	if (isdigit(*p)) {
+	    size_t len = 0;
+	    while (p <= end && isdigit(*p)) {
+		len *= 10;
+		len += *p - '0';
+		p++;
+	    }
+	    if (p <= end && *p == ':' && p + len <= end)
+		p += len;
+	    else
+		return NULL;
+	}
+	else
+	    return NULL;
+	break;
+    }
+    return p;
+}
+
+size_t
+benc_length(const char *p)
+{
+    size_t blen;
+    const char *next;
+
+    switch (*p) {
+    case 'd':
+    case 'l':
+	blen = 2; // [l|d]...e
+	next = benc_first(p);
+	while (*next != 'e') {
+	    size_t len = benc_length(next);
+	    blen += len;
+	    next += len;
+	}
+	return blen;
+    case 'i':
+	for (next = p + 1; *next != 'e'; next++)
+	    ;
+	return next - p + 1;
+    default:
+	assert(benc_str(p, &next, &blen, NULL) == 0);
+	return next - p + blen;
+    }
+}
+
+size_t
+benc_nelems(const char *p)
+{
+    size_t nelems = 0;
+    for (p = benc_first(p); p != NULL; p = benc_next(p))
+	nelems++;
+    return nelems;
+}
+
+const char *
+benc_first(const char *p)
+{
+    assert(benc_islst(p));
+    return *(p + 1) == 'e' ? NULL : p + 1;
+}
+
+const char *
+benc_next(const char *p)
+{
+    size_t blen = benc_length(p);
+    return *(p + blen) == 'e' ? NULL : p + blen;
+}
+
+int
+benc_str(const char *p, const char **out, size_t *len, const char**next)
+{
+    size_t blen = 0;
+    assert(isdigit(*p));
+    blen = *p - '0';
+    p++;
+    while (isdigit(*p)) {
+	blen *= 10;
+	blen += *p - '0';
+	p++;
+    }
+    assert(*p == ':');
+    benc_safeset(len, blen);
+    benc_safeset(out, p + 1);
+    benc_safeset(next, *(p + blen + 1) == 'e' ? NULL : p + blen + 1);
+    return 0;
+}
+
+int
+benc_strz(const char *p, char **out, size_t *len, const char **next)
+{
+    int err;
+    size_t blen;
+    const char *bstr;
+
+    if ((err = benc_str(p, &bstr, &blen, next)) == 0) {
+	if ((*out = malloc(blen + 1)) != NULL) {
+	    memcpy(*out, bstr, blen);
+	    (*out)[blen] = '\0';
+	    benc_safeset(len, blen);
+	} else
+	    err = ENOMEM;
+    }
+    return err;
+}
+
+int
+benc_stra(const char *p, char **out, size_t *len, const char **next)
+{
+    int err;
+    size_t blen;
+    const char *bstr;
+
+    if ((err = benc_str(p, &bstr, &blen, next)) == 0) {
+	if ((*out = malloc(blen)) != NULL) {
+	    memcpy(*out, bstr, blen);
+	    benc_safeset(len, blen);
+	} else
+	    err = ENOMEM;
+    }
+    return err;
+}
+
+int
+benc_int64(const char *p, int64_t *out, const char **next)
+{
+    int sign = 1;
+    int64_t res = 0;
+
+    assert(*p == 'i');
+    p++;
+    if (*p == '-') {
+	sign = -1;
+	p++;
+    }
+    assert(isdigit(*p));
+    res += sign * (*p - '0');
+    p++;
+    while (isdigit(*p)) {
+	res *= sign * 10;
+	res += sign * (*p - '0');
+	p++;
+    }
+    assert(*p == 'e');
+    benc_safeset(out, res);
+    benc_safeset(next, *(p + 1) == 'e' ? NULL : p + 1);
+
+    return 0;
+}
+
+int
+benc_uint32(const char *p, uint32_t *out, const char **next)
+{
+    int err;
+    int64_t res;
+    if ((err = benc_int64(p, &res, next)) == 0) {
+	if (res >= 0 && res <= 0xffffffffUL)
+	    *out = (uint32_t)res;
+	else
+	    err = EINVAL;
+    }
+    return err;
+}
+
+int
+benc_dget_any(const char *p, const char *key, const char **val)
+{
+    int res;
+    size_t len, blen;
+    const char *bstr;
+
+    assert(benc_isdct(p));
+
+    len = strlen(key);
+
+    p = benc_first(p);
+    while (p != NULL) {
+	if ((res = benc_str(p, &bstr, &blen, &p)) != 0)
+	    return res;
+
+	res = strncmp(bstr, key, blen);
+	if (res == 0 && len == blen) {
+	    *val = p;
+	    return 0;
+	} else if (res <= 0) {
+	    p = benc_next(p);
+	} else
+	    return ENOENT;
+    }
+    return ENOENT;
+}
+
+int
+benc_dget_lst(const char *p, const char *key, const char **val)
+{
+    int err;
+    if ((err = benc_dget_any(p, key, val)) == 0)
+	if (!benc_islst(*val))
+	    err = EINVAL;
+    return err;
+}
+
+int
+benc_dget_dct(const char *p, const char *key, const char **val)
+{
+    int err;
+    if ((err = benc_dget_any(p, key, val)) == 0)
+	if (!benc_isdct(*val))
+	    err = EINVAL;
+    return err;
+}
+
+int
+benc_dget_str(const char *p, const char *key, const char **val, size_t *len)
+{
+    int err;
+    const char *sp;
+    if ((err = benc_dget_any(p, key, &sp)) == 0)
+	err = benc_isstr(sp) ? benc_str(sp, val, len, NULL) : EINVAL;
+    return err;	
+}
+
+int
+benc_dget_stra(const char *p, const char *key, char **val, size_t *len)
+{
+    int err;
+    const char *sp;
+    if ((err = benc_dget_any(p, key, &sp)) == 0)
+	err = benc_isstr(sp) ? benc_stra(sp, val, len, NULL) : EINVAL;
+    return err;	
+}
+
+int
+benc_dget_strz(const char *p, const char *key, char **val, size_t *len)
+{
+    int err;
+    const char *sp;
+    if ((err = benc_dget_any(p, key, &sp)) == 0)
+	err = benc_isstr(sp) ? benc_strz(sp, val, len, NULL) : EINVAL;
+    return err;	
+}
+
+int
+benc_dget_int64(const char *p, const char *key, int64_t *val)
+{
+    int err;
+    const char *ip;
+    if ((err = benc_dget_any(p, key, &ip)) == 0)
+	err = benc_isint(ip) ? benc_int64(ip, val, NULL) : EINVAL;
+    return err;
+} 
+
+int
+benc_dget_uint32(const char *p, const char *key, uint32_t *val)
+{
+    int err;
+    const char *ip;
+    if ((err = benc_dget_any(p, key, &ip)) == 0)
+	err = benc_isint(ip) ? benc_uint32(ip, val, NULL) : EINVAL;
+    return err;
+} 
+
+int
+benc_islst(const char *p)
+{
+    return *p == 'l' || *p == 'd';
+}
+
+int
+benc_isdct(const char *p)
+{
+    return *p == 'd';
+}
+
+int
+benc_isint(const char *p)
+{
+    return *p == 'i';
+}
+
+int
+benc_isstr(const char *p)
+{
+    return isdigit(*p);
+}
diff --git a/misc/benc.h b/misc/benc.h
new file mode 100644
index 0000000..0cc3873
--- /dev/null
+++ b/misc/benc.h
@@ -0,0 +1,37 @@
+#ifndef BTPD_BENC_H
+#define BTPD_BENC_H
+
+int benc_validate(const char *p, size_t len);
+
+size_t benc_length(const char *p);
+size_t benc_nelems(const char *p);
+
+const char *benc_first(const char *p);
+const char *benc_next(const char *p);
+
+int benc_str(const char *p, const char **mem, size_t *len, const char**next);
+int benc_stra(const char *p, char **out, size_t *len, const char **next);
+int benc_strz(const char *p, char **out, size_t *len, const char **next);
+int benc_int64(const char *p, int64_t *out, const char **next);
+int benc_uint32(const char *p, uint32_t *out, const char **next);
+
+#define benc_off benc_int64
+
+int benc_dget_any(const char *p, const char *key, const char **val);
+int benc_dget_lst(const char *p, const char *key, const char **val);
+int benc_dget_dct(const char *p, const char *key, const char **val);
+int benc_dget_str(const char *p, const char *key,
+		  const char **val, size_t *len);
+int benc_dget_stra(const char *p, const char *key, char **val, size_t *len);
+int benc_dget_strz(const char *p, const char *key, char **val, size_t *len);
+int benc_dget_int64(const char *p, const char *key, int64_t *val);
+int benc_dget_uint32(const char *p, const char *key, uint32_t *val);
+
+#define benc_dget_off benc_dget_int64
+
+int benc_islst(const char *p);
+int benc_isdct(const char *p);
+int benc_isint(const char *p);
+int benc_isstr(const char *p);
+
+#endif
diff --git a/misc/iobuf.c b/misc/iobuf.c
new file mode 100644
index 0000000..a7304c6
--- /dev/null
+++ b/misc/iobuf.c
@@ -0,0 +1,64 @@
+#include <errno.h>
+#include <inttypes.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "iobuf.h"
+
+#define GROWLEN (1 << 14)
+
+int
+buf_init(struct io_buffer *iob, size_t size)
+{
+    iob->buf_off = 0;
+    iob->buf_len = size;
+    iob->buf = malloc(size);
+    if (iob->buf == NULL)
+        return ENOMEM;
+    else
+        return 0;
+}
+
+int
+buf_grow(struct io_buffer *iob, size_t addlen)
+{
+    char *nbuf = realloc(iob->buf, iob->buf_len + addlen);
+    if (nbuf == NULL)
+        return ENOMEM;
+    else {
+        iob->buf = nbuf;
+        iob->buf_len += addlen;
+        return 0;
+    }
+}
+
+int
+buf_print(struct io_buffer *iob, const char *fmt, ...)
+{
+    int np;
+    va_list ap;
+    va_start(ap, fmt);
+    np = vsnprintf(NULL, 0, fmt, ap);
+    va_end(ap);
+    while (np + 1 > iob->buf_len - iob->buf_off)
+        if (buf_grow(iob, GROWLEN) != 0)
+            return ENOMEM;
+    va_start(ap, fmt);
+    vsnprintf(iob->buf + iob->buf_off, np + 1, fmt, ap);
+    va_end(ap);
+    iob->buf_off += np;
+    return 0;
+}
+
+int
+buf_write(struct io_buffer *iob, const void *buf, size_t len)
+{
+    while (iob->buf_len - iob->buf_off < len)
+        if (buf_grow(iob, GROWLEN) != 0)
+            return ENOMEM;
+    bcopy(buf, iob->buf + iob->buf_off, len);
+    iob->buf_off += len;
+    return 0;
+}
diff --git a/misc/iobuf.h b/misc/iobuf.h
new file mode 100644
index 0000000..ef213d1
--- /dev/null
+++ b/misc/iobuf.h
@@ -0,0 +1,15 @@
+#ifndef BTPD_IOBUF_H
+#define BTPD_IOBUF_H
+
+struct io_buffer {
+    size_t buf_off;
+    size_t buf_len;
+    char *buf;
+};
+
+int buf_init(struct io_buffer *iob, size_t size);
+int buf_grow(struct io_buffer *iob, size_t size);
+int buf_write(struct io_buffer *iob, const void *data, size_t size);
+int buf_print(struct io_buffer *iob, const char *fmt, ...);
+
+#endif
diff --git a/misc/metainfo.c b/misc/metainfo.c
new file mode 100644
index 0000000..61da727
--- /dev/null
+++ b/misc/metainfo.c
@@ -0,0 +1,275 @@
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <openssl/sha.h>
+
+#include "benc.h"
+#include "metainfo.h"
+#include "subr.h"
+
+/*
+ * d
+ * announce = url
+ * info = d
+ *   name = advisory file/dir save name
+ *   piece length = power of two length of each block
+ *   pieces = 20b of sha1-hash * num of pieces
+ *   length = length of file in bytes in single file download
+ *   files = l d
+ *     length = length of file in bytes
+ *     path = l path components
+ *
+ */
+
+#ifndef PRId64
+#define PRId64 "lld"
+#endif
+#ifndef PRIu32
+#define PRIu32 "u"
+#endif
+
+void
+print_metainfo(struct metainfo *tp)
+{
+    unsigned i;
+
+    printf("Info hash: ");
+    for (i = 0; i < 20; i++)
+	printf("%.2x", tp->info_hash[i]);
+    printf("\n");
+    printf("Tracker URL: %s\n", tp->announce);
+    printf("Piece length: %" PRId64 "\n", (int64_t)tp->piece_length);
+    printf("Number of pieces: %" PRIu32 "\n", tp->npieces);
+    printf("Number of files: %u\n", tp->nfiles);
+    printf("Advisory name: %s\n", tp->name);
+    printf("Files:\n");
+    for (i = 0; i < tp->nfiles; i++) {
+	printf("%s (%" PRId64 ")\n",
+	    tp->files[i].path, (int64_t)tp->files[i].length);
+    }
+    printf("Total length: %" PRId64 "\n\n", (int64_t)tp->total_length);
+}
+
+static int
+check_path(const char *path, size_t len)
+{
+    if (len == 0)
+	return 0;
+    else if (len == 1 && path[0] == '.')
+	return 0;
+    else if (len == 2 && path[0] == '.' && path[1] == '.')
+	return 0;
+    else if (memchr(path, '/', len) != NULL)
+	return 0;
+    return 1;
+}
+
+int
+fill_fileinfo(const char *fdct, struct fileinfo *tfp)
+{
+    int err;
+    size_t npath, plen, len;
+    const char *plst, *iter, *str;
+
+    if ((err = benc_dget_off(fdct, "length", &tfp->length)) != 0)
+	return err;
+
+    if ((err = benc_dget_lst(fdct, "path", &plst)) != 0)
+	return err;
+
+    npath = plen = 0;
+    iter = benc_first(plst);
+    while (iter != NULL) {
+	if (!benc_isstr(iter))
+	    return EINVAL;
+	benc_str(iter, &str, &len, &iter);
+	if (!check_path(str, len))
+	    return EINVAL;
+	npath++;
+	plen += len;
+    }
+    if (npath == 0)
+	return EINVAL;
+
+    if ((tfp->path = malloc(plen + (npath - 1) + 1)) == NULL)
+	return ENOMEM;
+
+    iter = benc_first(plst);
+    benc_str(iter, &str, &len, &iter);
+    memcpy(tfp->path, str, len);
+    plen = len;
+    npath--;
+    while (npath > 0) {
+	tfp->path[plen++] = '/';
+	benc_str(iter, &str, &len, &iter);
+	memcpy(tfp->path + plen, str, len);
+	plen += len;
+	npath--;
+    }
+    tfp->path[plen] = '\0';
+    return 0;
+}
+
+void
+clear_metainfo(struct metainfo *mip)
+{
+    int i;
+    if (mip->piece_hash != NULL)
+	free(mip->piece_hash);
+    if (mip->announce != NULL)
+	free(mip->announce);
+    if (mip->files != NULL) {
+	for (i = 0; i < mip->nfiles; i++) {
+	    if (mip->files[i].path != NULL)
+		free(mip->files[i].path);
+	}
+	free(mip->files);
+    }
+    if (mip->name != NULL)
+	free(mip->name);
+}
+
+int
+fill_metainfo(const char *bep, struct metainfo *tp, int mem_hashes)
+{
+    size_t len;
+    int err;
+    const char *base_addr = bep;
+    const char *hash_addr;
+
+    if (!benc_isdct(bep))
+	return EINVAL;
+
+    if ((err = benc_dget_strz(bep, "announce", &tp->announce, NULL)) != 0)
+	goto out;
+
+    if ((err = benc_dget_dct(bep, "info", &bep)) != 0)
+	goto out;
+
+    SHA1(bep, benc_length(bep), tp->info_hash);
+
+    if ((err = benc_dget_off(bep, "piece length", &tp->piece_length)) != 0)
+	goto out;
+
+    if ((err = benc_dget_str(bep, "pieces", &hash_addr, &len)) != 0)
+	goto out;
+
+    if (len % 20 != 0) {
+	err = EINVAL;
+	goto out;
+    }
+    tp->npieces = len / 20;
+
+    tp->pieces_off = hash_addr - base_addr;
+    
+    if (mem_hashes) {
+	if ((tp->piece_hash = malloc(len)) == NULL) {
+	    err = ENOMEM;
+	    goto out;
+	}
+	bcopy(hash_addr, tp->piece_hash, len);
+    }
+
+    if ((err = benc_dget_strz(bep, "name", &tp->name, NULL)) != 0)
+	goto out;
+
+    err = benc_dget_off(bep, "length", &tp->total_length);
+    if (err == 0) {
+	tp->nfiles = 1;
+	tp->files = calloc(1, sizeof(struct fileinfo));
+	if (tp->files != NULL) {
+	    tp->files[0].length = tp->total_length;
+	    tp->files[0].path = strdup(tp->name);
+	    if (tp->files[0].path == NULL) {
+		err = ENOMEM;
+		goto out;
+	    }
+	} else {
+	    err = ENOMEM;
+	    goto out;
+	}
+    }
+    else if (err == ENOENT) {
+	int i;
+	const char *flst, *fdct;
+
+	if ((err = benc_dget_lst(bep, "files", &flst)) != 0)
+	    goto out;
+
+	tp->nfiles = benc_nelems(flst);
+	if (tp->nfiles < 1) {
+	    err = EINVAL;
+	    goto out;
+	}
+	tp->files = calloc(tp->nfiles, sizeof(struct fileinfo));
+
+	tp->total_length = 0;
+	i = 0;
+	for (fdct = benc_first(flst); fdct != NULL; fdct = benc_next(fdct)) {
+	    if (!benc_isdct(fdct)) {
+		err = EINVAL;
+		goto out;
+	    }
+
+	    if ((err = fill_fileinfo(fdct, &tp->files[i])) != 0)
+		goto out;
+
+	    tp->total_length += tp->files[i].length;
+	    i++;
+	}
+    }
+    else
+	goto out;
+out:
+    if (err != 0)
+	clear_metainfo(tp);
+
+    return err;
+}
+
+int
+load_metainfo(const char *path, off_t size, int mem_hashes,
+              struct metainfo **res)
+{
+    char *buf;
+    int fd, err = 0;
+
+    if ((fd = open(path, O_RDONLY)) == -1)
+	return errno;
+
+    if (size <= 0) {
+	struct stat sb;
+	if (fstat(fd, &sb) == -1) {
+	    close(fd);
+	    return errno;
+	} else
+	    size = sb.st_size;
+    }
+
+    if ((buf = mmap(0, size, PROT_READ, MAP_PRIVATE, fd, 0)) == MAP_FAILED)
+	err = errno;
+    close(fd);
+
+    if (err == 0)
+	err = benc_validate(buf, size);
+
+    if (err == 0)
+	if ((*res = calloc(1, sizeof(**res))) == NULL)
+	    err = ENOMEM;
+
+    if (err == 0)
+	if ((err = fill_metainfo(buf, *res, mem_hashes)) != 0)
+	    free(*res);
+
+    munmap(buf, size);
+    return err;
+}
diff --git a/misc/metainfo.h b/misc/metainfo.h
new file mode 100644
index 0000000..242bab3
--- /dev/null
+++ b/misc/metainfo.h
@@ -0,0 +1,28 @@
+#ifndef BTPD_METAINFO_H
+#define BTPD_METAINFO_H
+
+struct fileinfo {
+    char *path;
+    off_t length;
+};
+
+struct metainfo {
+    char *name;
+    char *announce;
+    uint8_t info_hash[20];
+    uint8_t (*piece_hash)[20];
+    unsigned pieces_off;
+    uint32_t npieces;
+    off_t piece_length;
+    off_t total_length;
+    unsigned nfiles;
+    struct fileinfo *files;
+};
+
+int fill_fileinfo(const char *fdct, struct fileinfo *fip);
+int fill_metainfo(const char *base, struct metainfo *mip, int mem_hashes);
+void clear_metainfo(struct metainfo *mip);
+void print_metainfo(struct metainfo *mip);
+int load_metainfo(const char *path, off_t size, int mem_hashes, struct metainfo **res);
+
+#endif
diff --git a/misc/stream.c b/misc/stream.c
new file mode 100644
index 0000000..ee98374
--- /dev/null
+++ b/misc/stream.c
@@ -0,0 +1,241 @@
+#include <assert.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <openssl/sha.h>
+
+#include "metainfo.h"
+#include "subr.h"
+#include "stream.h"
+
+struct bt_stream_ro *
+bts_open_ro(struct metainfo *meta, off_t off, F_fdcb fd_cb, void *fd_arg)
+{
+    struct bt_stream_ro *bts = malloc(sizeof(*bts));
+    if (bts == NULL)
+	return NULL;
+
+    bts->meta = meta;
+    bts->fd_cb = fd_cb;
+    bts->fd_arg = fd_arg;
+    bts->t_off = 0;
+    bts->f_off = 0;
+    bts->index = 0;
+    bts->fd = -1;
+    bts_seek_ro(bts, off);
+    return bts;
+}
+
+void
+bts_seek_ro(struct bt_stream_ro *bts, off_t off)
+{
+    struct fileinfo *files = bts->meta->files;
+
+    assert(off >= 0 && off <= bts->meta->total_length);
+
+    if (bts->fd != -1) {
+	close(bts->fd);
+	bts->fd = -1;
+    }
+
+    bts->t_off = off;
+    bts->index = 0;
+
+    while (off >= files[bts->index].length) {
+	off -= files[bts->index].length;
+	bts->index++;
+    }
+
+    bts->f_off = off;
+}
+
+int
+bts_read_ro(struct bt_stream_ro *bts, char *buf, size_t len)
+{
+    struct fileinfo *files = bts->meta->files;
+    size_t boff, wantread;
+    ssize_t didread;
+
+    assert(bts->t_off + len <= bts->meta->total_length);
+
+    boff = 0;
+    while (boff < len) {
+	if (bts->fd == -1) {
+	    int err =
+		bts->fd_cb(files[bts->index].path, &bts->fd, bts->fd_arg);
+	    if (err != 0)
+		return err;
+	    if (bts->f_off != 0)
+		lseek(bts->fd, bts->f_off, SEEK_SET);
+	}
+
+	wantread = min(len - boff, files[bts->index].length - bts->f_off);
+    again:
+	didread = read(bts->fd, buf + boff, wantread);
+	if (didread == -1) {
+            if (errno == EINTR)
+		goto again;
+	    else
+		return errno;
+	}
+
+	boff += didread;
+	bts->f_off += didread;
+	bts->t_off += didread;
+	if (bts->f_off == files[bts->index].length) {
+	    close(bts->fd);
+	    bts->fd = -1;
+	    bts->f_off = 0;
+	    bts->index++;
+	}
+	if (didread != wantread)
+	    return ENOENT;
+    }
+    return 0;
+}
+
+void
+bts_close_ro(struct bt_stream_ro *bts)
+{
+    if (bts->fd != -1)
+	close(bts->fd);
+    free(bts);
+}
+
+#define SHAFILEBUF (1 << 15)
+
+int
+bts_sha(struct bt_stream_ro *bts, off_t length, uint8_t *hash)
+{
+    SHA_CTX ctx;
+    char buf[SHAFILEBUF];
+    size_t wantread;
+    int err = 0;
+
+    SHA1_Init(&ctx);
+    while (length > 0) {
+	wantread = min(length, SHAFILEBUF);
+	if ((err = bts_read_ro(bts, buf, wantread)) != 0)
+	    break;
+	length -= wantread;
+	SHA1_Update(&ctx, buf, wantread);
+    }
+    SHA1_Final(hash, &ctx);
+    return err;
+}
+
+int
+bts_hashes(struct metainfo *meta,
+	   F_fdcb fd_cb,
+	   void (*cb)(uint32_t, uint8_t *, void *),
+	   void *arg)
+{
+    int err = 0;
+    uint8_t hash[SHA_DIGEST_LENGTH];
+    uint32_t piece;
+    struct bt_stream_ro *bts;
+    off_t plen = meta->piece_length;
+    off_t llen = meta->total_length % plen;
+
+    if ((bts = bts_open_ro(meta, 0, fd_cb, arg)) == NULL)
+	return ENOMEM;
+    
+    for (piece = 0; piece < meta->npieces; piece++) {	
+        if (piece < meta->npieces - 1)
+	    err = bts_sha(bts, plen, hash);
+	else
+	    err = bts_sha(bts, llen, hash);
+
+	if (err == 0)
+	    cb(piece, hash, arg);
+	else if (err == ENOENT) {
+	    cb(piece, NULL, arg);
+	    if (piece < meta->npieces - 1)
+		bts_seek_ro(bts, (piece + 1) * plen);
+	    err = 0;
+	} else
+	    break;
+    }
+    bts_close_ro(bts);
+    return err;
+}
+
+struct bt_stream_wo *
+bts_open_wo(struct metainfo *meta, off_t off, F_fdcb fd_cb, void *fd_arg)
+{
+    struct bt_stream_wo *bts = malloc(sizeof(*bts));
+    if (bts == NULL)
+	return NULL;
+
+    bts->meta = meta;
+    bts->fd_cb = fd_cb;
+    bts->fd_arg = fd_arg;
+    bts->t_off = 0;
+    bts->f_off = 0;
+    bts->index = 0;
+    bts->fd = -1;
+    bts_seek_ro((struct bt_stream_ro *)bts, off);
+    return bts;
+}
+
+int
+bts_write_wo(struct bt_stream_wo *bts, const char *buf, size_t len)
+{
+    struct fileinfo *files = bts->meta->files;
+    size_t boff, wantwrite;
+    ssize_t didwrite;
+
+    assert(bts->t_off + len <= bts->meta->total_length);
+
+    boff = 0;
+    while (boff < len) {
+	if (bts->fd == -1) {
+	    int err =
+		bts->fd_cb(files[bts->index].path, &bts->fd, bts->fd_arg);
+	    if (err != 0)
+		return err;
+	    if (bts->f_off != 0)
+		lseek(bts->fd, bts->f_off, SEEK_SET);
+	}
+
+	wantwrite = min(len - boff, files[bts->index].length - bts->f_off);
+	didwrite = write(bts->fd, buf + boff, wantwrite);
+	if (didwrite == -1)
+		return errno;
+
+	boff += didwrite;
+	bts->f_off += didwrite;
+	bts->t_off += didwrite;
+	if (bts->f_off == files[bts->index].length) {
+            if (fsync(bts->fd) == -1) {
+                int err = errno;
+                close(bts->fd);
+                return err;
+            }
+            if (close(bts->fd) == -1)
+                return errno;
+	    bts->fd = -1;
+	    bts->f_off = 0;
+	    bts->index++;
+	}
+    }
+    return 0;
+}
+
+int
+bts_close_wo(struct bt_stream_wo *bts)
+{
+    int err = 0;
+    if (bts->fd != -1) {
+	if (fsync(bts->fd) == -1) {
+	    err = errno;
+	    close(bts->fd);
+	} else if (close(bts->fd) == -1)
+	    err = errno;
+    }
+    free(bts);
+    return err;
+}
diff --git a/misc/stream.h b/misc/stream.h
new file mode 100644
index 0000000..f71e5c9
--- /dev/null
+++ b/misc/stream.h
@@ -0,0 +1,36 @@
+#ifndef BTPD_STREAM_H
+#define BTPD_STREAM_H
+
+typedef int (*F_fdcb)(const char *, int *, void *);
+
+#define def_stream(name) \
+struct name {\
+    struct metainfo *meta;\
+    F_fdcb fd_cb;\
+    void *fd_arg;\
+    unsigned index;\
+    off_t t_off;\
+    off_t f_off;\
+    int fd;\
+}
+
+def_stream(bt_stream_ro);
+
+struct bt_stream_ro *
+bts_open_ro(struct metainfo *meta, off_t off, F_fdcb fd_cb, void *fd_arg);
+int bts_read_ro(struct bt_stream_ro *bts, char *buf, size_t len);
+void bts_seek_ro(struct bt_stream_ro *bts, off_t nbytes);
+void bts_close_ro(struct bt_stream_ro *bts);
+
+def_stream(bt_stream_wo);
+
+struct bt_stream_wo *
+bts_open_wo(struct metainfo *meta, off_t off, F_fdcb fd_cb, void *fd_arg);
+int bts_write_wo(struct bt_stream_wo *bts, const char *buf, size_t len);
+int bts_close_wo(struct bt_stream_wo *bts);
+
+int bts_sha(struct bt_stream_ro *bts, off_t length, uint8_t *hash);
+int bts_hashes(struct metainfo *, F_fdcb fd_cb,
+	       void (*cb)(uint32_t, uint8_t *, void *), void *arg);
+
+#endif
diff --git a/misc/subr.c b/misc/subr.c
new file mode 100644
index 0000000..abbf4e9
--- /dev/null
+++ b/misc/subr.c
@@ -0,0 +1,141 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+void
+set_bit(uint8_t *bits, unsigned long index)
+{
+    bits[index / 8] |= (1 << (7 - index % 8));
+}
+
+void
+clear_bit(uint8_t *bits, unsigned long index)
+{
+    bits[index / 8] &= ~(1 << (7 - index % 8));
+}
+
+int
+has_bit(uint8_t *bits, unsigned long index)
+{
+    return bits[index / 8] & (1 << (7 - index % 8));
+}
+
+int
+set_nonblocking(int fd)
+{
+    int oflags;
+    if ((oflags = fcntl(fd, F_GETFL, 0)) == -1)
+	return errno;
+    if (fcntl(fd, F_SETFL, oflags | O_NONBLOCK) == -1)
+	return errno;
+    return 0;
+}
+
+int
+set_blocking(int fd)
+{
+    int oflags;
+    if ((oflags = fcntl(fd, F_GETFL, 0)) == -1)
+	return errno;
+    if (fcntl(fd, F_SETFL, oflags & ~O_NONBLOCK) == -1)
+	return errno;
+    return 0;
+}
+
+int
+mkdirs(char *path)
+{
+    int err = 0;
+    char *spos = strchr(path + 1, '/'); // Must ignore the root
+
+    while (spos != NULL) {
+	*spos = '\0';
+	err = mkdir(path, 0777);
+	*spos = '/';
+	
+	if (err != 0 && errno != EEXIST) {
+	    err = errno;
+	    break;
+	}
+
+	spos = strchr(spos + 1, '/');
+    }
+    return err;
+}
+
+int
+vopen(int *res, int flags, const char *fmt, ...)
+{
+    int fd, didmkdirs;
+    char path[PATH_MAX + 1];
+    va_list ap;
+
+    va_start(ap, fmt);
+    if (vsnprintf(path, PATH_MAX, fmt, ap) >= PATH_MAX) {
+	va_end(ap);
+	return ENAMETOOLONG;
+    }
+    va_end(ap);
+
+    didmkdirs = 0;
+again:
+    fd = open(path, flags, 0666);
+    if (fd < 0 && errno == ENOENT && (flags & O_CREAT) != 0 && !didmkdirs) {
+	if (mkdirs(path) == 0) {
+	    didmkdirs = 1;
+	    goto again;
+	} else
+	    return errno;
+    }
+
+    if (fd >= 0) {
+	*res = fd;
+	return 0;
+    } else
+	return errno;
+}
+
+int
+canon_path(const char *path, char **res)
+{
+    char rp[PATH_MAX];
+
+    if (realpath(path, rp) == NULL)
+	return errno;
+#if 0
+    // This could be necessary on solaris.
+    if (rp[0] != '/') {
+	char wd[MAXPATHLEN];
+	if (getcwd(wd, MAXPATHLEN) == NULL)
+	    return errno;
+	if (strlcat(wd, "/", MAXPATHLEN) >= MAXPATHLEN)
+	    return ENAMETOOLONG;
+	if (strlcat(wd, rp, MAXPATHLEN) >= MAXPATHLEN)
+	    return ENAMETOOLONG;
+	strcpy(rp, wd);
+    }
+#endif
+    if ((*res = strdup(rp)) == NULL)
+	return ENOMEM;
+
+    return 0;
+}
+
+size_t
+round_to_page(size_t size)
+{
+    size_t psize = getpagesize();
+    size_t rem = size % psize;
+    if (rem != 0)
+	size += psize - rem;
+    return size;
+}
diff --git a/misc/subr.h b/misc/subr.h
new file mode 100644
index 0000000..9467839
--- /dev/null
+++ b/misc/subr.h
@@ -0,0 +1,21 @@
+#ifndef BTPD_SUBR_H
+#define BTPD_SUBR_H
+
+#define min(x, y) ((x) <= (y) ? (x) : (y))
+
+int set_nonblocking(int fd);
+int set_blocking(int fd);
+
+int mkdirs(char *path);
+
+int vopen(int *resfd, int flags, const char *fmt, ...);
+
+void set_bit(uint8_t *bits, unsigned long index);
+int has_bit(uint8_t *bits, unsigned long index);
+void clear_bit(uint8_t *bits, unsigned long index);
+
+int canon_path(const char *path, char **res);
+
+size_t round_to_page(size_t size);
+
+#endif