commit dd0d462afae75ff243f8cd1528963f9ad489706d Author: Richard Nyberg Date: Fri Jun 24 09:51:38 2005 +0000 Import btpd-0.1. git-svn-id: file:///home/rnyberg/svngit/btpd/releases/0.1@1 76a1f634-46fa-0310-9943-bd1476092a85 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 . 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 +* openssl - Get at +* libevent - Get at + +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 + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +#include + +#include +#include +#include +#include +#include +#include + +#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 +#include +#include + +#include +#include +#include +#include +#include +#include + +#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 +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +#include +#include + +#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 +#include + +#include +#include +#include +#include +#include +#include + +#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 +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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 +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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_n]\n" + "\tAdd the given torrents to btpd.\n" + "\n" + "del ... [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 + +#include +#include +#include +#include +#include +#include + +#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 +#include +#include +#include +#include +#include +#include +#include + +#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 +#include +#include + +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 +#include +#include +#include +#include +#include + +#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 +#include +#include +#include +#include +#include + +#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 +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#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 +#include +#include +#include +#include +#include + +#include + +#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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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