about summary refs log tree commit diff
diff options
context:
space:
mode:
authorRichard Nyberg <rnyberg@murmeldjur.se>2007-02-24 19:59:48 +0000
committerRichard Nyberg <rnyberg@murmeldjur.se>2007-02-24 19:59:48 +0000
commit8d7898d7281c8af387b120a411f33253b424c310 (patch)
treebdc47e77d336e9ba6c1a8ab577d358354323a08e
parentd00d1fc36abad95f90bc7fb8d9ec80b0186485e1 (diff)
downloadbtpd-8d7898d7281c8af387b120a411f33253b424c310.tar.gz
btpd-8d7898d7281c8af387b120a411f33253b424c310.zip
o Make errors when reading or writing torrent data non fatal. Instead of
  killing btpd, only the troublesome torrent will be stopped.
o Some code shuffle.

-rw-r--r--btpd/cli_if.c12
-rw-r--r--btpd/content.c449
-rw-r--r--btpd/content.h3
-rw-r--r--btpd/download.c13
-rw-r--r--btpd/tlib.c75
-rw-r--r--btpd/tlib.h16
-rw-r--r--btpd/torrent.c28
-rw-r--r--btpd/torrent.h3
8 files changed, 315 insertions, 284 deletions
diff --git a/btpd/cli_if.c b/btpd/cli_if.c
index e35f222..017cbad 100644
--- a/btpd/cli_if.c
+++ b/btpd/cli_if.c
@@ -156,13 +156,13 @@ write_ans(struct io_buffer *iob, struct tlib *tl, enum ipc_tval val)
                 ts = IPC_TSTATE_START;
                 break;
             case T_STOPPING:
-                ts= IPC_TSTATE_STOP;
+                ts = IPC_TSTATE_STOP;
                 break;
-            case T_ACTIVE:
-                if (cm_full(tl->tp))
-                    ts = IPC_TSTATE_SEED;
-                else
-                    ts = IPC_TSTATE_LEECH;
+            case T_SEED:
+                ts = IPC_TSTATE_SEED;
+                break;
+            case T_LEECH:
+                ts = IPC_TSTATE_LEECH;
                 break;
             }
         }
diff --git a/btpd/content.c b/btpd/content.c
index 5fef38f..1ea67ee 100644
--- a/btpd/content.c
+++ b/btpd/content.c
@@ -12,14 +12,11 @@
 #include "btpd.h"
 #include "stream.h"
 
