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; } @@ -259,6 +181,12 @@ cm_active(struct torrent *tp) return cm->state != CM_INACTIVE; } +int +cm_error(struct torrent *tp) +{ + return tp->cm->error; +} + int cm_started(struct torrent *tp) { @@ -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 #include +#include #include #include @@ -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 };