about summary refs log tree commit diff
path: root/misc
diff options
context:
space:
mode:
Diffstat (limited to 'misc')
-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
11 files changed, 1211 insertions, 0 deletions
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