-struct rstat {
-    time_t mtime;
-    off_t size;
-};
-
 struct content {
     enum { CM_INACTIVE, CM_STARTING, CM_ACTIVE } state;
 
+    int error;
+
     uint32_t npieces_got;
 
     off_t ncontent_bytes;
@@ -40,9 +37,6 @@ struct content {
 
 static const uint8_t m_zerobuf[ZEROBUFLEN];
 
-int stat_and_adjust(struct torrent *tp, struct rstat ret[]);
-static int save_resume(struct torrent *tp, struct rstat sbs[]);
-
 static int
 fd_cb_rd(const char *path, int *fd, void *arg)
 {
@@ -57,23 +51,9 @@ fd_cb_wr(const char *path, int *fd, void *arg)
     return vopen(fd, O_RDWR, "%s/%s", tp->tl->dir, path);
 }
 
-struct pct_data {
-    off_t off, remain;
-    struct torrent *tp;
-    SHA_CTX sha;
-    BTPDQ_ENTRY(pct_data) entry;
-    uint32_t piece;
-    void (*cb)(struct torrent *, uint32_t, int);
-};
-
-BTPDQ_HEAD(pct_tq, pct_data);
-
-static struct pct_tq m_pctq = BTPDQ_HEAD_INITIALIZER(m_pctq);
-static void cm_write_done(struct torrent *tp);
-
 struct start_test_data {
     struct torrent *tp;
-    struct rstat *rstat;
+    struct file_time_size *fts;
     uint32_t start;
     BTPDQ_ENTRY(start_test_data) entry;
 };
@@ -90,127 +70,39 @@ static int
 test_hash(struct torrent *tp, uint8_t *hash, uint32_t piece)
 {
     char piece_hash[SHA_DIGEST_LENGTH];
-    int fd;
-    int err;
-
-    err = vopen(&fd, O_RDONLY, "torrents/%s/torrent", tp->relpath);
-    if (err != 0)
-        btpd_err("failed to open 'torrents/%s/torrent' (%s).\n",
-            tp->relpath, strerror(err));
-
-    lseek(fd, tp->pieces_off + piece * SHA_DIGEST_LENGTH, SEEK_SET);
-    read(fd, piece_hash, SHA_DIGEST_LENGTH);
-    close(fd);
-
+    tlib_read_hash(tp->tl, tp->pieces_off, piece, piece_hash);
     return bcmp(hash, piece_hash, SHA_DIGEST_LENGTH);
 }
 
-void
-pct_create(struct torrent *tp, uint32_t piece,
-    void (*cb)(struct torrent *, uint32_t, int))
-{
-    struct pct_data *p = btpd_calloc(1, sizeof(*p));
-    p->piece = piece;
-    p->tp = tp;
-    p->off = piece * tp->piece_length;
-    p->remain = torrent_piece_size(tp, piece);
-    SHA1_Init(&p->sha);
-    p->cb = cb;
-    BTPDQ_INSERT_TAIL(&m_pctq, p, entry);
-    btpd_ev_add(&m_workev, (& (struct timeval) { 0, 0 }));
-}
-
-void
-pct_kill(struct pct_data *p)
-{
-    BTPDQ_REMOVE(&m_pctq, p, entry);
-    free(p);
-}
-
-void
-pct_run(struct pct_data *p)
-{
-    char buf[READBUFLEN];
-    size_t unit = (10 << 14);
-
-    while (p->remain > 0 && unit > 0) {
-        size_t wantread = min(p->remain, sizeof(buf));
-        if (wantread > unit)
-            wantread = unit;
-        if ((errno = bts_get(p->tp->cm->rds, p->off, buf, wantread)) != 0)
-            btpd_err("IO error on '%s' (%s).\n", bts_filename(p->tp->cm->rds),
-                strerror(errno));
-        p->remain -= wantread;
-        unit -= wantread;
-        p->off += wantread;
-        SHA1_Update(&p->sha, buf, wantread);
-    }
-    if (p->remain == 0) {
-        uint8_t hash[SHA_DIGEST_LENGTH];
-        SHA1_Final(hash, &p->sha);
-        p->cb(p->tp, p->piece, test_hash(p->tp, hash, p->piece) == 0);
-        pct_kill(p);
-    }
-}
-
-void
-pct_cb(struct torrent *tp, uint32_t piece, int ok)
-{
-    struct content *cm = tp->cm;
-    if (ok) {
-        assert(cm->npieces_got < tp->npieces);
-        cm->npieces_got++;
-        set_bit(cm->piece_field, piece);
-        if (net_active(tp))
-            dl_on_ok_piece(tp->net, piece);
-        if (cm_full(tp))
-            cm_write_done(tp);
-    } else {
-        cm->ncontent_bytes -= torrent_piece_size(tp, piece);
-        bzero(cm->block_field + piece * cm->bppbf, cm->bppbf);
-        if (net_active(tp))
-            dl_on_bad_piece(tp->net, piece);
-    }
-}
-
-void
-work_stop(struct torrent *tp)
+static int
+test_piece(struct torrent *tp, uint32_t piece, int *ok)
 {
-    struct content *cm = tp->cm;
-    struct pct_data *pct, *next;
-    if (cm->state == CM_STARTING) {
-        struct start_test_data *std;
-        BTPDQ_FOREACH(std, &m_startq, entry)
-            if (std->tp == tp) {
-                BTPDQ_REMOVE(&m_startq, std, entry);
-                free(std->rstat);
-                free(std);
-                break;
-            }
+    int err;
+    uint8_t hash[SHA_DIGEST_LENGTH];
+    if ((err = bts_sha(tp->cm->rds, piece * tp->piece_length,
+             torrent_piece_size(tp, piece), hash)) != 0) {
+        btpd_log(BTPD_L_ERROR, "io error on '%s' (%s).\n",
+            bts_filename(tp->cm->rds), strerror(err));
+        return err;;
     }
-    BTPDQ_FOREACH_MUTABLE(pct, &m_pctq, entry, next)
-        if (pct->tp == tp)
-            pct_kill(pct);
+    *ok = test_hash(tp, hash, piece) == 0;
+    return 0;
 }
 
 static int test_hash(struct torrent *tp, uint8_t *hash, uint32_t piece);
 
+static void startup_test_run(void);
+
 void
 worker_cb(int fd, short type, void *arg)
 {
-    struct pct_data *p = BTPDQ_FIRST(&m_pctq);
-    if (p == NULL)
-        return;
-    pct_run(p);
-    if (!BTPDQ_EMPTY(&m_pctq))
-        event_add(&m_workev, (& (struct timeval) { 0, 0 }));
+    startup_test_run();
 }
 
 void
 cm_kill(struct torrent *tp)
 {
     struct content *cm = tp->cm;
-    bts_close(cm->rds);
     free(cm->piece_field);
     free(cm->block_field);
     free(cm->pos_field);
@@ -218,25 +110,41 @@ cm_kill(struct torrent *tp)
     tp->cm = NULL;
 }
 
+static int stat_and_adjust(struct torrent *tp, struct file_time_size ret[]);
+
 void
 cm_save(struct torrent *tp)
 {
-    struct rstat sbs[tp->nfiles];
-    if (stat_and_adjust(tp, sbs) == 0)
-        save_resume(tp, sbs);
+    struct file_time_size fts[tp->nfiles];
+    stat_and_adjust(tp, fts);
+    tlib_save_resume(tp->tl, tp->nfiles, fts,
+        ceil(tp->npieces / 8.0), tp->cm->piece_field,
+        tp->cm->bppbf * tp->npieces, tp->cm->block_field);
+}
+
+static void
+cm_on_error(struct torrent *tp)
+{
+    if (!tp->cm->error) {
+        tp->cm->error = 1;
+        cm_stop(tp);
+    }
 }
 
 static void
 cm_write_done(struct torrent *tp)
 {
+    int err = 0;
     struct content *cm = tp->cm;
 
-    if ((errno = bts_close(cm->wrs)) != 0)
-        btpd_err("error closing write stream for '%s' (%s).\n",
-            torrent_name(tp), strerror(errno));
+    if ((err = bts_close(cm->wrs)) != 0)
+        btpd_log(BTPD_L_ERROR, "error closing write stream for '%s' (%s).\n",
+            torrent_name(tp), strerror(err));
     cm->wrs = NULL;
     btpd_ev_del(&cm->save_timer);
-    cm_save(tp);
+    if (!err)
+        cm_save(tp);
+    cm_on_error(tp);
 }
 
 void
@@ -244,10 +152,24 @@ cm_stop(struct torrent *tp)
 {
     struct content *cm = tp->cm;
 
-    if (cm->state == CM_ACTIVE && !cm_full(tp))
-        cm_write_done(tp);
+    if (cm->state != CM_STARTING && cm->state != CM_ACTIVE)
+        return;
+
+    if (cm->state == CM_STARTING) {
+        struct start_test_data *std;
+        BTPDQ_FOREACH(std, &m_startq, entry)
+            if (std->tp == tp) {
+                BTPDQ_REMOVE(&m_startq, std, entry);
+                free(std->fts);
+                free(std);
+                break;
+            }
+    }
 
-    work_stop(tp);
+    if (cm->rds != NULL)
+        bts_close(cm->rds);
+    if (cm->wrs != NULL)
+        cm_write_done(tp);
 
     cm->state = CM_INACTIVE;
 }
@@ -260,6 +182,12 @@ cm_active(struct torrent *tp)
 }
 
 int
+cm_error(struct torrent *tp)
+{
+    return tp->cm->error;
+}
+
+int
 cm_started(struct torrent *tp)
 {
     struct content *cm = tp->cm;
@@ -295,13 +223,18 @@ int
 cm_get_bytes(struct torrent *tp, uint32_t piece, uint32_t begin, size_t len,
     uint8_t **buf)
 {
+    if (tp->cm->error)
+        return EIO;
+
     *buf = btpd_malloc(len);
     int err =
         bts_get(tp->cm->rds, piece * tp->piece_length + begin, *buf, len);
-    if (err != 0)
-        btpd_err("IO error on '%s' (%s).\n", bts_filename(tp->cm->rds),
-            strerror(err));
-    return 0;
+    if (err != 0) {
+        btpd_log(BTPD_L_ERROR, "io error on '%s' (%s).\n",
+            bts_filename(tp->cm->rds), strerror(err));
+        cm_on_error(tp);
+    }
+    return err;
 }
 
 void
@@ -316,7 +249,24 @@ cm_prealloc(struct torrent *tp, uint32_t piece)
 void
 cm_test_piece(struct torrent *tp, uint32_t piece)
 {
-    pct_create(tp, piece, pct_cb);
+    int ok;
+    struct content *cm = tp->cm;
+    if ((errno = test_piece(tp, piece, &ok)) != 0)
+        cm_on_error(tp);
+    else if (ok) {
+        assert(cm->npieces_got < tp->npieces);
+        cm->npieces_got++;
+        set_bit(cm->piece_field, piece);
+        if (net_active(tp))
+            dl_on_ok_piece(tp->net,piece);
+        if (cm_full(tp))
+            cm_write_done(tp);
+    } else {
+        cm->ncontent_bytes -= torrent_piece_size(tp,piece);
+        bzero(cm->block_field + piece * cm->bppbf, cm->bppbf);
+        if (net_active(tp))
+            dl_on_bad_piece(tp->net, piece);
+    }
 }
 
 int
@@ -326,6 +276,13 @@ cm_put_bytes(struct torrent *tp, uint32_t piece, uint32_t begin,
     int err;
     struct content *cm = tp->cm;
 
+    if (cm->error)
+        return EIO;
+
+    uint8_t *bf = cm->block_field + piece * cm->bppbf;
+    assert(!has_bit(bf, begin / PIECE_BLOCKLEN));
+    assert(!has_bit(cm->piece_field, piece));
+
     if (!has_bit(cm->pos_field, piece)) {
         unsigned npieces = ceil((double)cm_alloc_size / tp->piece_length);
         uint32_t start = piece - piece % npieces;
@@ -333,13 +290,17 @@ cm_put_bytes(struct torrent *tp, uint32_t piece, uint32_t begin,
 
         while (start < end) {
             if (!has_bit(cm->pos_field, start)) {
+                assert(!has_bit(cm->piece_field, start));
                 off_t len = torrent_piece_size(tp, start);
                 off_t off = tp->piece_length * start;
                 while (len > 0) {
                     size_t wlen = min(ZEROBUFLEN, len);
-                    if ((err = bts_put(cm->wrs, off, m_zerobuf, wlen)) != 0)
-                        btpd_err("IO error on '%s' (%s).\n",
+                    if ((err = bts_put(cm->wrs, off, m_zerobuf, wlen)) != 0) {
+                        btpd_log(BTPD_L_ERROR, "io error on '%s' (%s).\n",
                             bts_filename(cm->wrs), strerror(errno));
+                        cm_on_error(tp);
+                        return err;
+                    }
 
                     len -= wlen;
                     off += wlen;
@@ -350,12 +311,14 @@ cm_put_bytes(struct torrent *tp, uint32_t piece, uint32_t begin,
         }
     }
     err = bts_put(cm->wrs, piece * tp->piece_length + begin, buf, len);
-    if (err != 0)
-        btpd_err("IO error on '%s' (%s)\n", bts_filename(cm->wrs),
-            strerror(err));
+    if (err != 0) {
+        btpd_log(BTPD_L_ERROR, "io error on '%s' (%s)\n",
+            bts_filename(cm->wrs), strerror(err));
+        cm_on_error(tp);
+        return err;
+    }
 
     cm->ncontent_bytes += len;
-    uint8_t *bf = cm->block_field + piece * cm->bppbf;
     set_bit(bf, begin / PIECE_BLOCKLEN);
 
     return 0;
@@ -398,7 +361,7 @@ cm_has_piece(struct torrent *tp, uint32_t piece)
 }
 
 int
-stat_and_adjust(struct torrent *tp, struct rstat ret[])
+stat_and_adjust(struct torrent *tp, struct file_time_size ret[])
 {
     int fd;
     char path[PATH_MAX];
@@ -426,63 +389,8 @@ again:
     return 0;
 }
 
-static int
-load_resume(struct torrent *tp, struct rstat sbs[])
-{
-    int err, ver;
-    FILE *fp;
-    size_t pfsiz = ceil(tp->npieces / 8.0);
-    size_t bfsiz = tp->npieces * tp->cm->bppbf;
-
-    if ((err = vfopen(&fp, "r" , "torrents/%s/resume", tp->relpath)) != 0)
-        return err;
-
-    if (fscanf(fp, "%d\n", &ver) != 1)
-        goto invalid;
-    if (ver != 1)
-        goto invalid;
-    for (int i = 0; i < tp->nfiles; i++) {
-        quad_t size;
-        long time;
-        if (fscanf(fp, "%qd %ld\n", &size, &time) != 2)
-            goto invalid;
-        if (sbs[i].size != size || sbs[i].mtime != time)
-            err = EINVAL;
-    }
-    if (fread(tp->cm->piece_field, 1, pfsiz, fp) != pfsiz)
-        goto invalid;
-    if (fread(tp->cm->block_field, 1, bfsiz, fp) != bfsiz)
-        goto invalid;
-    fclose(fp);
-    return err;
-invalid:
-    fclose(fp);
-    bzero(tp->cm->piece_field, pfsiz);
-    bzero(tp->cm->block_field, bfsiz);
-    return EINVAL;
-}
-
-static int
-save_resume(struct torrent *tp, struct rstat sbs[])
-{
-    int err;
-    FILE *fp;
-    if ((err = vfopen(&fp, "wb", "torrents/%s/resume", tp->relpath)) != 0)
-        return err;
-    fprintf(fp, "%d\n", 1);
-    for (int i = 0; i < tp->nfiles; i++)
-        fprintf(fp, "%lld %ld\n", (long long)sbs[i].size, (long)sbs[i].mtime);
-    fwrite(tp->cm->piece_field, 1, ceil(tp->npieces / 8.0), fp);
-    fwrite(tp->cm->block_field, 1, tp->npieces * tp->cm->bppbf, fp);
-    if (fclose(fp) != 0)
-        err = errno;
-    return err;
-}
-
-void start_test_cb(struct torrent *tp, uint32_t piece, int ok);
-
 void
-start_test_end(struct torrent *tp, int unclean)
+startup_test_end(struct torrent *tp, int unclean)
 {
     struct content *cm = tp->cm;
 
@@ -510,48 +418,62 @@ start_test_end(struct torrent *tp, int unclean)
         } else if (nblocks_got > 0)
             set_bit(cm->pos_field, piece);
     }
+    if (unclean) {
+        struct start_test_data *std = BTPDQ_FIRST(&m_startq);
+        BTPDQ_REMOVE(&m_startq, std, entry);
+        tlib_save_resume(tp->tl, tp->nfiles, std->fts,
+            ceil(tp->npieces / 8.0), cm->piece_field, cm->bppbf * 8,
+            cm->block_field);
+        free(std->fts);
+        free(std);
+    }
     if (!cm_full(tp)) {
         int err;
         if ((err = bts_open(&cm->wrs, tp->nfiles, tp->files,
-                 fd_cb_wr, tp)) != 0)
-            btpd_err("failed to open write stream for '%s' (%s).\n",
+                 fd_cb_wr, tp)) != 0) {
+            btpd_log(BTPD_L_ERROR,
+                "failed to open write stream for '%s' (%s).\n",
                 torrent_name(tp), strerror(err));
+            cm_on_error(tp);
+            return;
+        }
         btpd_ev_add(&cm->save_timer, SAVE_INTERVAL);
     }
-    if (unclean) {
-        struct start_test_data *std = BTPDQ_FIRST(&m_startq);
-
-        assert(std->tp == tp);
-        BTPDQ_REMOVE(&m_startq, std, entry);
-        save_resume(tp, std->rstat);
-        free(std->rstat);
-        free(std);
-
-        if ((std = BTPDQ_FIRST(&m_startq)) != NULL)
-            pct_create(std->tp, std->start, start_test_cb);
-    }
     cm->state = CM_ACTIVE;
 }
 
 void
-start_test_cb(struct torrent *tp, uint32_t piece, int ok)
+startup_test_run(void)
 {
-    struct content *cm = tp->cm;
+    int ok;
+    struct torrent *tp;
+    struct content *cm;
+    struct start_test_data * std = BTPDQ_FIRST(&m_startq);
+    uint32_t this;
+    if (std == NULL)
+        return;
+    tp = std->tp;
+    cm = tp->cm;
+    if (test_piece(std->tp, std->start, &ok) != 0) {
+        cm_on_error(std->tp);
+        return;
+    }
     if (ok)
-        set_bit(cm->piece_field, piece);
+        set_bit(cm->piece_field, std->start);
     else
-        clear_bit(cm->piece_field, piece);
-    piece++;
-    while (piece < tp->npieces && !has_bit(cm->pos_field, piece))
-        piece++;
-    if (piece < tp->npieces)
-        pct_create(tp, piece, start_test_cb);
-    else
-        start_test_end(tp, 1);
+        clear_bit(cm->piece_field, std->start);
+    this = std->start;
+    do
+        std->start++;
+    while (std->start < tp->npieces && !has_bit(cm->pos_field, std->start));
+    if (std->start >= tp->npieces)
+        startup_test_end(tp, 1);
+    if (!BTPDQ_EMPTY(&m_startq))
+        event_add(&m_workev, (& (struct timeval) { 0, 0 }));
 }
 
 void
-start_test(struct torrent *tp, struct rstat *sbs)
+startup_test_begin(struct torrent *tp, struct file_time_size *fts)
 {
     uint32_t piece = 0;
     struct content *cm = tp->cm;
@@ -561,45 +483,63 @@ start_test(struct torrent *tp, struct rstat *sbs)
         struct start_test_data *std = btpd_calloc(1, sizeof(*std));
         std->tp = tp;
         std->start = piece;
-        std->rstat = sbs;
+        std->fts = fts;
         BTPDQ_INSERT_TAIL(&m_startq, std, entry);
         if (std == BTPDQ_FIRST(&m_startq))
-            pct_create(tp, piece, start_test_cb);
+            event_add(&m_workev, (& (struct timeval) { 0, 0 }));
     } else {
-        free(sbs);
-        start_test_end(tp, 0);
+        free(fts);
+        startup_test_end(tp, 0);
     }
 }
 
 void
-cm_start(struct torrent *tp)
+cm_start(struct torrent *tp, int force_test)
 {
-    int err, resume_clean = 0;
-    struct rstat *sbs;
+    int err, run_test = force_test;
+    struct file_time_size *fts;
     struct content *cm = tp->cm;
 
-    if ((errno = bts_open(&cm->rds, tp->nfiles, tp->files, fd_cb_rd, tp)) != 0)
-        btpd_err("failed to open stream for '%s' (%s).\n",
-            torrent_name(tp), strerror(errno));
-
     cm->state = CM_STARTING;
 
-    sbs = btpd_calloc(tp->nfiles, sizeof(*sbs));
+    if ((errno =
+            bts_open(&cm->rds, tp->nfiles, tp->files, fd_cb_rd, tp)) != 0) {
+        btpd_log(BTPD_L_ERROR, "failed to open stream for '%s' (%s).\n",
+            torrent_name(tp), strerror(errno));
+        cm_on_error(tp);
+        return;
+    }
 
-    if ((err = stat_and_adjust(tp, sbs)) != 0)
-        btpd_err("failed stat_and_adjust for '%s' (%s).\n",
+    fts = btpd_calloc(tp->nfiles * 2, sizeof(*fts));
+
+    if ((err = stat_and_adjust(tp, fts)) != 0) {
+        btpd_log(BTPD_L_ERROR, "failed stat_and_adjust for '%s' (%s).\n",
             torrent_name(tp), strerror(err));
+        free(fts);
+        cm_on_error(tp);
+        return;
+    }
 
-    resume_clean = load_resume(tp, sbs) == 0;
-    if (!resume_clean) {
+    if (tlib_load_resume(tp->tl, tp->nfiles, fts + tp->nfiles,
+            ceil(tp->npieces / 8.0), cm->piece_field,
+            cm->bppbf * tp->npieces, cm->block_field) != 0)
+        run_test = 1;
+    for (int i = 0; i < tp->nfiles; i++) {
+        if ((fts[i].mtime != fts[i + tp->nfiles].mtime ||
+                fts[i].size != fts[i + tp->nfiles].size)) {
+            run_test = 1;
+            break;
+        }
+    }
+    if (run_test) {
         memset(cm->pos_field, 0xff, ceil(tp->npieces / 8.0));
         off_t off = 0;
         for (int i = 0; i < tp->nfiles; i++) {
-            if (sbs[i].size != tp->files[i].length) {
+            if (fts[i].size != tp->files[i].length) {
                 uint32_t start, end;
                 end = (off + tp->files[i].length - 1)
                     / tp->piece_length;
-                start = (off + sbs[i].size) / tp->piece_length;
+                start = (off + fts[i].size) / tp->piece_length;
                 while (start <= end) {
                     clear_bit(cm->pos_field, start);
                     clear_bit(cm->piece_field, start);
@@ -610,19 +550,8 @@ cm_start(struct torrent *tp)
             off += tp->files[i].length;
         }
     }
-    for (uint32_t piece = 0; piece < tp->npieces; piece++) {
-        if (has_bit(cm->piece_field, piece))
-            continue;
-        uint8_t *bf = cm->block_field + cm->bppbf * piece;
-        uint32_t nblocks = torrent_piece_blocks(tp, piece);
-        uint32_t block = 0;
-        while (block < nblocks && has_bit(bf, block))
-            block++;
-        if (block == nblocks)
-            set_bit(cm->pos_field, piece);
-    }
 
-    start_test(tp, sbs);
+    startup_test_begin(tp, fts);
 }
 
 void
diff --git a/btpd/content.h b/btpd/content.h
index 610b353..af968b0 100644
--- a/btpd/content.h
+++ b/btpd/content.h
@@ -6,10 +6,11 @@ void cm_init(void);
 void cm_create(struct torrent *tp, const char *mi);
 void cm_kill(struct torrent *tp);
 
-void cm_start(struct torrent *tp);
+void cm_start(struct torrent *tp, int force_test);
 void cm_stop(struct torrent * tp);
 
 int cm_active(struct torrent *tp);
+int cm_error(struct torrent *tp);
 int cm_started(struct torrent *tp);
 int cm_full(struct torrent *tp);
 
diff --git a/btpd/download.c b/btpd/download.c
index a836ac9..523886a 100644
--- a/btpd/download.c
+++ b/btpd/download.c
@@ -79,7 +79,7 @@ dl_on_choke(struct peer *p)
 void
 dl_on_ok_piece(struct net *n, uint32_t piece)
 {
-    struct peer *p, *next;
+    struct peer *p;
     struct piece *pc = dl_find_piece(n, piece);
 
     btpd_log(BTPD_L_POL, "Got piece: %u.\n", pc->index);
@@ -98,17 +98,6 @@ dl_on_ok_piece(struct net *n, uint32_t piece)
 
     assert(pc->nreqs == 0);
     piece_free(pc);
-
-    if (cm_full(n->tp)) {
-        btpd_log(BTPD_L_BTPD, "Finished downloading '%s'.\n",
-            torrent_name(n->tp));
-        tr_complete(n->tp);
-        BTPDQ_FOREACH_MUTABLE(p, &n->peers, p_entry, next) {
-            assert(p->nwant == 0);
-            if (peer_full(p))
-                peer_kill(p);
-        }
-    }
 }
 
 /*
diff --git a/btpd/tlib.c b/btpd/tlib.c
index 151972e..93b1df6 100644
--- a/btpd/tlib.c
+++ b/btpd/tlib.c
@@ -2,6 +2,7 @@
 #include <sys/stat.h>
 
 #include <dirent.h>
+#include <fcntl.h>
 #include <string.h>
 #include <unistd.h>
 
@@ -297,3 +298,77 @@ tlib_init(void)
     }
     closedir(dirp);
 }
+
+void
+tlib_read_hash(struct tlib *tl, size_t off, uint32_t piece, uint8_t *hash)
+{
+    int fd;
+    ssize_t nread;
+    char relpath[RELPATH_SIZE];
+    bin2hex(tl->hash, relpath, 20);
+
+    if ((errno = vopen(&fd, O_RDONLY, "torrents/%s/torrent", relpath)) != 0)
+        btpd_err("failed to open 'torrents/%s/torrent' (%s).\n",
+            relpath, strerror(errno));
+    lseek(fd, off + piece * 20, SEEK_SET);
+    if ((nread = read(fd, hash, 20)) != 20) {
+        if (nread == -1)
+            btpd_err("failed to read 'torrents/%s/torrent' (%s).\n", relpath,
+                strerror(errno));
+        else
+            btpd_err("corrupt file: 'torrents/%s/torrent'.\n", relpath);
+    }
+
+    close(fd);
+}
+
+int
+tlib_load_resume(struct tlib *tl, unsigned nfiles, struct file_time_size *fts,
+    size_t pfsize, uint8_t *pc_field, size_t bfsize, uint8_t *blk_field)
+{
+    int err, ver;
+    FILE *fp;
+
+    if ((err = vfopen(&fp, "r" , "torrents/%s/resume", tl->tp->relpath)) != 0)
+        return err;
+
+    if (fscanf(fp, "%d\n", &ver) != 1)
+        goto invalid;
+    if (ver != 1)
+        goto invalid;
+    for (int i = 0; i < nfiles; i++) {
+        quad_t size;
+        long time;
+        if (fscanf(fp, "%qd %ld\n", &size, &time) != 2)
+            goto invalid;
+        fts[i].size = size;
+        fts[i].mtime = time;
+    }
+    if (fread(pc_field, 1, pfsize, fp) != pfsize)
+        goto invalid;
+    if (fread(blk_field, 1, bfsize, fp) != bfsize)
+        goto invalid;
+    fclose(fp);
+    return 0;
+invalid:
+    fclose(fp);
+    bzero(pc_field, pfsize);
+    bzero(blk_field, bfsize);
+    return EINVAL;
+}
+
+void
+tlib_save_resume(struct tlib *tl, unsigned nfiles, struct file_time_size *fts,
+    size_t pfsize, uint8_t *pc_field, size_t bfsize, uint8_t *blk_field)
+{
+    int err;
+    FILE *fp;
+    if ((err = vfopen(&fp, "wb", "torrents/%s/resume", tl->tp->relpath)) != 0)
+        return;
+    fprintf(fp, "%d\n", 1);
+    for (int i = 0; i < nfiles; i++)
+        fprintf(fp, "%lld %ld\n", (long long)fts[i].size, (long)fts[i].mtime);
+    fwrite(pc_field, 1, pfsize, fp);
+    fwrite(blk_field, 1, bfsize, fp);
+    if (fclose(fp) != 0); //XXX
+}
diff --git a/btpd/tlib.h b/btpd/tlib.h
index 4578bad..2373c80 100644
--- a/btpd/tlib.h
+++ b/btpd/tlib.h
@@ -16,6 +16,11 @@ struct tlib {
     HTBL_ENTRY(hchain);
 };
 
+struct file_time_size {
+    off_t size;
+    time_t mtime;
+};
+
 void tlib_init(void);
 void tlib_put_all(struct tlib **v);
 
@@ -29,4 +34,15 @@ struct tlib *tlib_by_hash(const uint8_t *hash);
 struct tlib *tlib_by_num(unsigned num);
 unsigned tlib_count(void);
 
+void tlib_read_hash(struct tlib *tl, size_t off, uint32_t piece,
+    uint8_t *hash);
+
+int tlib_load_resume(struct tlib *tl, unsigned nfiles,
+    struct file_time_size *fts, size_t pfsize, uint8_t *pc_field,
+    size_t bfsize, uint8_t *blk_field);
+
+void tlib_save_resume(struct tlib *tl, unsigned nfiles,
+    struct file_time_size *fts, size_t pfsize, uint8_t *pc_field,
+    size_t bfsize, uint8_t *blk_field);
+
 #endif
diff --git a/btpd/torrent.c b/btpd/torrent.c
index f8bb520..f9c9e86 100644
--- a/btpd/torrent.c
+++ b/btpd/torrent.c
@@ -140,12 +140,12 @@ torrent_start(struct tlib *tl)
 
     btpd_log(BTPD_L_BTPD, "Starting torrent '%s'.\n", torrent_name(tp));
     if (tr_create(tp, mi) == 0) {
+        tl->tp = tp;
         net_create(tp);
         cm_create(tp, mi);
         BTPDQ_INSERT_TAIL(&m_torrents, tp, entry);
         m_ntorrents++;
-        cm_start(tp);
-        tl->tp = tp;
+        cm_start(tp, 0);
         free(mi);
         return IPC_OK;
     } else {
@@ -180,7 +180,8 @@ void
 torrent_stop(struct torrent *tp)
 {
     switch (tp->state) {
-    case T_ACTIVE:
+    case T_LEECH:
+    case T_SEED:
     case T_STARTING:
         tp->state = T_STOPPING;
         if (net_active(tp))
@@ -200,14 +201,33 @@ torrent_stop(struct torrent *tp)
 void
 torrent_on_tick(struct torrent *tp)
 {
+    if (tp->state != T_STOPPING && cm_error(tp))
+        torrent_stop(tp);
     switch (tp->state) {
     case T_STARTING:
         if (cm_started(tp)) {
-            tp->state = T_ACTIVE;
+            if (cm_full(tp))
+                tp->state = T_SEED;
+            else
+                tp->state = T_LEECH;
             net_start(tp);
             tr_start(tp);
         }
         break;
+    case T_LEECH:
+        if (cm_full(tp)) {
+            struct peer *p, *next;
+            tp->state = T_SEED;
+            btpd_log(BTPD_L_BTPD, "Finished downloading '%s'.\n",
+                torrent_name(tp));
+            tr_complete(tp);
+            BTPDQ_FOREACH_MUTABLE(p, &tp->net->peers, p_entry, next) {
+                assert(p->nwant == 0);
+                if (peer_full(p))
+                    peer_kill(p);
+            }
+        }
+        break;
     case T_STOPPING:
         if (!(cm_active(tp) || tr_active(tp)))
             torrent_kill(tp);
diff --git a/btpd/torrent.h b/btpd/torrent.h
index f35cdff..5f3116a 100644
--- a/btpd/torrent.h
+++ b/btpd/torrent.h
@@ -6,7 +6,8 @@
 
 enum torrent_state {
     T_STARTING,
-    T_ACTIVE,
+    T_LEECH,
+    T_SEED,
     T_STOPPING
 };