git-svn-id: file:///home/rnyberg/svngit/btpd/releases/0.1@1 76a1f634-46fa-0310-9943-bd1476092a85master
@@ -0,0 +1,54 @@ | |||
The btpd software is distributed under the following terms: | |||
Copyright (c) 2005 Richard Nyberg <rnyberg@gmail.com>. All rights reserved. | |||
Redistribution and use in source and binary forms, with or without | |||
modification, are permitted provided that the following conditions | |||
are met: | |||
1. Redistributions of source code must retain the above copyright | |||
notice, this list of conditions and the following disclaimer. | |||
2. Redistributions in binary form must reproduce the above copyright | |||
notice, this list of conditions and the following disclaimer in the | |||
documentation and/or other materials provided with the distribution. | |||
THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND | |||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||
ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE | |||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |||
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |||
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |||
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |||
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |||
SUCH DAMAGE. | |||
Additionally, the file btpd/queue.h is distributed under the following | |||
terms: | |||
Copyright (c) 1991, 1993 | |||
The Regents of the University of California. All rights reserved. | |||
Redistribution and use in source and binary forms, with or without | |||
modification, are permitted provided that the following conditions | |||
are met: | |||
1. Redistributions of source code must retain the above copyright | |||
notice, this list of conditions and the following disclaimer. | |||
2. Redistributions in binary form must reproduce the above copyright | |||
notice, this list of conditions and the following disclaimer in the | |||
documentation and/or other materials provided with the distribution. | |||
4. Neither the name of the University nor the names of its contributors | |||
may be used to endorse or promote products derived from this software | |||
without specific prior written permission. | |||
THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND | |||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||
ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE | |||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |||
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |||
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |||
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |||
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |||
SUCH DAMAGE. |
@@ -0,0 +1,2 @@ | |||
SUBDIRS=misc btpd cli | |||
EXTRA_DIST=COPYRIGHT |
@@ -0,0 +1,59 @@ | |||
+ PROGRAMS | |||
btpd consists of the following programs: | |||
* btpd - The BitTorrent client. | |||
* btcli - Command line interface to btpd. | |||
* btinfo - Shows information from a torrent file. | |||
All programs takes the "--help" option. | |||
+ DIRECTORY STRUCTURE | |||
foo.torrent | |||
The torrent metainfo file. | |||
foo.torrent.d | |||
Content will be downloaded to, and uploaded from, this dir. | |||
It and its subdirectories and files will be created by | |||
btpd as it downloads them. | |||
foo.torrent.i | |||
Created by 'btcli add'. Contains info on downloaded pieces. | |||
+ SAMPLE USAGE | |||
NOTE: Don't start one instance of btpd per torrent. You should only | |||
need one instance regardless of how many torrents you want to share. | |||
Start btpd: | |||
# btpd | |||
Start downloading or seeding bar.torrent: | |||
# btcli add /path/to/bar.torrent | |||
List active torrents (only bar.torrent atm): | |||
# btcli list | |||
Show some stats: | |||
# btcli stat | |||
Stop downloading/seeding bar.torrent: | |||
# btcli del /path/to/bar.torrent | |||
Shut down btpd (Why would you do such a thing?): | |||
# btcli die | |||
+ BUILDING | |||
Make sure you have recent versions of the following software: | |||
* curl - Get at <URL:http://curl.haxx.se/> | |||
* openssl - Get at <URL:http://www.openssl.org/> | |||
* libevent - Get at <URL:http://www.monkey.org/~provos/libevent/> | |||
You also need a c99 compiler. A non antique GCC should do. | |||
# ./configure | |||
# make | |||
# make install | |||
See ./configure --help for options if it fails. |
@@ -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... |
@@ -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@ |
@@ -0,0 +1,386 @@ | |||
#include <sys/types.h> | |||
#include <sys/socket.h> | |||
#include <sys/un.h> | |||
#include <netinet/in.h> | |||
#include <arpa/inet.h> | |||
#include <sys/stat.h> | |||
#include <sys/time.h> | |||
#include <sys/wait.h> | |||
#include <ctype.h> | |||
#include <dirent.h> | |||
#include <err.h> | |||
#include <errno.h> | |||
#include <fcntl.h> | |||
#include <getopt.h> | |||
#include <math.h> | |||
#include <locale.h> | |||
#include <signal.h> | |||
#include <stdarg.h> | |||
#include <stdio.h> | |||
#include <stdlib.h> | |||
#include <string.h> | |||
#include <time.h> | |||
#include <unistd.h> | |||
#include "btpd.h" | |||
#include "tracker_req.h" | |||
extern void client_connection_cb(int sd, short type, void *arg); | |||
struct btpd btpd; | |||
void * | |||
btpd_malloc(size_t size) | |||
{ | |||
void *a; | |||
if ((a = malloc(size)) == NULL) | |||
btpd_err("Failed to allocate %d bytes.\n", (int)size); | |||
return a; | |||
} | |||
void * | |||
btpd_calloc(size_t nmemb, size_t size) | |||
{ | |||
void *a; | |||
if ((a = calloc(nmemb, size)) == NULL) | |||
btpd_err("Failed to allocate %d bytes.\n", (int)(nmemb * size)); | |||
return a; | |||
} | |||
const char * | |||
logtype_str(uint32_t type) | |||
{ | |||
if (type & BTPD_L_BTPD) | |||
return "btpd"; | |||
else if (type & BTPD_L_ERROR) | |||
return "error"; | |||
else if (type & BTPD_L_CONN) | |||
return "conn"; | |||
else if (type & BTPD_L_TRACKER) | |||
return "tracker"; | |||
else if (type & BTPD_L_MSG) | |||
return "msg"; | |||
else | |||
return ""; | |||
} | |||
void | |||
btpd_err(const char *fmt, ...) | |||
{ | |||
va_list ap; | |||
va_start(ap, fmt); | |||
if (BTPD_L_ERROR & btpd.logmask) { | |||
char tbuf[20]; | |||
time_t tp = time(NULL); | |||
strftime(tbuf, 20, "%b %e %T", localtime(&tp)); | |||
printf("%s %s: ", tbuf, logtype_str(BTPD_L_ERROR)); | |||
vprintf(fmt, ap); | |||
} | |||
va_end(ap); | |||
exit(1); | |||
} | |||
void | |||
btpd_log(uint32_t type, const char *fmt, ...) | |||
{ | |||
va_list ap; | |||
va_start(ap, fmt); | |||
if (type & btpd.logmask) { | |||
char tbuf[20]; | |||
time_t tp = time(NULL); | |||
strftime(tbuf, 20, "%b %e %T", localtime(&tp)); | |||
printf("%s %s: ", tbuf, logtype_str(type)); | |||
vprintf(fmt, ap); | |||
} | |||
va_end(ap); | |||
} | |||
static void | |||
btpd_init(void) | |||
{ | |||
bcopy(BTPD_VERSION, btpd.peer_id, sizeof(BTPD_VERSION) - 1); | |||
btpd.peer_id[sizeof(BTPD_VERSION) - 1] = '|'; | |||
srandom(time(NULL)); | |||
for (int i = sizeof(BTPD_VERSION); i < 20; i++) | |||
btpd.peer_id[i] = rint(random() * 255.0 / RAND_MAX); | |||
btpd.version = BTPD_VERSION; | |||
btpd.logmask = BTPD_L_BTPD | BTPD_L_ERROR; | |||
TAILQ_INIT(&btpd.kids); | |||
btpd.ntorrents = 0; | |||
TAILQ_INIT(&btpd.cm_list); | |||
TAILQ_INIT(&btpd.readq); | |||
TAILQ_INIT(&btpd.writeq); | |||
btpd.port = 6881; | |||
btpd.obwlim = 0; | |||
btpd.ibwlim = 0; | |||
btpd.obw_left = 0; | |||
btpd.ibw_left = 0; | |||
btpd.npeers = 0; | |||
int nfiles = getdtablesize(); | |||
if (nfiles <= 20) | |||
btpd_err("Too few open files allowed (%d). " | |||
"Check \"ulimit -n\"\n", nfiles); | |||
else if (nfiles < 64) | |||
btpd_log(BTPD_L_BTPD, | |||
"You have restricted the number of open files to %d. " | |||
"More could be beneficial to the download performance.\n", | |||
nfiles); | |||
btpd.maxpeers = nfiles - 20; | |||
} | |||
void | |||
btpd_shutdown(void) | |||
{ | |||
struct torrent *tp; | |||
tp = TAILQ_FIRST(&btpd.cm_list); | |||
while (tp != NULL) { | |||
struct torrent *next = TAILQ_NEXT(tp, entry); | |||
torrent_unload(tp); | |||
tp = next; | |||
} | |||
btpd_log(BTPD_L_BTPD, "Exiting.\n"); | |||
exit(0); | |||
} | |||
static void | |||
signal_cb(int signal, short type, void *arg) | |||
{ | |||
btpd_log(BTPD_L_BTPD, "Got signal %d.\n", signal); | |||
btpd_shutdown(); | |||
} | |||
static void | |||
child_cb(int signal, short type, void *arg) | |||
{ | |||
int status; | |||
pid_t pid; | |||
while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { | |||
if (WIFEXITED(status) || WIFSIGNALED(status)) { | |||
struct child *kid = TAILQ_FIRST(&btpd.kids); | |||
while (kid != NULL && kid->pid != pid) | |||
kid = TAILQ_NEXT(kid, entry); | |||
assert(kid != NULL); | |||
TAILQ_REMOVE(&btpd.kids, kid, entry); | |||
kid->child_done(kid); | |||
} | |||
} | |||
} | |||
static void | |||
heartbeat_cb(int sd, short type, void *arg) | |||
{ | |||
struct torrent *tp; | |||
struct timeval begin, end, wadj; | |||
gettimeofday(&begin, NULL); | |||
btpd.seconds++; | |||
TAILQ_FOREACH(tp, &btpd.cm_list, entry) | |||
cm_by_second(tp); | |||
net_by_second(); | |||
gettimeofday(&end, NULL); | |||
timersub(&end, &begin, &wadj); | |||
evtimer_add(&btpd.heartbeat, | |||
(& (struct timeval) { 0, 1000000 - wadj.tv_usec })); | |||
} | |||
static void | |||
usage() | |||
{ | |||
printf("Usage: btpd [options]\n" | |||
"\n" | |||
"Options:\n" | |||
"\n" | |||
"--bw-in n\n" | |||
"\tLimit incoming BitTorrent traffic to n kB/s.\n" | |||
"\tDefault is 0 which means unlimited.\n" | |||
"\n" | |||
"--bw-out n\n" | |||
"\tlimit outgoing BitTorrent traffic to n kB/s.\n" | |||
"\tDefault is 0 which means unlimited.\n" | |||
"\n" | |||
"--ipc key\n" | |||
"\tThe same key must be used by the cli to talk to this\n" | |||
"\tbtpd instance. You shouldn't need to use this option.\n" | |||
"\n" | |||
"--logfile file\n" | |||
"\tLog to the given file. By default btpd logs to ./btpd.log.\n" | |||
"\n" | |||
"-p n, --port n\n" | |||
"\tListen at port n. Default is 6881.\n" | |||
"\n" | |||
"--help\n" | |||
"\tShow this help.\n" | |||
"\n"); | |||
exit(1); | |||
} | |||
static int longval = 0; | |||
static struct option longopts[] = { | |||
{ "port", required_argument, NULL, 'p' }, | |||
{ "bw-in", required_argument, &longval, 1 }, | |||
{ "bw-out", required_argument, &longval, 2 }, | |||
{ "logfile", required_argument, &longval, 3 }, | |||
{ "ipc", required_argument, &longval, 4 }, | |||
{ "help", no_argument, &longval, 5 }, | |||
{ NULL, 0, NULL, 0 } | |||
}; | |||
int | |||
main(int argc, char **argv) | |||
{ | |||
int error, ch; | |||
char *logfile = NULL, *ipc = NULL; | |||
setlocale(LC_ALL, ""); | |||
btpd_init(); | |||
while ((ch = getopt_long(argc, argv, "p:", longopts, NULL)) != -1) { | |||
switch (ch) { | |||
case 'p': | |||
btpd.port = atoi(optarg); | |||
break; | |||
case 0: | |||
switch (longval) { | |||
case 1: | |||
btpd.ibwlim = atoi(optarg) * 1024; | |||
btpd.ibw_left = btpd.ibwlim; | |||
break; | |||
case 2: | |||
btpd.obwlim = atoi(optarg) * 1024; | |||
btpd.obw_left = btpd.obwlim; | |||
break; | |||
case 3: | |||
logfile = optarg; | |||
break; | |||
case 4: | |||
ipc = optarg; | |||
for (int i = 0; i < strlen(ipc); i++) | |||
if (!isalnum(ipc[i])) | |||
btpd_err("--ipc only takes letters and digits.\n"); | |||
break; | |||
case 5: | |||
usage(); | |||
default: | |||
usage(); | |||
} | |||
break; | |||
case '?': | |||
default: | |||
usage(); | |||
} | |||
} | |||
argc -= optind; | |||
argv += optind; | |||
if (argc != 0) | |||
usage(); | |||
//net_init(); | |||
{ | |||
int sd; | |||
int flag = 1; | |||
struct sockaddr_in addr; | |||
addr.sin_family = AF_INET; | |||
addr.sin_addr.s_addr = htonl(INADDR_ANY); | |||
addr.sin_port = htons(btpd.port); | |||
if ((sd = socket(AF_INET, SOCK_STREAM, 0)) == -1) | |||
btpd_err("socket: %s\n", strerror(errno)); | |||
setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag)); | |||
if (bind(sd, (struct sockaddr *)&addr, sizeof(addr)) == -1) | |||
btpd_err("bind: %s\n", strerror(errno)); | |||
listen(sd, 10); | |||
set_nonblocking(sd); | |||
btpd.peer4_sd = sd; | |||
} | |||
//ipc_init(); | |||
{ | |||
int sd; | |||
struct sockaddr_un addr; | |||
size_t psiz = sizeof(addr.sun_path); | |||
addr.sun_family = PF_UNIX; | |||
if (ipc != NULL) { | |||
if (snprintf(addr.sun_path, psiz, "/tmp/btpd_%u_%s", | |||
geteuid(), ipc) >= psiz) | |||
btpd_err("%s is too long.\n", ipc); | |||
} else | |||
snprintf(addr.sun_path, psiz, "/tmp/btpd_%u_default", geteuid()); | |||
if ((sd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) | |||
btpd_err("sock: %s\n", strerror(errno)); | |||
if (bind(sd, (struct sockaddr *)&addr, sizeof(addr)) != 0) { | |||
if (errno == EADDRINUSE) { | |||
if (connect(sd, (struct sockaddr *)&addr, sizeof(addr)) == 0) | |||
btpd_err("btpd already running at %s.\n", addr.sun_path); | |||
else { | |||
unlink(addr.sun_path); | |||
if (bind(sd, (struct sockaddr *)&addr, sizeof(addr)) != 0) | |||
btpd_err("bind: %s\n", strerror(errno)); | |||
} | |||
} else | |||
btpd_err("bind: %s\n", strerror(errno)); | |||
} | |||
if (chmod(addr.sun_path, 0600) == -1) | |||
btpd_err("chmod: %s (%s).\n", addr.sun_path, strerror(errno)); | |||
listen(sd, 4); | |||
set_nonblocking(sd); | |||
btpd.ipc_sd = sd; | |||
} | |||
freopen("/dev/null", "r", stdin); | |||
if (logfile == NULL) | |||
logfile = "btpd.log"; | |||
freopen(logfile, "w", stdout); | |||
freopen(logfile, "w", stderr); | |||
setlinebuf(stdout); | |||
setlinebuf(stderr); | |||
daemon(1, 1); | |||
event_init(); | |||
signal(SIGPIPE, SIG_IGN); | |||
signal_set(&btpd.sigint, SIGINT, signal_cb, NULL); | |||
signal_add(&btpd.sigint, NULL); | |||
signal_set(&btpd.sigterm, SIGTERM, signal_cb, NULL); | |||
signal_add(&btpd.sigterm, NULL); | |||
signal_set(&btpd.sigchld, SIGCHLD, child_cb, NULL); | |||
signal_add(&btpd.sigchld, NULL); | |||
evtimer_set(&btpd.heartbeat, heartbeat_cb, NULL); | |||
evtimer_add(&btpd.heartbeat, (& (struct timeval) { 1, 0 })); | |||
event_set(&btpd.cli, btpd.ipc_sd, EV_READ | EV_PERSIST, | |||
client_connection_cb, &btpd); | |||
event_add(&btpd.cli, NULL); | |||
event_set(&btpd.accept4, btpd.peer4_sd, EV_READ | EV_PERSIST, | |||
net_connection_cb, &btpd); | |||
event_add(&btpd.accept4, NULL); | |||
error = event_dispatch(); | |||
btpd_err("Returned from dispatch. Error = %d.\n", error); | |||
return error; | |||
} |
@@ -0,0 +1,91 @@ | |||
#ifndef BTPD_H | |||
#define BTPD_H | |||
#include <sys/types.h> | |||
#include <sys/time.h> | |||
#include <assert.h> | |||
#include <errno.h> | |||
#include <event.h> | |||
#include <inttypes.h> | |||
#include <stddef.h> | |||
#include <stdlib.h> | |||
#include "queue.h" | |||
#include "benc.h" | |||
#include "metainfo.h" | |||
#include "iobuf.h" | |||
#include "net.h" | |||
#include "peer.h" | |||
#include "torrent.h" | |||
#include "policy.h" | |||
#include "subr.h" | |||
#define BTPD_VERSION (PACKAGE_NAME "/" PACKAGE_VERSION) | |||
struct child { | |||
pid_t pid; | |||
void (*child_done)(struct child *child); | |||
TAILQ_ENTRY(child) entry; | |||
}; | |||
TAILQ_HEAD(child_tq, child); | |||
struct btpd { | |||
uint8_t peer_id[20]; | |||
const char *version; | |||
uint32_t logmask; | |||
struct child_tq kids; | |||
unsigned ntorrents; | |||
struct torrent_tq cm_list; | |||
struct peer_tq readq; | |||
struct peer_tq writeq; | |||
int port; | |||
int peer4_sd; | |||
int ipc_sd; | |||
unsigned long obwlim, ibwlim; | |||
unsigned long ibw_left, obw_left; | |||
unsigned npeers; | |||
unsigned maxpeers; | |||
unsigned long seconds; | |||
struct event cli; | |||
struct event accept4; | |||
struct event heartbeat; | |||
struct event sigint; | |||
struct event sigterm; | |||
struct event sigchld; | |||
}; | |||
extern struct btpd btpd; | |||
#define BTPD_L_ALL 0xffffffff | |||
#define BTPD_L_ERROR 0x00000001 | |||
#define BTPD_L_TRACKER 0x00000002 | |||
#define BTPD_L_CONN 0x00000004 | |||
#define BTPD_L_MSG 0x00000008 | |||
#define BTPD_L_BTPD 0x00000010 | |||
#define BTPD_L_POL 0x00000020 | |||
void btpd_log(uint32_t type, const char *fmt, ...); | |||
void btpd_err(const char *fmt, ...); | |||
void *btpd_malloc(size_t size); | |||
void *btpd_calloc(size_t nmemb, size_t size); | |||
void btpd_shutdown(void); | |||
#endif |
@@ -0,0 +1,225 @@ | |||
#include <sys/types.h> | |||
#include <sys/socket.h> | |||
#include <arpa/inet.h> | |||
#include <inttypes.h> | |||
#include <limits.h> | |||
#include <stdarg.h> | |||
#include <stdio.h> | |||
#include <string.h> | |||
#include <unistd.h> | |||
#include "btpd.h" | |||
#ifndef PRIu64 | |||
#define PRIu64 "llu" | |||
#endif | |||
#define buf_swrite(iob, s) buf_write(iob, s, sizeof(s) - 1) | |||
static void | |||
errdie(int error) | |||
{ | |||
if (error != 0) | |||
btpd_err("io_buf: %s.\n", strerror(error)); | |||
} | |||
static void | |||
cmd_stat(int argc, const char *args, FILE *fp) | |||
{ | |||
struct torrent *tp; | |||
struct io_buffer iob; | |||
errdie(buf_init(&iob, (1 << 14))); | |||
errdie(buf_swrite(&iob, "d")); | |||
errdie(buf_print(&iob, "6:npeersi%ue", btpd.npeers)); | |||
errdie(buf_print(&iob, "9:ntorrentsi%ue", btpd.ntorrents)); | |||
errdie(buf_print(&iob, "7:secondsi%lue", btpd.seconds)); | |||
errdie(buf_swrite(&iob, "8:torrentsl")); | |||
TAILQ_FOREACH(tp, &btpd.cm_list, entry) { | |||
uint32_t seen_npieces = 0; | |||
for (uint32_t i = 0; i < tp->meta.npieces; i++) | |||
if (tp->piece_count[i] > 0) | |||
seen_npieces++; | |||
errdie(buf_print(&iob, "d4:downi%" PRIu64 "e", tp->downloaded)); | |||
errdie(buf_swrite(&iob, "4:hash20:")); | |||
errdie(buf_write(&iob, tp->meta.info_hash, 20)); | |||
errdie(buf_print(&iob, "12:have npiecesi%ue", tp->have_npieces)); | |||
errdie(buf_print(&iob, "6:npeersi%ue", tp->npeers)); | |||
errdie(buf_print(&iob, "7:npiecesi%ue", tp->meta.npieces)); | |||
errdie(buf_print(&iob, "4:path%d:%s", | |||
(int)strlen(tp->relpath), tp->relpath)); | |||
errdie(buf_print(&iob, "12:seen npiecesi%ue", seen_npieces)); | |||
errdie(buf_print(&iob, "2:upi%" PRIu64 "ee", tp->uploaded)); | |||
} | |||
errdie(buf_swrite(&iob, "ee")); | |||
uint32_t len = iob.buf_off; | |||
fwrite(&len, sizeof(len), 1, fp); | |||
fwrite(iob.buf, 1, iob.buf_off, fp); | |||
free(iob.buf); | |||
} | |||
static void | |||
cmd_add(int argc, const char *args, FILE *fp) | |||
{ | |||
struct io_buffer iob; | |||
errdie(buf_init(&iob, (1 << 10))); | |||
errdie(buf_write(&iob, "l", 1)); | |||
while (args != NULL) { | |||
size_t plen; | |||
char path[PATH_MAX]; | |||
const char *pathp; | |||
if (!benc_isstr(args)) { | |||
free(iob.buf); | |||
return; | |||
} | |||
benc_str(args, &pathp, &plen, &args); | |||
if (plen >= PATH_MAX) { | |||
errdie(buf_print(&iob, "d4:codei%dee", ENAMETOOLONG)); | |||
continue; | |||
} | |||
bcopy(pathp, path, plen); | |||
path[plen] = '\0'; | |||
btpd_log(BTPD_L_BTPD, "add request for %s.\n", path); | |||
errdie(buf_print(&iob, "d4:codei%dee", torrent_load(path))); | |||
} | |||
errdie(buf_write(&iob, "e", 1)); | |||
uint32_t len = iob.buf_off; | |||
fwrite(&len, sizeof(len), 1, fp); | |||
fwrite(iob.buf, 1, iob.buf_off, fp); | |||
free(iob.buf); | |||
} | |||
static void | |||
cmd_del(int argc, const char *args, FILE *fp) | |||
{ | |||
struct io_buffer iob; | |||
errdie(buf_init(&iob, (1 << 10))); | |||
errdie(buf_swrite(&iob, "l")); | |||
while (args != NULL) { | |||
size_t len; | |||
const char *hash; | |||
struct torrent *tp; | |||
if (!benc_isstr(args) || | |||
benc_str(args, &hash, &len, &args) != 0 || len != 20) { | |||
free(iob.buf); | |||
return; | |||
} | |||
tp = torrent_get_by_hash(hash); | |||
if (tp != NULL) { | |||
btpd_log(BTPD_L_BTPD, "del request for %s.\n", tp->relpath); | |||
torrent_unload(tp); | |||
errdie(buf_swrite(&iob, "d4:codei0ee")); | |||
} else { | |||
btpd_log(BTPD_L_BTPD, "del request didn't match.\n"); | |||
errdie(buf_print(&iob, "d4:codei%dee", ENOENT)); | |||
} | |||
} | |||
errdie(buf_swrite(&iob, "e")); | |||
uint32_t len = iob.buf_off; | |||
fwrite(&len, sizeof(len), 1, fp); | |||
fwrite(iob.buf, 1, iob.buf_off, fp); | |||
free(iob.buf); | |||
} | |||
static void | |||
cmd_die(int argc, const char *args, FILE *fp) | |||
{ | |||
char res[] = "d4:codei0ee"; | |||
uint32_t len = sizeof(res) - 1; | |||
fwrite(&len, sizeof(len), 1, fp); | |||
fwrite(res, 1, len, fp); | |||
fflush(fp); | |||
btpd_log(BTPD_L_BTPD, "Someone wants me dead.\n"); | |||
btpd_shutdown(); | |||
} | |||
static struct { | |||
const char *name; | |||
int nlen; | |||
void (*fun)(int, const char *, FILE *); | |||
} cmd_table[] = { | |||
{ "add", 3, cmd_add }, | |||
{ "del", 3, cmd_del }, | |||
{ "die", 3, cmd_die }, | |||
{ "stat", 4, cmd_stat } | |||
}; | |||
static int ncmds = sizeof(cmd_table) / sizeof(cmd_table[0]); | |||
static void | |||
cmd_dispatch(const char *buf, FILE *fp) | |||
{ | |||
size_t cmdlen; | |||
const char *cmd; | |||
const char *args; | |||
int found = 0; | |||
benc_str(benc_first(buf), &cmd, &cmdlen, &args); | |||
for (int i = 0; !found && i < ncmds; i++) { | |||
if (cmdlen == cmd_table[i].nlen && | |||
strncmp(cmd_table[i].name, cmd, cmdlen) == 0) { | |||
cmd_table[i].fun(benc_nelems(buf) - 1, args, fp); | |||
found = 1; | |||
} | |||
} | |||
} | |||
static void | |||
do_ipc(FILE *fp) | |||
{ | |||
uint32_t cmdlen, nread; | |||
char *buf; | |||
if (fread(&cmdlen, sizeof(cmdlen), 1, fp) != 1) | |||
return; | |||
buf = btpd_malloc(cmdlen); | |||
if ((nread = fread(buf, 1, cmdlen, fp)) == cmdlen) { | |||
if (benc_validate(buf, cmdlen) == 0 && benc_islst(buf) && | |||
benc_first(buf) != NULL && benc_isstr(benc_first(buf))) | |||
cmd_dispatch(buf, fp); | |||
} | |||
free(buf); | |||
} | |||
void | |||
client_connection_cb(int sd, short type, void *arg) | |||
{ | |||
int nsd; | |||
FILE *fp; | |||
if ((nsd = accept(sd, NULL, NULL)) < 0) { | |||
if (errno == EWOULDBLOCK || errno == ECONNABORTED) | |||
return; | |||
else | |||
btpd_err("client accept: %s\n", strerror(errno)); | |||
} | |||
if ((errno = set_blocking(nsd)) != 0) | |||
btpd_err("set_blocking: %s.\n", strerror(errno)); | |||
if ((fp = fdopen(nsd, "r+")) == NULL) { | |||
close(nsd); | |||
return; | |||
} | |||
do_ipc(fp); | |||
fclose(fp); | |||
} |
@@ -0,0 +1,960 @@ | |||
#include <sys/types.h> | |||
#include <sys/uio.h> | |||
#include <sys/socket.h> | |||
#include <netinet/in.h> | |||
#include <netdb.h> | |||
#include <sys/mman.h> | |||
#include <sys/wait.h> | |||
#include <assert.h> | |||
#include <errno.h> | |||
#include <fcntl.h> | |||
#include <math.h> | |||
#include <stdio.h> | |||
#include <stdlib.h> | |||
#include <string.h> | |||
#include <unistd.h> | |||
#include "btpd.h" | |||
#define min(x, y) ((x) <= (y) ? (x) : (y)) | |||
static unsigned long | |||
net_write(struct peer *p, unsigned long wmax); | |||
void | |||
net_read_cb(int sd, short type, void *arg) | |||
{ | |||
struct peer *p = (struct peer *)arg; | |||
if (btpd.ibwlim == 0) { | |||
p->reader->read(p, 0); | |||
} else if (btpd.ibw_left > 0) { | |||
btpd.ibw_left -= p->reader->read(p, btpd.ibw_left); | |||
} else { | |||
p->flags |= PF_ON_READQ; | |||
TAILQ_INSERT_TAIL(&btpd.readq, p, rq_entry); | |||
} | |||
} | |||
void | |||
net_write_cb(int sd, short type, void *arg) | |||
{ | |||
struct peer *p = (struct peer *)arg; | |||
if (btpd.obwlim == 0) { | |||
net_write(p, 0); | |||
} else if (btpd.obw_left > 0) { | |||
btpd.obw_left -= net_write(p, btpd.obw_left); | |||
} else { | |||
p->flags |= PF_ON_WRITEQ; | |||
TAILQ_INSERT_TAIL(&btpd.writeq, p, wq_entry); | |||
} | |||
} | |||
static void | |||
nokill_iob(struct io_buffer *iob) | |||
{ | |||
//Nothing | |||
} | |||
static void | |||
kill_free_buf(struct io_buffer *iob) | |||
{ | |||
free(iob->buf); | |||
} | |||
static struct iob_link * | |||
malloc_liob(size_t len) | |||
{ | |||
struct iob_link *iol; | |||
iol = (struct iob_link *)btpd_malloc(sizeof(*iol) + len); | |||
iol->iob.buf = (char *)iol + sizeof(*iol); | |||
iol->iob.buf_len = len; | |||
iol->iob.buf_off = 0; | |||
iol->kill_buf = nokill_iob; | |||
return iol; | |||
} | |||
static struct iob_link * | |||
salloc_liob(char *buf, size_t len, void (*kill_buf)(struct io_buffer *)) | |||
{ | |||
struct iob_link *iol; | |||
iol = (struct iob_link *)btpd_malloc(sizeof(*iol)); | |||
iol->iob.buf = buf; | |||
iol->iob.buf_len = len; | |||
iol->iob.buf_off = 0; | |||
iol->kill_buf = kill_buf; | |||
return iol; | |||
} | |||
void | |||
net_unsend_piece(struct peer *p, struct piece_req *req) | |||
{ | |||
struct iob_link *piece; | |||
TAILQ_REMOVE(&p->p_reqs, req, entry); | |||
piece = TAILQ_NEXT(req->head, entry); | |||
TAILQ_REMOVE(&p->outq, piece, entry); | |||
piece->kill_buf(&piece->iob); | |||
free(piece); | |||
TAILQ_REMOVE(&p->outq, req->head, entry); | |||
req->head->kill_buf(&req->head->iob); | |||
free(req->head); | |||
free(req); | |||
if (TAILQ_EMPTY(&p->outq)) { | |||
if (p->flags & PF_ON_WRITEQ) { | |||
TAILQ_REMOVE(&btpd.writeq, p, wq_entry); | |||
p->flags &= ~PF_ON_WRITEQ; | |||
} else | |||
event_del(&p->out_ev); | |||
} | |||
} | |||
void | |||
kill_shake(struct input_reader *reader) | |||
{ | |||
free(reader); | |||
} | |||
#define NIOV 16 | |||
static unsigned long | |||
net_write(struct peer *p, unsigned long wmax) | |||
{ | |||
struct iob_link *iol; | |||
struct piece_req *req; | |||
struct iovec iov[NIOV]; | |||
int niov; | |||
int limited; | |||
ssize_t nwritten; | |||
unsigned long bcount; | |||
limited = wmax > 0; | |||
niov = 0; | |||
assert((iol = TAILQ_FIRST(&p->outq)) != NULL); | |||
while (niov < NIOV && iol != NULL | |||
&& (!limited || (limited && wmax > 0))) { | |||
iov[niov].iov_base = iol->iob.buf + iol->iob.buf_off; | |||
iov[niov].iov_len = iol->iob.buf_len - iol->iob.buf_off; | |||
if (limited) { | |||
if (iov[niov].iov_len > wmax) | |||
iov[niov].iov_len = wmax; | |||
wmax -= iov[niov].iov_len; | |||
} | |||
niov++; | |||
iol = TAILQ_NEXT(iol, entry); | |||
} | |||
again: | |||
nwritten = writev(p->sd, iov, niov); | |||
if (nwritten < 0) { | |||
if (errno == EINTR) | |||
goto again; | |||
else if (errno == EAGAIN) { | |||
event_add(&p->out_ev, NULL); | |||
return 0; | |||
} else { | |||
btpd_log(BTPD_L_CONN, "write error: %s\n", strerror(errno)); | |||
peer_kill(p); | |||
return 0; | |||
} | |||
} | |||
bcount = nwritten; | |||
p->rate_from_me[btpd.seconds % RATEHISTORY] += nwritten; | |||
req = TAILQ_FIRST(&p->p_reqs); | |||
iol = TAILQ_FIRST(&p->outq); | |||
while (bcount > 0) { | |||
if (req != NULL && req->head == iol) { | |||
struct iob_link *piece = TAILQ_NEXT(req->head, entry); | |||
struct piece_req *next = TAILQ_NEXT(req, entry); | |||
TAILQ_REMOVE(&p->p_reqs, req, entry); | |||
free(req); | |||
req = next; | |||
p->tp->uploaded += piece->iob.buf_len; | |||
} | |||
if (bcount >= iol->iob.buf_len - iol->iob.buf_off) { | |||
bcount -= iol->iob.buf_len - iol->iob.buf_off; | |||
TAILQ_REMOVE(&p->outq, iol, entry); | |||
iol->kill_buf(&iol->iob); | |||
free(iol); | |||
iol = TAILQ_FIRST(&p->outq); | |||
} else { | |||
iol->iob.buf_off += bcount; | |||
bcount = 0; | |||
} | |||
} | |||
if (!TAILQ_EMPTY(&p->outq)) | |||
event_add(&p->out_ev, NULL); | |||
else if (p->flags & PF_WRITE_CLOSE) { | |||
btpd_log(BTPD_L_CONN, "Closed because of write flag.\n"); | |||
peer_kill(p); | |||
} | |||
return nwritten; | |||
} | |||
void | |||
net_send(struct peer *p, struct iob_link *iol) | |||
{ | |||
if (TAILQ_EMPTY(&p->outq)) | |||
event_add(&p->out_ev, NULL); | |||
TAILQ_INSERT_TAIL(&p->outq, iol, entry); | |||
} | |||
void | |||
net_write32(void *buf, uint32_t num) | |||
{ | |||
*(uint32_t *)buf = htonl(num); | |||
} | |||
uint32_t | |||
net_read32(void *buf) | |||
{ | |||
return ntohl(*(uint32_t *)buf); | |||
} | |||
void | |||
net_send_piece(struct peer *p, uint32_t index, uint32_t begin, | |||
char *block, size_t blen) | |||
{ | |||
struct iob_link *head, *piece; | |||
struct piece_req *req; | |||
btpd_log(BTPD_L_MSG, "send piece: %u, %u, %u\n", index, begin, blen); | |||
head = malloc_liob(13); | |||
net_write32(head->iob.buf, 9 + blen); | |||
head->iob.buf[4] = MSG_PIECE; | |||
net_write32(head->iob.buf + 5, index); | |||
net_write32(head->iob.buf + 9, begin); | |||
net_send(p, head); | |||
piece = salloc_liob(block, blen, kill_free_buf); | |||
net_send(p, piece); | |||
req = btpd_malloc(sizeof(*req)); | |||
req->index = index; | |||
req->begin = begin; | |||
req->length = blen; | |||
req->head = head; | |||
TAILQ_INSERT_TAIL(&p->p_reqs, req, entry); | |||
} | |||
void | |||
net_send_request(struct peer *p, struct piece_req *req) | |||
{ | |||
struct iob_link *out; | |||
out = malloc_liob(17); | |||
net_write32(out->iob.buf, 13); | |||
out->iob.buf[4] = MSG_REQUEST; | |||
net_write32(out->iob.buf + 5, req->index); | |||
net_write32(out->iob.buf + 9, req->begin); | |||
net_write32(out->iob.buf + 13, req->length); | |||
net_send(p, out); | |||
} | |||
void | |||
net_send_cancel(struct peer *p, struct piece_req *req) | |||
{ | |||
struct iob_link *out; | |||
out = malloc_liob(17); | |||
net_write32(out->iob.buf, 13); | |||
out->iob.buf[4] = MSG_CANCEL; | |||
net_write32(out->iob.buf + 5, req->index); | |||
net_write32(out->iob.buf + 9, req->begin); | |||
net_write32(out->iob.buf + 13, req->length); | |||
net_send(p, out); | |||
} | |||
void | |||
net_send_have(struct peer *p, uint32_t index) | |||
{ | |||
struct iob_link *out; | |||
out = malloc_liob(9); | |||
net_write32(out->iob.buf, 5); | |||
out->iob.buf[4] = MSG_HAVE; | |||
net_write32(out->iob.buf + 5, index); | |||
net_send(p, out); | |||
} | |||
void | |||
net_send_onesized(struct peer *p, char type) | |||
{ | |||
struct iob_link *out; | |||
out = malloc_liob(5); | |||
net_write32(out->iob.buf, 1); | |||
out->iob.buf[4] = type; | |||
net_send(p, out); | |||
} | |||
void | |||
net_send_unchoke(struct peer *p) | |||
{ | |||
net_send_onesized(p, MSG_UNCHOKE); | |||
} | |||
void | |||
net_send_choke(struct peer *p) | |||
{ | |||
net_send_onesized(p, MSG_CHOKE); | |||
} | |||
void | |||
net_send_uninterest(struct peer *p) | |||
{ | |||
net_send_onesized(p, MSG_UNINTEREST); | |||
} | |||
void | |||
net_send_interest(struct peer *p) | |||
{ | |||
net_send_onesized(p, MSG_INTEREST); | |||
} | |||
void | |||
net_send_bitfield(struct peer *p) | |||
{ | |||
struct iob_link *out; | |||
uint32_t plen; | |||
plen = (uint32_t)ceil(p->tp->meta.npieces / 8.0); | |||
out = malloc_liob(5); | |||
net_write32(out->iob.buf, plen + 1); | |||
out->iob.buf[4] = MSG_BITFIELD; | |||
net_send(p, out); | |||
out = salloc_liob(p->tp->piece_field, plen, nokill_iob); | |||
net_send(p, out); | |||
} | |||
void | |||
net_send_shake(struct peer *p) | |||
{ | |||
struct iob_link *out; | |||
out = malloc_liob(68); | |||
bcopy("\x13""BitTorrent protocol\0\0\0\0\0\0\0\0", out->iob.buf, 28); | |||
bcopy(p->tp->meta.info_hash, out->iob.buf + 28, 20); | |||
bcopy(btpd.peer_id, out->iob.buf + 48, 20); | |||
net_send(p, out); | |||
if (p->tp->have_npieces > 0) | |||
net_send_bitfield(p); | |||
} | |||
static void | |||
kill_generic(struct input_reader *reader) | |||
{ | |||
free(reader); | |||
} | |||
static size_t | |||
net_read(struct peer *p, char *buf, size_t len) | |||
{ | |||
ssize_t nread = read(p->sd, buf, len); | |||
if (nread < 0) { | |||
if (errno == EINTR || errno == EAGAIN) { | |||
event_add(&p->in_ev, NULL); | |||
return 0; | |||
} else { | |||
btpd_log(BTPD_L_CONN, "read error: %s\n", strerror(errno)); | |||
peer_kill(p); | |||
return 0; | |||
} | |||
} else if (nread == 0) { | |||
btpd_log(BTPD_L_CONN, "conn closed by other side.\n"); | |||
if (!TAILQ_EMPTY(&p->outq)) | |||
p->flags |= PF_WRITE_CLOSE; | |||
else | |||
peer_kill(p); | |||
return 0; | |||
} else | |||
return nread; | |||
} | |||
void | |||
kill_bitfield(struct input_reader *rd) | |||
{ | |||
free(rd); | |||
} | |||
static void net_generic_reader(struct peer *p); | |||
static unsigned long | |||
read_bitfield(struct peer *p, unsigned long rmax) | |||
{ | |||
ssize_t nread; | |||
struct bitfield_reader *rd = (struct bitfield_reader *)p->reader; | |||
if (rmax == 0) | |||
rmax = rd->iob.buf_len - rd->iob.buf_off; | |||
else | |||
rmax = min(rmax, rd->iob.buf_len - rd->iob.buf_off); | |||
if ((nread = net_read(p, rd->iob.buf + rd->iob.buf_off, rmax)) == 0) | |||
return 0; | |||
rd->iob.buf_off += nread; | |||
if (rd->iob.buf_off == rd->iob.buf_len) { | |||
bcopy(rd->iob.buf, p->piece_field, rd->iob.buf_len); | |||
for (unsigned i = 0; i < p->tp->meta.npieces; i++) | |||
if (has_bit(p->piece_field, i)) { | |||
p->npieces++; | |||
cm_on_piece_ann(p, i); | |||
} | |||
free(rd); | |||
net_generic_reader(p); | |||
} else | |||
event_add(&p->in_ev, NULL); | |||
return nread; | |||
} | |||
void | |||
kill_piece(struct input_reader *rd) | |||
{ | |||
free(rd); | |||
} | |||
static unsigned long | |||
read_piece(struct peer *p, unsigned long rmax) | |||
{ | |||
ssize_t nread; | |||
struct piece_reader *rd = (struct piece_reader *)p->reader; | |||
if (rmax == 0) | |||
rmax = rd->iob.buf_len - rd->iob.buf_off; | |||
else | |||
rmax = min(rmax, rd->iob.buf_len - rd->iob.buf_off); | |||
if ((nread = net_read(p, rd->iob.buf + rd->iob.buf_off, rmax)) == 0) | |||
return 0; | |||
rd->iob.buf_off += nread; | |||
p->rate_to_me[btpd.seconds % RATEHISTORY] += nread; | |||
p->tp->downloaded += nread; | |||
if (rd->iob.buf_off == rd->iob.buf_len) { | |||
struct piece_req *req = TAILQ_FIRST(&p->my_reqs); | |||
if (req != NULL && | |||
req->index == rd->index && | |||
req->begin == rd->begin && | |||
req->length == rd->iob.buf_len) { | |||
// | |||
off_t cbegin = rd->index * p->tp->meta.piece_length + rd->begin; | |||
torrent_put_bytes(p->tp, rd->iob.buf, cbegin, rd->iob.buf_len); | |||
cm_on_block(p); | |||
} | |||
free(rd); | |||
net_generic_reader(p); | |||
} else | |||
event_add(&p->in_ev, NULL); | |||
return nread; | |||
} | |||
#define GRBUFLEN (1 << 15) | |||
static unsigned long | |||
net_generic_read(struct peer *p, unsigned long rmax) | |||
{ | |||
char buf[GRBUFLEN]; | |||
struct generic_reader *gr = (struct generic_reader *)p->reader; | |||
ssize_t nread; | |||
size_t off, len; | |||
int got_part; | |||
len = 0; | |||
if (gr->iob.buf_off > 0) { | |||
len = gr->iob.buf_off; | |||
gr->iob.buf_off = 0; | |||
bcopy(gr->iob.buf, buf, len); | |||
} | |||
if (rmax == 0) | |||
rmax = GRBUFLEN - len; | |||
else | |||
rmax = min(rmax, GRBUFLEN - len); | |||
if ((nread = net_read(p, buf + len, rmax)) == 0) | |||
return 0; | |||
len += nread; | |||
off = 0; | |||
got_part = 0; | |||
while (!got_part && len - off >= 4) { | |||
size_t msg_len = net_read32(buf + off); | |||
if (msg_len == 0) { /* Keep alive */ | |||
off += 4; | |||
continue; | |||
} | |||
if (len - off < 5) { | |||
got_part = 1; | |||
break; | |||
} | |||
switch (buf[off + 4]) { | |||
case MSG_CHOKE: | |||
btpd_log(BTPD_L_MSG, "choke.\n"); | |||
if (msg_len != 1) | |||
goto bad_data; | |||
if ((p->flags & (PF_P_CHOKE|PF_I_WANT)) == PF_I_WANT) { | |||
p->flags |= PF_P_CHOKE; | |||
cm_on_undownload(p); | |||
} else | |||
p->flags |= PF_P_CHOKE; | |||
break; | |||
case MSG_UNCHOKE: | |||
btpd_log(BTPD_L_MSG, "unchoke.\n"); | |||
if (msg_len != 1) | |||
goto bad_data; | |||
if ((p->flags & (PF_P_CHOKE|PF_I_WANT)) | |||
== (PF_P_CHOKE|PF_I_WANT)) { | |||
p->flags &= ~PF_P_CHOKE; | |||
cm_on_download(p); | |||
} else | |||
p->flags &= ~PF_P_CHOKE; | |||
break; | |||
case MSG_INTEREST: | |||
btpd_log(BTPD_L_MSG, "interested.\n"); | |||
if (msg_len != 1) | |||
goto bad_data; | |||
if ((p->flags & (PF_P_WANT|PF_I_CHOKE)) == 0) { | |||
p->flags |= PF_P_WANT; | |||
cm_on_upload(p); | |||
} else | |||
p->flags |= PF_P_WANT; | |||
break; | |||
case MSG_UNINTEREST: | |||
btpd_log(BTPD_L_MSG, "uninterested.\n"); | |||
if (msg_len != 1) | |||
goto bad_data; | |||
if ((p->flags & (PF_P_WANT|PF_I_CHOKE)) == PF_P_WANT) { | |||
p->flags &= ~PF_P_WANT; | |||
cm_on_unupload(p); | |||
} else | |||
p->flags &= ~PF_P_WANT; | |||
break; | |||
case MSG_HAVE: | |||
btpd_log(BTPD_L_MSG, "have.\n"); | |||
if (msg_len != 5) | |||
goto bad_data; | |||
else if (len - off >= msg_len + 4) { | |||
unsigned long piece = net_read32(buf + off + 5); | |||
if (!has_bit(p->piece_field, piece)) { | |||
set_bit(p->piece_field, piece); | |||
p->npieces++; | |||
cm_on_piece_ann(p, piece); | |||
} | |||
} else | |||
got_part = 1; | |||
break; | |||
case MSG_BITFIELD: | |||
btpd_log(BTPD_L_MSG, "bitfield.\n"); | |||
if (msg_len != (size_t)ceil(p->tp->meta.npieces / 8.0) + 1) | |||
goto bad_data; | |||
else if (p->npieces != 0) | |||
goto bad_data; | |||
else if (len - off >= msg_len + 4) { | |||
bcopy(buf + off + 5, p->piece_field, msg_len - 1); | |||
for (unsigned i = 0; i < p->tp->meta.npieces; i++) | |||
if (has_bit(p->piece_field, i)) { | |||
p->npieces++; | |||
cm_on_piece_ann(p, i); | |||
} | |||
} else { | |||
struct bitfield_reader *rp; | |||
size_t mem = sizeof(*rp) + msg_len - 1; | |||
p->reader->kill(p->reader); | |||
rp = btpd_calloc(1, mem); | |||
rp->rd.kill = kill_bitfield; | |||
rp->rd.read = read_bitfield; | |||
rp->iob.buf = (char *)rp + sizeof(*rp); | |||
rp->iob.buf_len = msg_len - 1; | |||
rp->iob.buf_off = len - off - 5; | |||
bcopy(buf + off + 5, rp->iob.buf, rp->iob.buf_off); | |||
p->reader = (struct input_reader *)rp; | |||
event_add(&p->in_ev, NULL); | |||
return nread; | |||
} | |||
break; | |||
case MSG_REQUEST: | |||
btpd_log(BTPD_L_MSG, "request.\n"); | |||
if (msg_len != 13) | |||
goto bad_data; | |||
else if (len - off >= msg_len + 4) { | |||
if ((p->flags & (PF_P_WANT|PF_I_CHOKE)) != PF_P_WANT) | |||
break; | |||
uint32_t index, begin, length; | |||
off_t cbegin; | |||
char *content; | |||
index = net_read32(buf + off + 5); | |||
begin = net_read32(buf + off + 9); | |||
length = net_read32(buf + off + 13); | |||
if (length > (1 << 15)) | |||
goto bad_data; | |||
if (index >= p->tp->meta.npieces | |||
|| !has_bit(p->tp->piece_field, index)) | |||
goto bad_data; | |||
if (begin + length > p->tp->meta.piece_length) | |||
goto bad_data; | |||
cbegin = index * p->tp->meta.piece_length + begin; | |||
if (cbegin + length > p->tp->meta.total_length) | |||
goto bad_data; | |||
content = torrent_get_bytes(p->tp, cbegin, length); | |||
net_send_piece(p, index, begin, content, length); | |||
} else | |||
got_part = 1; | |||
break; | |||
case MSG_PIECE: | |||
btpd_log(BTPD_L_MSG, "piece.\n"); | |||
if (msg_len < 10) | |||
goto bad_data; | |||
else if (len - off >= 13) { | |||
uint32_t index = net_read32(buf + off + 5); | |||
uint32_t begin = net_read32(buf + off + 9); | |||
uint32_t length = msg_len - 9; | |||
#if 0 | |||
struct piece_req *req = TAILQ_FIRST(&p->my_reqs); | |||
if (req == NULL) | |||
goto bad_data; | |||
if (!(index == req->index && | |||
begin == req->begin && | |||
length == req->length)) | |||
goto bad_data; | |||
#endif | |||
if (len - off >= msg_len + 4) { | |||
off_t cbegin = index * p->tp->meta.piece_length + begin; | |||
p->tp->downloaded += length; | |||
p->rate_to_me[btpd.seconds % RATEHISTORY] += length; | |||
struct piece_req *req = TAILQ_FIRST(&p->my_reqs); | |||
if (req != NULL && | |||
req->index == index && | |||
req->begin == begin && | |||
req->length == length) { | |||
// | |||
torrent_put_bytes(p->tp, buf + off + 13, cbegin, length); | |||
cm_on_block(p); | |||
} | |||
} else { | |||
struct piece_reader *rp; | |||
size_t mem = sizeof(*rp) + length; | |||
p->reader->kill(p->reader); | |||
rp = btpd_calloc(1, mem); | |||
rp->rd.kill = kill_piece; | |||
rp->rd.read = read_piece; | |||
rp->index = index; | |||
rp->begin = begin; | |||
rp->iob.buf = (char *)rp + sizeof(*rp); | |||
rp->iob.buf_len = length; | |||
rp->iob.buf_off = len - off - 13; | |||
bcopy(buf + off + 13, rp->iob.buf, rp->iob.buf_off); | |||
p->reader = (struct input_reader *)rp; | |||
event_add(&p->in_ev, NULL); | |||
p->tp->downloaded += rp->iob.buf_off; | |||
p->rate_to_me[btpd.seconds % RATEHISTORY] += | |||
rp->iob.buf_off; | |||
return nread; | |||
} | |||
} else | |||
got_part = 1; | |||
break; | |||
case MSG_CANCEL: | |||
if (msg_len != 13) | |||
goto bad_data; | |||
else if (len - off >= msg_len + 4) { | |||
struct piece_req *req; | |||
uint32_t index, begin, length; | |||
index = net_read32(buf + off + 5); | |||
begin = net_read32(buf + off + 9); | |||
length = net_read32(buf + off + 13); | |||
btpd_log(BTPD_L_MSG, "cancel: %u, %u, %u\n", | |||
index, begin, length); | |||
req = TAILQ_FIRST(&p->p_reqs); | |||
while (req != NULL) { | |||
if (req->index == index && | |||
req->begin == begin && | |||
req->length == length) { | |||
btpd_log(BTPD_L_MSG, "cancel matched.\n"); | |||
net_unsend_piece(p, req); | |||
break; | |||
} | |||
req = TAILQ_NEXT(req, entry); | |||
} | |||
} else | |||
got_part = 1; | |||
break; | |||
default: | |||
goto bad_data; | |||
} | |||
if (!got_part) | |||
off += 4 + msg_len; | |||
} | |||
if (off != len) { | |||
gr->iob.buf_off = len - off; | |||
bcopy(buf + off, gr->iob.buf, gr->iob.buf_off); | |||
} | |||
event_add(&p->in_ev, NULL); | |||
return nread; | |||
bad_data: | |||
btpd_log(BTPD_L_MSG, "bad data\n"); | |||
peer_kill(p); | |||
return nread; | |||
} | |||
static void | |||
net_generic_reader(struct peer *p) | |||
{ | |||
struct generic_reader *gr; | |||
gr = btpd_calloc(1, sizeof(*gr)); | |||
gr->rd.read = net_generic_read; | |||
gr->rd.kill = kill_generic; | |||
gr->iob.buf = gr->_io_buf; | |||
gr->iob.buf_len = MAX_INPUT_LEFT; | |||
gr->iob.buf_off = 0; | |||
p->reader = (struct input_reader *)gr; | |||
event_add(&p->in_ev, NULL); | |||
} | |||
static unsigned long | |||
net_shake_read(struct peer *p, unsigned long rmax) | |||
{ | |||
ssize_t nread; | |||
struct handshake *hs = (struct handshake *)p->reader; | |||
struct io_buffer *in = &hs->in; | |||
if (rmax == 0) | |||
rmax = in->buf_len - in->buf_off; | |||
else | |||
rmax = min(rmax, in->buf_len - in->buf_off); | |||
nread = net_read(p, in->buf + in->buf_off, rmax); | |||
if (nread == 0) | |||
return 0; | |||
in->buf_off += nread; | |||
switch (hs->state) { | |||
case SHAKE_INIT: | |||
if (in->buf_off < 20) | |||
break; | |||
else if (bcmp(in->buf, "\x13""BitTorrent protocol", 20) == 0) | |||
hs->state = SHAKE_PSTR; | |||
else | |||
goto bad_shake; | |||
case SHAKE_PSTR: | |||
if (in->buf_off < 28) | |||
break; | |||
else | |||
hs->state = SHAKE_RESERVED; | |||
#if 0 | |||
else if (bcmp(in->buf + 20, "\0\0\0\0\0\0\0\0", 8) == 0) | |||
hs->state = SHAKE_RESERVED; | |||
else | |||
goto bad_shake; | |||
#endif | |||
case SHAKE_RESERVED: | |||
if (in->buf_off < 48) | |||
break; | |||
else if (hs->incoming) { | |||
struct torrent *tp = torrent_get_by_hash(in->buf + 28); | |||
#if 0 | |||
tp = TAILQ_FIRST(&btpd.cm_list); | |||
while (tp != NULL) { | |||
if (bcmp(in->buf + 28, tp->meta.info_hash, 20) == 0) | |||
break; | |||
else | |||
tp = TAILQ_NEXT(tp, entry); | |||
} | |||
#endif | |||
if (tp != NULL) { | |||
hs->state = SHAKE_INFO; | |||
p->tp = tp; | |||
net_send_shake(p); | |||
} else | |||
goto bad_shake; | |||
} else { | |||
if (bcmp(in->buf + 28, p->tp->meta.info_hash, 20) == 0) | |||
hs->state = SHAKE_INFO; | |||
else | |||
goto bad_shake; | |||
} | |||
case SHAKE_INFO: | |||
if (in->buf_off < 68) | |||
break; | |||
else { | |||
if (!hs->incoming && bcmp(in->buf + 48, p->id, 20) != 0) | |||
goto bad_shake; | |||
else if (hs->incoming && torrent_has_peer(p->tp, in->buf + 48)) | |||
goto bad_shake; // Not really, but we are already connected | |||
if (hs->incoming) | |||
bcopy(in->buf + 48, p->id, 20); | |||
hs->state = SHAKE_ID; | |||
} | |||
default: | |||
assert(hs->state == SHAKE_ID); | |||
} | |||
if (hs->state == SHAKE_ID) { | |||
btpd_log(BTPD_L_CONN, "Got whole shake.\n"); | |||
free(hs); | |||
p->piece_field = btpd_calloc(1, (int)ceil(p->tp->meta.npieces / 8.0)); | |||
cm_on_new_peer(p); | |||
net_generic_reader(p); | |||
} else | |||
event_add(&p->in_ev, NULL); | |||
return nread; | |||
bad_shake: | |||
btpd_log(BTPD_L_CONN, "Bad shake(%d)\n", hs->state); | |||
peer_kill(p); | |||
return nread; | |||
} | |||
void | |||
net_handshake(struct peer *p, int incoming) | |||
{ | |||
struct handshake *hs; | |||
hs = calloc(1, sizeof(*hs)); | |||
hs->incoming = incoming; | |||
hs->state = SHAKE_INIT; | |||
hs->in.buf_len = SHAKE_LEN; | |||
hs->in.buf_off = 0; | |||
hs->in.buf = hs->_io_buf; | |||
p->reader = (struct input_reader *)hs; | |||
hs->rd.read = net_shake_read; | |||
hs->rd.kill = kill_shake; | |||
if (!incoming) | |||
net_send_shake(p); | |||
} | |||
int | |||
net_connect(const char *ip, int port, int *sd) | |||
{ | |||
struct addrinfo hints, *res; | |||
char portstr[6]; | |||
assert(btpd.npeers < btpd.maxpeers); | |||
if (snprintf(portstr, sizeof(portstr), "%d", port) >= sizeof(portstr)) | |||
return EINVAL; | |||
bzero(&hints, sizeof(hints)); | |||
hints.ai_family = AF_UNSPEC; | |||
hints.ai_flags = AI_NUMERICHOST; | |||
hints.ai_socktype = SOCK_STREAM; | |||
if (getaddrinfo(ip, portstr, &hints, &res) != 0) | |||
return errno; | |||
if (res) { | |||
if ((*sd = socket(PF_INET, SOCK_STREAM, 0)) == -1) { | |||
btpd_log(BTPD_L_CONN, "Botched connection %s.", strerror(errno)); | |||
freeaddrinfo(res); | |||
return errno; | |||
} | |||
set_nonblocking(*sd); | |||
if (connect(*sd, res->ai_addr, res->ai_addrlen) == -1 && | |||
errno != EINPROGRESS) { | |||
btpd_log(BTPD_L_CONN, "Botched connection %s.", strerror(errno)); | |||
close(*sd); | |||
freeaddrinfo(res); | |||
return errno; | |||
} | |||
} | |||
freeaddrinfo(res); | |||
btpd.npeers++; | |||
return 0; | |||
} | |||
void | |||
net_connection_cb(int sd, short type, void *arg) | |||
{ | |||
int nsd; | |||
nsd = accept(sd, NULL, NULL); | |||
if (nsd < 0) { | |||
if (errno == EWOULDBLOCK || errno == ECONNABORTED || errno == EINTR) | |||
return; | |||
else | |||
btpd_err("accept4: %s\n", strerror(errno)); | |||
} | |||
if (set_nonblocking(nsd) != 0) { | |||
close(nsd); | |||
return; | |||
} | |||
assert(btpd.npeers <= btpd.maxpeers); | |||
if (btpd.npeers == btpd.maxpeers) { | |||
close(nsd); | |||
return; | |||
} | |||
btpd.npeers++; | |||
peer_create_in(nsd); | |||
btpd_log(BTPD_L_CONN, "got connection.\n"); | |||
} | |||
void | |||
net_by_second(void) | |||
{ | |||
struct peer *p; | |||
struct torrent *tp; | |||
int ri = btpd.seconds % RATEHISTORY; | |||
TAILQ_FOREACH(tp, &btpd.cm_list, entry) { | |||
TAILQ_FOREACH(p, &tp->peers, cm_entry) { | |||
p->rate_to_me[ri] = 0; | |||
p->rate_from_me[ri] = 0; | |||
} | |||
} | |||
btpd.obw_left = btpd.obwlim; | |||
btpd.ibw_left = btpd.ibwlim; | |||
if (btpd.ibwlim > 0) { | |||
while ((p = TAILQ_FIRST(&btpd.readq)) != NULL && btpd.ibw_left > 0) { | |||
TAILQ_REMOVE(&btpd.readq, p, rq_entry); | |||
p->flags &= ~PF_ON_READQ; | |||
btpd.ibw_left -= p->reader->read(p, btpd.ibw_left); | |||
} | |||
} else { | |||
while ((p = TAILQ_FIRST(&btpd.readq)) != NULL) { | |||
TAILQ_REMOVE(&btpd.readq, p, rq_entry); | |||
p->flags &= ~PF_ON_READQ; | |||
p->reader->read(p, 0); | |||
} | |||
} | |||
if (btpd.obwlim) { | |||
while ((p = TAILQ_FIRST(&btpd.writeq)) != NULL && btpd.obw_left > 0) { | |||
TAILQ_REMOVE(&btpd.writeq, p, wq_entry); | |||
p->flags &= ~PF_ON_WRITEQ; | |||
btpd.obw_left -= net_write(p, btpd.obw_left); | |||
} | |||
} else { | |||
while ((p = TAILQ_FIRST(&btpd.writeq)) != NULL) { | |||
TAILQ_REMOVE(&btpd.writeq, p, wq_entry); | |||
p->flags &= ~PF_ON_WRITEQ; | |||
net_write(p, 0); | |||
} | |||
} | |||
} |
@@ -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 |
@@ -0,0 +1,178 @@ | |||
#include <sys/types.h> | |||
#include <string.h> | |||
#include <unistd.h> | |||
#include "btpd.h" | |||
unsigned long | |||
peer_get_rate(unsigned long *rates) | |||
{ | |||
unsigned long ret = 0; | |||
for (int i = 0; i < RATEHISTORY; i++) | |||
ret += rates[i]; | |||
return ret; | |||
} | |||
void | |||
peer_kill(struct peer *p) | |||
{ | |||
struct iob_link *iol; | |||
struct piece_req *req; | |||
btpd_log(BTPD_L_CONN, "killed peer.\n"); | |||
if (p->flags & PF_ATTACHED) | |||
cm_on_lost_peer(p); | |||
if (p->flags & PF_ON_READQ) | |||
TAILQ_REMOVE(&btpd.readq, p, rq_entry); | |||
if (p->flags & PF_ON_WRITEQ) | |||
TAILQ_REMOVE(&btpd.writeq, p, wq_entry); | |||
close(p->sd); | |||
event_del(&p->in_ev); | |||
event_del(&p->out_ev); | |||
iol = TAILQ_FIRST(&p->outq); | |||
while (iol != NULL) { | |||
struct iob_link *next = TAILQ_NEXT(iol, entry); | |||
iol->kill_buf(&iol->iob); | |||
free(iol); | |||
iol = next; | |||
} | |||
req = TAILQ_FIRST(&p->p_reqs); | |||
while (req != NULL) { | |||
struct piece_req *next = TAILQ_NEXT(req, entry); | |||
free(req); | |||
req = next; | |||
} | |||
req = TAILQ_FIRST(&p->my_reqs); | |||
while (req != NULL) { | |||
struct piece_req *next = TAILQ_NEXT(req, entry); | |||
free(req); | |||
req = next; | |||
} | |||
p->reader->kill(p->reader); | |||
if (p->piece_field != NULL) | |||
free(p->piece_field); | |||
free(p); | |||
btpd.npeers--; | |||
} | |||
void | |||
peer_request(struct peer *p, uint32_t index, uint32_t begin, uint32_t len) | |||
{ | |||
struct piece_req *req = btpd_calloc(1, sizeof(*req)); | |||
req->index = index; | |||
req->begin = begin; | |||
req->length = len; | |||
TAILQ_INSERT_TAIL(&p->my_reqs, req, entry); | |||
net_send_request(p, req); | |||
} | |||
void | |||
peer_cancel(struct peer *p, uint32_t index, uint32_t begin, uint32_t len) | |||
{ | |||
struct piece_req *req; | |||
again: | |||
req = TAILQ_FIRST(&p->my_reqs); | |||
while (req != NULL && | |||
!(index == req->index && | |||
begin == req->begin && | |||
len == req->length)) | |||
req = TAILQ_NEXT(req, entry); | |||
if (req != NULL) { | |||
net_send_cancel(p, req); | |||
TAILQ_REMOVE(&p->my_reqs, req, entry); | |||
free(req); | |||
goto again; | |||
} | |||
} | |||
void | |||
peer_have(struct peer *p, uint32_t index) | |||
{ | |||
net_send_have(p, index); | |||
} | |||
void | |||
peer_unchoke(struct peer *p) | |||
{ | |||
p->flags &= ~PF_I_CHOKE; | |||
net_send_unchoke(p); | |||
} | |||
void | |||
peer_choke(struct peer *p) | |||
{ | |||
struct piece_req *req; | |||
while ((req = TAILQ_FIRST(&p->p_reqs)) != NULL) | |||
net_unsend_piece(p, req); | |||
p->flags |= PF_I_CHOKE; | |||
net_send_choke(p); | |||
} | |||
void | |||
peer_want(struct peer *p, uint32_t index) | |||
{ | |||
p->nwant++; | |||
if (p->nwant == 1) { | |||
p->flags |= PF_I_WANT; | |||
net_send_interest(p); | |||
} | |||
} | |||
void | |||
peer_unwant(struct peer *p, uint32_t index) | |||
{ | |||
p->nwant--; | |||
if (p->nwant == 0) { | |||
p->flags &= ~PF_I_WANT; | |||
net_send_uninterest(p); | |||
} | |||
} | |||
static struct peer * | |||
peer_create_common(int sd) | |||
{ | |||
struct peer *p = btpd_calloc(1, sizeof(*p)); | |||
p->sd = sd; | |||
p->flags = PF_I_CHOKE | PF_P_CHOKE; | |||
TAILQ_INIT(&p->p_reqs); | |||
TAILQ_INIT(&p->my_reqs); | |||
TAILQ_INIT(&p->outq); | |||
event_set(&p->out_ev, p->sd, EV_WRITE, net_write_cb, p); | |||
event_set(&p->in_ev, p->sd, EV_READ, net_read_cb, p); | |||
event_add(&p->in_ev, NULL); | |||
return p; | |||
} | |||
void | |||
peer_create_in(int sd) | |||
{ | |||
struct peer *p = peer_create_common(sd); | |||
net_handshake(p, 1); | |||
} | |||
void | |||
peer_create_out(struct torrent *tp, | |||
const uint8_t *id, | |||
const char *ip, | |||
int port) | |||
{ | |||
int sd; | |||
struct peer *p; | |||
if (net_connect(ip, port, &sd) != 0) | |||
return; | |||
p = peer_create_common(sd); | |||
p->tp = tp; | |||
bcopy(id, p->id, 20); | |||
net_handshake(p, 0); | |||
} |
@@ -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 |
@@ -0,0 +1,706 @@ | |||
#include <sys/types.h> | |||
#include <sys/mman.h> | |||
#include <openssl/sha.h> | |||
#include <fcntl.h> | |||
#include <math.h> | |||
#include <stdio.h> | |||
#include <string.h> | |||
#include <unistd.h> | |||
#include "btpd.h" | |||
#include "stream.h" | |||
#include "tracker_req.h" | |||
#define BLOCKLEN (1 << 14) | |||
static void cm_on_piece(struct torrent *tp, struct piece *piece); | |||
static void | |||
assign_piece_requests_eg(struct piece *piece, struct peer *peer) | |||
{ | |||
for (unsigned i = 0; i < piece->nblocks; i++) { | |||
if (!has_bit(piece->have_field, i)) { | |||
uint32_t start = i * BLOCKLEN; | |||
uint32_t len; | |||
if (i < piece->nblocks - 1) | |||
len = BLOCKLEN; | |||
else if (piece->index < peer->tp->meta.npieces - 1) | |||
len = peer->tp->meta.piece_length - i * BLOCKLEN; | |||
else { | |||
off_t piece_len = | |||
peer->tp->meta.total_length - | |||
peer->tp->meta.piece_length * | |||
(peer->tp->meta.npieces - 1); | |||
len = piece_len - i * BLOCKLEN; | |||
} | |||
peer_request(peer, piece->index, start, len); | |||
} | |||
} | |||
} | |||
static void | |||
cm_assign_requests_eg(struct peer *peer) | |||
{ | |||
struct piece *piece; | |||
TAILQ_FOREACH(piece, &peer->tp->getlst, entry) { | |||
if (has_bit(peer->piece_field, piece->index)) { | |||
peer_want(peer, piece->index); | |||
if ((peer->flags & PF_P_CHOKE) == 0) | |||
assign_piece_requests_eg(piece, peer); | |||
} | |||
} | |||
} | |||
static void | |||
cm_unassign_requests_eg(struct peer *peer) | |||
{ | |||
struct piece_req *req = TAILQ_FIRST(&peer->my_reqs); | |||
while (req != NULL) { | |||
struct piece_req *next = TAILQ_NEXT(req, entry); | |||
free(req); | |||
req = next; | |||
} | |||
TAILQ_INIT(&peer->my_reqs); | |||
} | |||
static void | |||
cm_enter_endgame(struct torrent *tp) | |||
{ | |||
struct peer *peer; | |||
btpd_log(BTPD_L_POL, "Entering end game\n"); | |||
tp->endgame = 1; | |||
TAILQ_FOREACH(peer, &tp->peers, cm_entry) | |||
cm_assign_requests_eg(peer); | |||
} | |||
static int | |||
piece_full(struct piece *p) | |||
{ | |||
return p->ngot + p->nbusy == p->nblocks; | |||
} | |||
static int | |||
cm_should_schedule(struct torrent *tp) | |||
{ | |||
if (!tp->endgame) { | |||
int should = 1; | |||
struct piece *p = TAILQ_FIRST(&tp->getlst); | |||
while (p != NULL) { | |||
if (!piece_full(p)) { | |||
should = 0; | |||
break; | |||
} | |||
p = TAILQ_NEXT(p, entry); | |||
} | |||
return should; | |||
} else | |||
return 0; | |||
} | |||
static void | |||
cm_on_peerless_piece(struct torrent *tp, struct piece *piece) | |||
{ | |||
if (!tp->endgame) { | |||
assert(tp->piece_count[piece->index] == 0); | |||
btpd_log(BTPD_L_POL, "peerless piece %u\n", piece->index); | |||
msync(tp->imem, tp->isiz, MS_ASYNC); | |||
TAILQ_REMOVE(&tp->getlst, piece, entry); | |||
free(piece); | |||
if (cm_should_schedule(tp)) | |||
cm_schedule_piece(tp); | |||
} | |||
} | |||
static int | |||
rate_cmp(unsigned long rate1, unsigned long rate2) | |||
{ | |||
if (rate1 < rate2) | |||
return -1; | |||
else if (rate1 == rate2) | |||
return 0; | |||
else | |||
return 1; | |||
} | |||
static int | |||
dwnrate_cmp(const void *p1, const void *p2) | |||
{ | |||
unsigned long rate1 = peer_get_rate((*(struct peer **)p1)->rate_to_me); | |||
unsigned long rate2 = peer_get_rate((*(struct peer **)p2)->rate_to_me); | |||
return rate_cmp(rate1, rate2); | |||
} | |||
static int | |||
uprate_cmp(const void *p1, const void *p2) | |||
{ | |||
unsigned long rate1 = peer_get_rate((*(struct peer **)p1)->rate_from_me); | |||
unsigned long rate2 = peer_get_rate((*(struct peer **)p2)->rate_from_me); | |||
return rate_cmp(rate1, rate2); | |||
} | |||
static void | |||
choke_alg(struct torrent *tp) | |||
{ | |||
int i; | |||
struct peer *p; | |||
struct peer **psort; | |||
assert(tp->npeers > 0); | |||
psort = (struct peer **)btpd_malloc(tp->npeers * sizeof(p)); | |||
i = 0; | |||
TAILQ_FOREACH(p, &tp->peers, cm_entry) | |||
psort[i++] = p; | |||
if (tp->have_npieces == tp->meta.npieces) | |||
qsort(psort, tp->npeers, sizeof(p), uprate_cmp); | |||
else | |||
qsort(psort, tp->npeers, sizeof(p), dwnrate_cmp); | |||
tp->ndown = 0; | |||
if (tp->optimistic != NULL) { | |||
if (tp->optimistic->flags & PF_I_CHOKE) | |||
peer_unchoke(tp->optimistic); | |||
if (tp->optimistic->flags & PF_P_WANT) | |||
tp->ndown = 1; | |||
} | |||
for (i = tp->npeers - 1; i >= 0; i--) { | |||
if (psort[i] == tp->optimistic) | |||
continue; | |||
if (tp->ndown < 4) { | |||
if (psort[i]->flags & PF_P_WANT) | |||
tp->ndown++; | |||
if (psort[i]->flags & PF_I_CHOKE) | |||
peer_unchoke(psort[i]); | |||
} else { | |||
if ((psort[i]->flags & PF_I_CHOKE) == 0) | |||
peer_choke(psort[i]); | |||
} | |||
} | |||
free(psort); | |||
tp->choke_time = btpd.seconds + 10; | |||
} | |||
static void | |||
next_optimistic(struct torrent *tp, struct peer *np) | |||
{ | |||
if (np != NULL) | |||
tp->optimistic = np; | |||
else if (tp->optimistic == NULL) | |||
tp->optimistic = TAILQ_FIRST(&tp->peers); | |||
else { | |||
np = TAILQ_NEXT(tp->optimistic, cm_entry); | |||
if (np != NULL) | |||
tp->optimistic = np; | |||
else | |||
tp->optimistic = TAILQ_FIRST(&tp->peers); | |||
} | |||
assert(tp->optimistic != NULL); | |||
choke_alg(tp); | |||
tp->opt_time = btpd.seconds + 30; | |||
} | |||
void | |||
cm_on_upload(struct peer *peer) | |||
{ | |||
choke_alg(peer->tp); | |||
} | |||
void | |||
cm_on_unupload(struct peer *peer) | |||
{ | |||
choke_alg(peer->tp); | |||
} | |||
void | |||
cm_by_second(struct torrent *tp) | |||
{ | |||
if (btpd.seconds == tp->tracker_time) | |||
tracker_req(tp, TR_EMPTY); | |||
if (btpd.seconds == tp->opt_time) | |||
next_optimistic(tp, NULL); | |||
if (btpd.seconds == tp->choke_time) | |||
choke_alg(tp); | |||
} | |||
void | |||
cm_on_download(struct peer *peer) | |||
{ | |||
if (!peer->tp->endgame) | |||
assert(cm_assign_requests(peer, 5) != 0); | |||
else | |||
cm_assign_requests_eg(peer); | |||
} | |||
void | |||
cm_on_undownload(struct peer *peer) | |||
{ | |||
if (!peer->tp->endgame) | |||
cm_unassign_requests(peer); | |||
else | |||
cm_unassign_requests_eg(peer); | |||
} | |||
void | |||
cm_on_piece_ann(struct peer *peer, uint32_t piece) | |||
{ | |||
struct piece *p; | |||
struct torrent *tp = peer->tp; | |||
tp->piece_count[piece]++; | |||
if (has_bit(tp->piece_field, piece)) | |||
return; | |||
p = TAILQ_FIRST(&tp->getlst); | |||
while (p != NULL && p->index != piece) | |||
p = TAILQ_NEXT(p, entry); | |||
if (p != NULL && tp->endgame) { | |||
peer_want(peer, p->index); | |||
if ((peer->flags & PF_P_CHOKE) == 0) | |||
cm_on_download(peer); | |||
} else if (p != NULL && !piece_full(p)) { | |||
peer_want(peer, p->index); | |||
if ((peer->flags & PF_P_CHOKE) == 0 && TAILQ_EMPTY(&peer->my_reqs)) | |||
cm_on_download(peer); | |||
} else if (p == NULL && cm_should_schedule(tp)) | |||
cm_schedule_piece(tp); | |||
} | |||
void | |||
cm_on_lost_peer(struct peer *peer) | |||
{ | |||
struct torrent *tp = peer->tp; | |||
struct piece *piece; | |||
tp->npeers--; | |||
peer->flags &= ~PF_ATTACHED; | |||
if (tp->npeers == 0) { | |||
TAILQ_REMOVE(&tp->peers, peer, cm_entry); | |||
tp->optimistic = NULL; | |||
tp->choke_time = tp->opt_time = 0; | |||
} else if (tp->optimistic == peer) { | |||
struct peer *next = TAILQ_NEXT(peer, cm_entry); | |||
TAILQ_REMOVE(&tp->peers, peer, cm_entry); | |||
next_optimistic(peer->tp, next); | |||
} else if ((peer->flags & (PF_P_WANT|PF_I_CHOKE)) == PF_P_WANT) { | |||
TAILQ_REMOVE(&tp->peers, peer, cm_entry); | |||
cm_on_unupload(peer); | |||
} else { | |||
TAILQ_REMOVE(&tp->peers, peer, cm_entry); | |||
} | |||
for (size_t i = 0; i < peer->tp->meta.npieces; i++) | |||
if (has_bit(peer->piece_field, i)) | |||
tp->piece_count[i]--; | |||
if ((peer->flags & (PF_I_WANT|PF_P_CHOKE)) == PF_I_WANT) | |||
cm_on_undownload(peer); | |||
for (piece = TAILQ_FIRST(&tp->getlst); piece; | |||
piece = TAILQ_NEXT(piece, entry)) { | |||
if (has_bit(peer->piece_field, piece->index) && | |||
tp->piece_count[piece->index] == 0) | |||
cm_on_peerless_piece(tp, piece); | |||
} | |||
} | |||
void | |||
cm_on_new_peer(struct peer *peer) | |||
{ | |||
struct torrent *tp = peer->tp; | |||
tp->npeers++; | |||
peer->flags |= PF_ATTACHED; | |||
if (tp->npeers == 1) { | |||
TAILQ_INSERT_HEAD(&tp->peers, peer, cm_entry); | |||
next_optimistic(peer->tp, peer); | |||
} else { | |||
if (random() > RAND_MAX / 3) | |||
TAILQ_INSERT_AFTER(&tp->peers, tp->optimistic, peer, cm_entry); | |||
else | |||
TAILQ_INSERT_TAIL(&tp->peers, peer, cm_entry); | |||
} | |||
} | |||
static int | |||
missing_piece(struct torrent *tp, uint32_t index) | |||
{ | |||
struct piece *p; | |||
if (has_bit(tp->piece_field, index)) | |||
return 0; | |||
TAILQ_FOREACH(p, &tp->getlst, entry) | |||
if (p->index == index) | |||
return 0; | |||
return 1; | |||
} | |||
static struct piece * | |||
alloc_piece(struct torrent *tp, uint32_t piece) | |||
{ | |||
struct piece *res; | |||
size_t mem, field; | |||
unsigned long nblocks; | |||
off_t piece_length = tp->meta.piece_length; | |||
if (piece == tp->meta.npieces - 1) { | |||
off_t totl = tp->meta.total_length; | |||
off_t npm1 = tp->meta.npieces - 1; | |||
piece_length = totl - npm1 * piece_length; | |||
} | |||
nblocks = (unsigned)ceil((double)piece_length / BLOCKLEN); | |||
field = (size_t)ceil(nblocks / 8.0); | |||
mem = sizeof(*res) + field; | |||
res = btpd_calloc(1, mem); | |||
res->down_field = (uint8_t *)res + sizeof(*res); | |||
res->have_field = | |||
tp->block_field + | |||
(size_t)ceil(piece * tp->meta.piece_length / (double)(1 << 17)); | |||
res->nblocks = nblocks; | |||
res->index = piece; | |||
for (unsigned i = 0; i < nblocks; i++) | |||
if (has_bit(res->have_field, i)) | |||
res->ngot++; | |||
return res; | |||
} | |||
static void | |||
activate_piece_peers(struct torrent *tp, struct piece *piece) | |||
{ | |||
struct peer *peer; | |||
assert(!piece_full(piece) && tp->endgame == 0); | |||
TAILQ_FOREACH(peer, &tp->peers, cm_entry) | |||
if (has_bit(peer->piece_field, piece->index)) | |||
peer_want(peer, piece->index); | |||
peer = TAILQ_FIRST(&tp->peers); | |||
while (peer != NULL && !piece_full(piece)) { | |||
if ((peer->flags & (PF_P_CHOKE|PF_I_WANT)) == PF_I_WANT && | |||
TAILQ_EMPTY(&peer->my_reqs)) { | |||
// | |||
cm_on_download(peer); | |||
} | |||
peer = TAILQ_NEXT(peer, cm_entry); | |||
} | |||
} | |||
void | |||
cm_schedule_piece(struct torrent *tp) | |||
{ | |||
uint32_t i; | |||
uint32_t min_i; | |||
unsigned min_c; | |||
struct piece *piece; | |||
int enter_end_game = 1; | |||
assert(tp->endgame == 0); | |||
for (i = 0; i < tp->meta.npieces; i++) | |||
if (missing_piece(tp, i)) { | |||
enter_end_game = 0; | |||
if (tp->piece_count[i] > 0) | |||
break; | |||
} | |||
if (i == tp->meta.npieces) { | |||
if (enter_end_game) | |||
cm_enter_endgame(tp); | |||
return; | |||
} | |||
min_i = i; | |||
min_c = 1; | |||
for(i++; i < tp->meta.npieces; i++) { | |||
if (missing_piece(tp, i) && tp->piece_count[i] > 0) { | |||
if (tp->piece_count[i] == tp->piece_count[min_i]) | |||
min_c++; | |||
else if (tp->piece_count[i] < tp->piece_count[min_i]) { | |||
min_i = i; | |||
min_c = 1; | |||
} | |||
} | |||
} | |||
if (min_c > 1) { | |||
min_c = 1 + rint((double)random() * (min_c - 1) / RAND_MAX); | |||
for (i = min_i; min_c > 0; i++) { | |||
if (missing_piece(tp, i) && | |||
tp->piece_count[i] == tp->piece_count[min_i]) { | |||
// | |||
min_c--; | |||
min_i = i; | |||
} | |||
} | |||
} | |||
btpd_log(BTPD_L_POL, "scheduled piece: %u.\n", min_i); | |||
piece = alloc_piece(tp, min_i); | |||
TAILQ_INSERT_HEAD(&tp->getlst, piece, entry); | |||
if (piece->ngot == piece->nblocks) { | |||
cm_on_piece(tp, piece); | |||
if (cm_should_schedule(tp)) | |||
cm_schedule_piece(tp); | |||
} else | |||
activate_piece_peers(tp, piece); | |||
} | |||
static void | |||
cm_on_piece_unfull(struct torrent *tp, struct piece *piece) | |||
{ | |||
activate_piece_peers(tp, piece); | |||
} | |||
static void | |||
cm_on_piece_full(struct torrent *tp, struct piece *piece) | |||
{ | |||
struct peer *p; | |||
if (cm_should_schedule(tp)) | |||
cm_schedule_piece(tp); | |||
TAILQ_FOREACH(p, &tp->peers, cm_entry) { | |||
if (has_bit(p->piece_field, piece->index)) | |||
peer_unwant(p, piece->index); | |||
} | |||
} | |||
static int | |||
cm_assign_request(struct peer *peer) | |||
{ | |||
struct piece *piece; | |||
unsigned i; | |||
uint32_t start, len; | |||
piece = TAILQ_FIRST(&peer->tp->getlst); | |||
while (piece != NULL) { | |||
if (!piece_full(piece) && has_bit(peer->piece_field, piece->index)) | |||
break; | |||
piece = TAILQ_NEXT(piece, entry); | |||
} | |||
if (piece == NULL) | |||
return 0; | |||
i = 0; | |||
while(has_bit(piece->have_field, i) || has_bit(piece->down_field, i)) | |||
i++; | |||
start = i * BLOCKLEN; | |||
if (i < piece->nblocks - 1) | |||
len = BLOCKLEN; | |||
else if (piece->index < peer->tp->meta.npieces - 1) | |||
len = peer->tp->meta.piece_length - i * BLOCKLEN; | |||
else { | |||
off_t piece_len = | |||
peer->tp->meta.total_length - | |||
peer->tp->meta.piece_length * (peer->tp->meta.npieces - 1); | |||
len = piece_len - i * BLOCKLEN; | |||
} | |||
peer_request(peer, piece->index, start, len); | |||
set_bit(piece->down_field, i); | |||
piece->nbusy++; | |||
if (piece_full(piece)) | |||
cm_on_piece_full(peer->tp, piece); | |||
return 1; | |||
} | |||
int | |||
cm_assign_requests(struct peer *peer, int nreqs) | |||
{ | |||
int onreqs = nreqs; | |||
while (nreqs > 0 && cm_assign_request(peer)) | |||
nreqs--; | |||
return onreqs - nreqs; | |||
} | |||
void | |||
cm_unassign_requests(struct peer *peer) | |||
{ | |||
struct torrent *tp = peer->tp; | |||
struct piece *piece = TAILQ_FIRST(&tp->getlst); | |||
while (piece != NULL) { | |||
int was_full = piece_full(piece); | |||
struct piece_req *req = TAILQ_FIRST(&peer->my_reqs); | |||
while (req != NULL) { | |||
struct piece_req *next = TAILQ_NEXT(req, entry); | |||
if (piece->index == req->index) { | |||
assert(has_bit(piece->down_field, req->begin / BLOCKLEN)); | |||
clear_bit(piece->down_field, req->begin / BLOCKLEN); | |||
piece->nbusy--; | |||
TAILQ_REMOVE(&peer->my_reqs, req, entry); | |||
free(req); | |||
} | |||
req = next; | |||
} | |||
if (was_full && !piece_full(piece)) | |||
cm_on_piece_unfull(tp, piece); | |||
piece = TAILQ_NEXT(piece, entry); | |||
} | |||
assert(TAILQ_EMPTY(&peer->my_reqs)); | |||
} | |||
static int | |||
test_hash(struct torrent *tp, uint8_t *hash, unsigned long index) | |||
{ | |||
if (tp->meta.piece_hash != NULL) | |||
return memcmp(hash, tp->meta.piece_hash[index], SHA_DIGEST_LENGTH); | |||
else { | |||
char piece_hash[SHA_DIGEST_LENGTH]; | |||
int fd; | |||
int bufi; | |||
int err; | |||
err = vopen(&fd, O_RDONLY, "%s", tp->relpath); | |||
if (err != 0) | |||
btpd_err("test_hash: %s\n", strerror(err)); | |||
err = lseek(fd, tp->meta.pieces_off + index * SHA_DIGEST_LENGTH, | |||
SEEK_SET); | |||
if (err < 0) | |||
btpd_err("test_hash: %s\n", strerror(errno)); | |||
bufi = 0; | |||
while (bufi < SHA_DIGEST_LENGTH) { | |||
ssize_t nread = | |||
read(fd, piece_hash + bufi, SHA_DIGEST_LENGTH - bufi); | |||
bufi += nread; | |||
} | |||
close(fd); | |||
return memcmp(hash, piece_hash, SHA_DIGEST_LENGTH); | |||
} | |||
} | |||
static int | |||
ro_fd_cb(const char *path, int *fd, void *arg) | |||
{ | |||
struct torrent *tp = arg; | |||
return vopen(fd, O_RDONLY, "%s.d/%s", tp->relpath, path); | |||
} | |||
static void | |||
cm_on_piece(struct torrent *tp, struct piece *piece) | |||
{ | |||
int err; | |||
uint8_t hash[20]; | |||
struct bt_stream_ro *bts; | |||
off_t plen = tp->meta.piece_length; | |||
if (piece->index == tp->meta.npieces - 1) { | |||
plen = | |||
tp->meta.total_length - | |||
tp->meta.piece_length * (tp->meta.npieces - 1); | |||
} | |||
if ((bts = bts_open_ro(&tp->meta, piece->index * tp->meta.piece_length, | |||
ro_fd_cb, tp)) == NULL) | |||
btpd_err("Out of memory.\n"); | |||
if ((err = bts_sha(bts, plen, hash)) != 0) | |||
btpd_err("Ouch! %s\n", strerror(err)); | |||
bts_close_ro(bts); | |||
if (test_hash(tp, hash, piece->index) == 0) { | |||
btpd_log(BTPD_L_POL, "Got piece: %u.\n", piece->index); | |||
struct peer *p; | |||
set_bit(tp->piece_field, piece->index); | |||
tp->have_npieces++; | |||
if (tp->have_npieces == tp->meta.npieces) { | |||
btpd_log(BTPD_L_BTPD, "Finished: %s.\n", tp->relpath); | |||
tracker_req(tp, TR_COMPLETED); | |||
} | |||
msync(tp->imem, tp->isiz, MS_ASYNC); | |||
TAILQ_FOREACH(p, &tp->peers, cm_entry) | |||
peer_have(p, piece->index); | |||
if (tp->endgame) | |||
TAILQ_FOREACH(p, &tp->peers, cm_entry) | |||
peer_unwant(p, piece->index); | |||
TAILQ_REMOVE(&tp->getlst, piece, entry); | |||
free(piece); | |||
} else if (tp->endgame) { | |||
struct peer *p; | |||
btpd_log(BTPD_L_ERROR, "Bad hash for piece %u of %s.\n", | |||
piece->index, tp->relpath); | |||
for (unsigned i = 0; i < piece->nblocks; i++) | |||
clear_bit(piece->have_field, i); | |||
piece->ngot = 0; | |||
TAILQ_FOREACH(p, &tp->peers, cm_entry) | |||
if (has_bit(p->piece_field, piece->index) && | |||
(p->flags & PF_P_CHOKE) == 0) { | |||
// | |||
assign_piece_requests_eg(piece, p); | |||
} | |||
} else { | |||
btpd_log(BTPD_L_ERROR, "Bad hash for piece %u of %s.\n", | |||
piece->index, tp->relpath); | |||
for (unsigned i = 0; i < piece->nblocks; i++) { | |||
clear_bit(piece->have_field, i); | |||
assert(!has_bit(piece->down_field, i)); | |||
} | |||
msync(tp->imem, tp->isiz, MS_ASYNC); | |||
TAILQ_REMOVE(&tp->getlst, piece, entry); | |||
free(piece); | |||
if (cm_should_schedule(tp)) | |||
cm_schedule_piece(tp); | |||
} | |||
} | |||
void | |||
cm_on_block(struct peer *peer) | |||
{ | |||
struct torrent *tp = peer->tp; | |||
struct piece_req *req = TAILQ_FIRST(&peer->my_reqs); | |||
struct piece *piece = TAILQ_FIRST(&tp->getlst); | |||
unsigned block = req->begin / BLOCKLEN; | |||
while (piece != NULL && piece->index != req->index) | |||
piece = TAILQ_NEXT(piece, entry); | |||
set_bit(piece->have_field, block); | |||
clear_bit(piece->down_field, block); | |||
piece->ngot++; | |||
piece->nbusy--; | |||
if (tp->endgame) { | |||
uint32_t index = req->index; | |||
uint32_t begin = req->begin; | |||
uint32_t length = req->length; | |||
struct peer *p; | |||
TAILQ_REMOVE(&peer->my_reqs, req, entry); | |||
free(req); | |||
TAILQ_FOREACH(p, &tp->peers, cm_entry) { | |||
if (has_bit(p->piece_field, index) && | |||
(peer->flags & PF_P_CHOKE) == 0) | |||
peer_cancel(p, index, begin, length); | |||
} | |||
if (piece->ngot == piece->nblocks) | |||
cm_on_piece(tp, piece); | |||
} else { | |||
TAILQ_REMOVE(&peer->my_reqs, req, entry); | |||
free(req); | |||
if (piece->ngot == piece->nblocks) | |||
cm_on_piece(tp, piece); | |||
if ((peer->flags & (PF_I_WANT|PF_P_CHOKE)) == PF_I_WANT) | |||
cm_assign_requests(peer, 1); | |||
} | |||
} |
@@ -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 |
@@ -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 |
@@ -0,0 +1,244 @@ | |||
#include <sys/types.h> | |||
#include <sys/mman.h> | |||
#include <sys/stat.h> | |||
#include <assert.h> | |||
#include <errno.h> | |||
#include <fcntl.h> | |||
#include <math.h> | |||
#include <limits.h> | |||
#include <stdarg.h> | |||
#include <stdio.h> | |||
#include <stdlib.h> | |||
#include <string.h> | |||
#include <unistd.h> | |||
#include <openssl/sha.h> | |||
#include "btpd.h" | |||
#include "tracker_req.h" | |||
#include "stream.h" | |||
static int | |||
ro_fd_cb(const char *path, int *fd, void *arg) | |||
{ | |||
struct torrent *tp = arg; | |||
return vopen(fd, O_RDONLY, "%s.d/%s", tp->relpath, path); | |||
} | |||
static int | |||
wo_fd_cb(const char *path, int *fd, void *arg) | |||
{ | |||
struct torrent *tp = arg; | |||
return vopen(fd, O_WRONLY|O_CREAT, "%s.d/%s", tp->relpath, path); | |||
} | |||
static int | |||
torrent_load3(const char *file, struct metainfo *mi, char *mem, size_t memsiz) | |||
{ | |||
struct torrent *tp = btpd_calloc(1, sizeof(*tp)); | |||
tp->relpath = strdup(file); | |||
if (tp->relpath == NULL) | |||
btpd_err("Out of memory.\n"); | |||
tp->piece_count = btpd_calloc(mi->npieces, sizeof(tp->piece_count[0])); | |||
TAILQ_INIT(&tp->peers); | |||
TAILQ_INIT(&tp->getlst); | |||
tp->imem = mem; | |||
tp->isiz = memsiz; | |||
tp->piece_field = tp->imem; | |||
tp->block_field = | |||
(uint8_t *)tp->imem + (size_t)ceil(mi->npieces / 8.0); | |||
for (uint32_t i = 0; i < mi->npieces; i++) | |||
if (has_bit(tp->piece_field, i)) | |||
tp->have_npieces++; | |||
tp->meta = *mi; | |||
free(mi); | |||
TAILQ_INSERT_TAIL(&btpd.cm_list, tp, entry); | |||
tracker_req(tp, TR_STARTED); | |||
btpd.ntorrents++; | |||
return 0; | |||
} | |||
static int | |||
torrent_load2(const char *file, struct metainfo *mi) | |||
{ | |||
int error, ifd; | |||
struct stat sb; | |||
char *mem; | |||
size_t memsiz; | |||
if ((error = vopen(&ifd, O_RDWR, "%s.i", file)) != 0) { | |||
btpd_log(BTPD_L_ERROR, "Error opening %s.i: %s.\n", | |||
file, strerror(error)); | |||
return error; | |||
} | |||
if (fstat(ifd, &sb) == -1) { | |||
error = errno; | |||
btpd_log(BTPD_L_ERROR, "Error stating %s.i: %s.\n", | |||
file, strerror(error)); | |||
close(ifd); | |||
return error; | |||
} | |||
memsiz = | |||
ceil(mi->npieces / 8.0) + | |||
ceil(mi->npieces * mi->piece_length / (double)(1 << 17)); | |||
if (sb.st_size != memsiz) { | |||
btpd_log(BTPD_L_ERROR, "File has wrong size: %s.i.\n", file); | |||
close(ifd); | |||
return EINVAL; | |||
} | |||
mem = mmap(NULL, memsiz, PROT_READ|PROT_WRITE, MAP_SHARED, ifd, 0); | |||
if (mem == MAP_FAILED) | |||
btpd_err("Error mmap'ing %s.i: %s.\n", file, strerror(errno)); | |||
close(ifd); | |||
if ((error = torrent_load3(file, mi, mem, memsiz) != 0)) { | |||
munmap(mem, memsiz); | |||
return error; | |||
} | |||
return 0; | |||
} | |||
int | |||
torrent_load(const char *file) | |||
{ | |||
struct metainfo *mi; | |||
int error; | |||
if ((error = load_metainfo(file, -1, 0, &mi)) != 0) { | |||
btpd_log(BTPD_L_ERROR, "Couldn't load metainfo file %s: %s.\n", | |||
file, strerror(error)); | |||
return error; | |||
} | |||
if (torrent_get_by_hash(mi->info_hash) != NULL) { | |||
btpd_log(BTPD_L_BTPD, "%s has same hash as an already loaded torrent.\n", file); | |||
error = EEXIST; | |||
} | |||
if (error == 0) | |||
error = torrent_load2(file, mi); | |||
if (error != 0) { | |||
clear_metainfo(mi); | |||
free(mi); | |||
} | |||
return error; | |||
} | |||
void | |||
torrent_unload(struct torrent *tp) | |||
{ | |||
struct peer *peer; | |||
struct piece *piece; | |||
btpd_log(BTPD_L_BTPD, "Unloading %s.\n", tp->relpath); | |||
tracker_req(tp, TR_STOPPED); | |||
peer = TAILQ_FIRST(&tp->peers); | |||
while (peer != NULL) { | |||
struct peer *next = TAILQ_NEXT(peer, cm_entry); | |||
peer->flags &= ~PF_ATTACHED; | |||
peer_kill(peer); | |||
peer = next; | |||
} | |||
piece = TAILQ_FIRST(&tp->getlst); | |||
while (piece != NULL) { | |||
struct piece *next = TAILQ_NEXT(piece, entry); | |||
free(piece); | |||
piece = next; | |||
} | |||
free(tp->piece_count); | |||
free((void *)tp->relpath); | |||
clear_metainfo(&tp->meta); | |||
munmap(tp->imem, tp->isiz); | |||
TAILQ_REMOVE(&btpd.cm_list, tp, entry); | |||
free(tp); | |||
btpd.ntorrents--; | |||
} | |||
off_t | |||
torrent_bytes_left(struct torrent *tp) | |||
{ | |||
if (tp->have_npieces == 0) | |||
return tp->meta.total_length; | |||
else if (has_bit(tp->piece_field, tp->meta.npieces - 1)) { | |||
return tp->meta.total_length - | |||
((tp->have_npieces - 1) * tp->meta.piece_length + | |||
tp->meta.total_length % tp->meta.piece_length); | |||
} else | |||
return tp->meta.total_length - | |||
tp->have_npieces * tp->meta.piece_length; | |||
} | |||
char * | |||
torrent_get_bytes(struct torrent *tp, off_t start, size_t len) | |||
{ | |||
char *buf = btpd_malloc(len); | |||
struct bt_stream_ro *bts; | |||
if ((bts = bts_open_ro(&tp->meta, start, ro_fd_cb, tp)) == NULL) | |||
btpd_err("Out of memory.\n"); | |||
if (bts_read_ro(bts, buf, len) != 0) | |||
btpd_err("Io error.\n"); | |||
bts_close_ro(bts); | |||
return buf; | |||
} | |||
void | |||
torrent_put_bytes(struct torrent *tp, const char *buf, off_t start, size_t len) | |||
{ | |||
int err; | |||
struct bt_stream_wo *bts; | |||
if ((bts = bts_open_wo(&tp->meta, start, wo_fd_cb, tp)) == NULL) | |||
btpd_err("Out of memory.\n"); | |||
if ((err = bts_write_wo(bts, buf, len)) != 0) | |||
btpd_err("Io error1: %s\n", strerror(err)); | |||
if ((err = bts_close_wo(bts)) != 0) | |||
btpd_err("Io error2: %s\n", strerror(err)); | |||
} | |||
int | |||
torrent_has_peer(struct torrent *tp, const uint8_t *id) | |||
{ | |||
int has = 0; | |||
struct peer *p = TAILQ_FIRST(&tp->peers); | |||
while (p != NULL) { | |||
if (bcmp(p->id, id, 20) == 0) { | |||
has = 1; | |||
break; | |||
} | |||
p = TAILQ_NEXT(p, cm_entry); | |||
} | |||
return has; | |||
} | |||
struct torrent * | |||
torrent_get_by_hash(const uint8_t *hash) | |||
{ | |||
struct torrent *tp = TAILQ_FIRST(&btpd.cm_list); | |||
while (tp != NULL && bcmp(hash, tp->meta.info_hash, 20) != 0) | |||
tp = TAILQ_NEXT(tp, entry); | |||
return tp; | |||
} |
@@ -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 |
@@ -0,0 +1,300 @@ | |||
#include <sys/types.h> | |||
#include <sys/socket.h> | |||
#include <netinet/in.h> | |||
#include <netdb.h> | |||
#include <sys/wait.h> | |||
#include <sys/mman.h> | |||
#include <stdio.h> | |||
#include <errno.h> | |||
#include <stdlib.h> | |||
#include <unistd.h> | |||
#include <string.h> | |||
#include <inttypes.h> | |||
#include <curl/curl.h> | |||
#include "btpd.h" | |||
#include "tracker_req.h" | |||
#ifndef PRIu64 | |||
#define PRIu64 "llu" | |||
#endif | |||
#define REQ_SIZE (getpagesize() * 2) | |||
struct tracker_req { | |||
struct child child; | |||
enum tr_event tr_event; | |||
uint8_t info_hash[20]; | |||
struct io_buffer *res; | |||
}; | |||
static void | |||
maybe_connect_to(struct torrent *tp, const char *pinfo) | |||
{ | |||
const char *pid = NULL; | |||
char *ip = NULL; | |||
int64_t port; | |||
size_t len; | |||
if (!benc_isdct(pinfo)) | |||
return; | |||
if (benc_dget_str(pinfo, "peer id", &pid, &len) != 0 || len != 20) | |||
return; | |||
if (bcmp(btpd.peer_id, pid, 20) == 0) | |||
return; | |||
if (torrent_has_peer(tp, pid)) | |||
return; | |||
if (benc_dget_strz(pinfo, "ip", &ip, NULL) != 0) | |||
goto out; | |||
if (benc_dget_int64(pinfo, "port", &port) != 0) | |||
goto out; | |||
peer_create_out(tp, pid, ip, port); | |||
out: | |||
if (ip != NULL) | |||
free(ip); | |||
} | |||
static void | |||
tracker_done(struct child *child) | |||
{ | |||
struct tracker_req *req = (struct tracker_req *)child; | |||
int failed = 0; | |||
char *buf; | |||
const char *peers; | |||
uint32_t interval; | |||
struct torrent *tp; | |||
if ((tp = torrent_get_by_hash(req->info_hash)) == NULL) | |||
goto out; | |||
if (benc_validate(req->res->buf, req->res->buf_off) != 0 | |||
|| !benc_isdct(req->res->buf)) { | |||
if (req->res->buf_off != 0) { | |||
fwrite(req->res->buf, 1, req->res->buf_off, (stdout)); | |||
putchar('\n'); | |||
} | |||
btpd_log(BTPD_L_ERROR, "Bad data from tracker.\n"); | |||
failed = 1; | |||
goto out; | |||
} | |||
if ((benc_dget_strz(req->res->buf, "failure reason", &buf, NULL)) == 0) { | |||
btpd_log(BTPD_L_ERROR, "Tracker failure: %s.\n", buf); | |||
free(buf); | |||
failed = 1; | |||
goto out; | |||
} | |||
if ((benc_dget_uint32(req->res->buf, "interval", &interval)) != 0) { | |||
btpd_log(BTPD_L_ERROR, "Bad data from tracker.\n"); | |||
failed = 1; | |||
goto out; | |||
} | |||
tp->tracker_time = btpd.seconds + interval; | |||
if ((benc_dget_lst(req->res->buf, "peers", &peers)) != 0) { | |||
btpd_log(BTPD_L_TRACKER, "Bad data from tracker.\n"); | |||
failed = 1; | |||
goto out; | |||
} | |||
for (peers = benc_first(peers); | |||
peers != NULL && btpd.npeers < btpd.maxpeers; | |||
peers = benc_next(peers)) | |||
maybe_connect_to(tp, peers); | |||
out: | |||
if (failed) { | |||
if (req->tr_event == TR_STARTED) { | |||
btpd_log(BTPD_L_BTPD, | |||
"Start request failed for %s.\n", tp->relpath); | |||
torrent_unload(tp); | |||
} else | |||
tp->tracker_time = btpd.seconds + 10; | |||
} | |||
munmap(req->res, REQ_SIZE); | |||
free(req); | |||
} | |||
static const char * | |||
event2str(enum tr_event ev) | |||
{ | |||
switch (ev) { | |||
case TR_STARTED: | |||
return "started"; | |||
case TR_STOPPED: | |||
return "stopped"; | |||
case TR_COMPLETED: | |||
return "completed"; | |||
case TR_EMPTY: | |||
return ""; | |||
default: | |||
btpd_err("Bad tracker event %d.\n", ev); | |||
return ""; // Shut GCC up! | |||
} | |||
} | |||
static int | |||
create_url(struct tracker_req *req, struct torrent *tp, char **url) | |||
{ | |||
char e_hash[61], e_id[61]; | |||
char qc; | |||
int i; | |||
uint64_t left; | |||
const char *event; | |||
event = event2str(req->tr_event); | |||
qc = (strchr(tp->meta.announce, '?') == NULL) ? '?' : '&'; | |||
for (i = 0; i < 20; i++) | |||
sprintf(e_hash + i * 3, "%%%.2x", tp->meta.info_hash[i]); | |||
e_hash[61] = '\0'; | |||
for (i = 0; i < 20; i++) | |||
sprintf(e_id + i * 3, "%%%.2x", btpd.peer_id[i]); | |||
e_id[61] = '\0'; | |||
left = torrent_bytes_left(tp); | |||
i = asprintf(url, "%s%cinfo_hash=%s&peer_id=%s&port=%d" | |||
"&uploaded=%" PRIu64 | |||
"&downloaded=%" PRIu64 | |||
"&left=%" PRIu64 | |||
"%s%s", | |||
tp->meta.announce, qc, e_hash, e_id, btpd.port, | |||
tp->uploaded, tp->downloaded, left, | |||
req->tr_event == TR_EMPTY ? "" : "&event=", | |||
event); | |||
if (i < 0) | |||
return ENOMEM; | |||
return 0; | |||
} | |||
static size_t | |||
http_cb(void *ptr, size_t size, size_t nmemb, void *stream) | |||
{ | |||
struct tracker_req *req = (struct tracker_req *)stream; | |||
size_t nbytes = size * nmemb; | |||
if (nbytes <= req->res->buf_len - req->res->buf_off) { | |||
memcpy(req->res->buf + req->res->buf_off, ptr, nbytes); | |||
req->res->buf_off += nbytes; | |||
return nbytes; | |||
} | |||
else | |||
return 0; | |||
} | |||
static void | |||
http_helper(struct tracker_req *req, struct torrent *tp) | |||
{ | |||
char cerror[CURL_ERROR_SIZE]; | |||
char fr[] = "failure reason"; | |||
CURL *handle; | |||
char *url; | |||
int err; | |||
if (create_url(req, tp, &url) != 0) | |||
goto memory_error; | |||
if (curl_global_init(0) != 0) | |||
goto libcurl_error; | |||
if ((handle = curl_easy_init()) == NULL) | |||
goto libcurl_error; | |||
err = curl_easy_setopt(handle, CURLOPT_URL, url); | |||
if (err == 0) | |||
err = curl_easy_setopt(handle, CURLOPT_USERAGENT, btpd.version); | |||
if (err == 0) | |||
err = curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, http_cb); | |||
if (err == 0) | |||
err = curl_easy_setopt(handle, CURLOPT_WRITEDATA, req); | |||
if (err == 0) | |||
err = curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, cerror); | |||
if (err != 0) { | |||
strncpy(cerror, curl_easy_strerror(err), CURL_ERROR_SIZE - 1); | |||
goto handle_error; | |||
} | |||
req->res->buf_off = 0; | |||
if (curl_easy_perform(handle) != 0) | |||
goto handle_error; | |||
#if 0 | |||
curl_easy_cleanup(handle); | |||
curl_global_cleanup(); | |||
free(url); | |||
#endif | |||
exit(0); | |||
memory_error: | |||
strncpy(cerror, "Out of memory", CURL_ERROR_SIZE - 1); | |||
goto handle_error; | |||
libcurl_error: | |||
strncpy(cerror, "Generic libcurl error", CURL_ERROR_SIZE - 1); | |||
goto handle_error; | |||
handle_error: | |||
req->res->buf_off = | |||
snprintf(req->res->buf, req->res->buf_len, | |||
"d%d:%s%d:%se", (int)strlen(fr), fr, (int)strlen(cerror), cerror); | |||
if (req->res->buf_off >= req->res->buf_len) | |||
req->res->buf_off = 0; | |||
exit(1); | |||
} | |||
void | |||
tracker_req(struct torrent *tp, enum tr_event tr_event) | |||
{ | |||
struct tracker_req *req; | |||
btpd_log(BTPD_L_TRACKER, | |||
"request for %s, event: %s.\n", | |||
tp->relpath, event2str(tr_event)); | |||
req = (struct tracker_req *)btpd_calloc(1, sizeof(*req)); | |||
req->res = mmap(NULL, REQ_SIZE, PROT_READ | PROT_WRITE, | |||
MAP_ANON | MAP_SHARED, -1, 0); | |||
if (req->res == MAP_FAILED) { | |||
free(req); | |||
btpd_err("Failed mmap: %s\n", strerror(errno)); | |||
} | |||
req->res->buf_len = REQ_SIZE - sizeof(*req->res); | |||
req->res->buf_off = 0; | |||
req->res->buf = (char *)req->res + sizeof(*req->res); | |||
req->tr_event = tr_event; | |||
bcopy(tp->meta.info_hash, req->info_hash, 20); | |||
fflush(NULL); | |||
req->child.child_done = tracker_done; | |||
TAILQ_INSERT_TAIL(&btpd.kids, &req->child, entry); | |||
req->child.pid = fork(); | |||
if (req->child.pid < 0) { | |||
btpd_err("Couldn't fork (%s).\n", strerror(errno)); | |||
} else if (req->child.pid == 0) { // Child | |||
int nfiles = getdtablesize(); | |||
for (int i = 0; i < nfiles; i++) | |||
close(i); | |||
http_helper(req, tp); | |||
} | |||
} |
@@ -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 |
@@ -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@ |
@@ -0,0 +1,577 @@ | |||
#include <sys/types.h> | |||
#include <err.h> | |||
#include <errno.h> | |||
#include <fcntl.h> | |||
#include <getopt.h> | |||
#include <inttypes.h> | |||
#include <math.h> | |||
#include <stdio.h> | |||
#include <stdlib.h> | |||
#include <string.h> | |||
#include <unistd.h> | |||
#include <openssl/sha.h> | |||
#include "benc.h" | |||
#include "metainfo.h" | |||
#include "stream.h" | |||
#include "subr.h" | |||
#include "btpd_if.h" | |||
static void | |||
usage() | |||
{ | |||
printf("Usage: btcli command [options] [files]\n" | |||
"Commands:\n" | |||
"add <file_1> ... [file_n]\n" | |||
"\tAdd the given torrents to btpd.\n" | |||
"\n" | |||
"del <file_1> ... [file_n]\n" | |||
"\tRemove the given torrents from btpd.\n" | |||
"\n" | |||
"die\n" | |||
"\tShut down btpd.\n" | |||
"\n" | |||
"list\n" | |||
"\tList active torrents.\n" | |||
"\n" | |||
"stat [-i] [-w n] [file_1] ... [file_n]\n" | |||
"\tShow stats for either all active or the given torrents.\n" | |||
"\tThe stats displayed are:\n" | |||
"\t%% of pieces seen, %% of pieces verified, \n" | |||
"\tMB down, rate down, MB up, rate up, no peers\n" | |||
"-i\n" | |||
"\tShow stats per torrent in addition to total stats.\n" | |||
"-w n\n" | |||
"\tRepeat every n seconds.\n" | |||
"\n" | |||
"Common options:\n" | |||
"--ipc key\n" | |||
"\tTalk to the btpd started with the same key.\n" | |||
"\n" | |||
"--help\n" | |||
"\tShow this help.\n" | |||
"\n"); | |||
exit(1); | |||
} | |||
static void | |||
handle_error(int error) | |||
{ | |||
switch (error) { | |||
case 0: | |||
break; | |||
case ENOENT: | |||
case ECONNREFUSED: | |||
errx(1, "Couldn't connect. Check that btpd is running."); | |||
default: | |||
errx(1, "%s", strerror(error)); | |||
} | |||
} | |||
static void | |||
do_ipc_open(char *ipctok, struct ipc **ipc) | |||
{ | |||
switch (ipc_open(ipctok, ipc)) { | |||
case 0: | |||
break; | |||
case EINVAL: | |||
errx(1, "--ipc argument only takes letters and digits."); | |||
case ENAMETOOLONG: | |||
errx(1, "--ipc argument is too long."); | |||
} | |||
} | |||
struct cb { | |||
char *path; | |||
uint8_t *piece_field; | |||
uint32_t have; | |||
struct metainfo *meta; | |||
}; | |||
static void | |||
hash_cb(uint32_t index, uint8_t *hash, void *arg) | |||
{ | |||
struct cb *cb = arg; | |||
if (hash != NULL) | |||
if (bcmp(hash, cb->meta->piece_hash[index], SHA_DIGEST_LENGTH) == 0) { | |||
set_bit(cb->piece_field, index); | |||
cb->have++; | |||
} | |||
printf("\rTested: %5.1f%%", 100.0 * (index + 1) / cb->meta->npieces); | |||
fflush(stdout); | |||
} | |||
static int | |||
fd_cb(const char *path, int *fd, void *arg) | |||
{ | |||
struct cb *fp = arg; | |||
return vopen(fd, O_RDONLY, "%s.d/%s", fp->path, path); | |||
} | |||
static void | |||
gen_ifile(char *path) | |||
{ | |||
int fd; | |||
struct cb cb; | |||
struct metainfo *mi; | |||
size_t field_len; | |||
if ((errno = load_metainfo(path, -1, 1, &mi)) != 0) | |||
err(1, "load_metainfo: %s", path); | |||
field_len = ceil(mi->npieces / 8.0); | |||
cb.path = path; | |||
cb.piece_field = calloc(1, field_len); | |||
cb.have = 0; | |||
cb.meta = mi; | |||
if (cb.piece_field == NULL) | |||
errx(1, "Out of memory.\n"); | |||
if ((errno = bts_hashes(mi, fd_cb, hash_cb, &cb)) != 0) | |||
err(1, "bts_hashes"); | |||
printf("\nHave: %5.1f%%\n", 100.0 * cb.have / cb.meta->npieces); | |||
if ((errno = vopen(&fd, O_WRONLY|O_CREAT, "%s.i", path)) != 0) | |||
err(1, "opening %s.i", path); | |||
if (ftruncate(fd, | |||
field_len + | |||
(off_t)ceil(mi->npieces * mi->piece_length / (double)(1<<17))) < 0) | |||
err(1, "ftruncate: %s", path); | |||
if (write(fd, cb.piece_field, field_len) != field_len) | |||
err(1, "write %s.i", path); | |||
if (close(fd) < 0) | |||
err(1, "close %s.i", path); | |||
clear_metainfo(mi); | |||
free(mi); | |||
} | |||
static struct option add_opts[] = { | |||
{ "ipc", required_argument, NULL, 1 }, | |||
{ "help", required_argument, NULL, 2}, | |||
{NULL, 0, NULL, 0} | |||
}; | |||
static void | |||
do_add(char *ipctok, char **paths, int npaths, char **out) | |||
{ | |||
struct ipc *ipc; | |||
do_ipc_open(ipctok, &ipc); | |||
handle_error(btpd_add(ipc, paths, npaths, out)); | |||
ipc_close(ipc); | |||
} | |||
static void | |||
cmd_add(int argc, char **argv) | |||
{ | |||
int ch; | |||
char *ipctok = NULL; | |||
while ((ch = getopt_long(argc, argv, "", add_opts, NULL)) != -1) { | |||
switch(ch) { | |||
case 1: | |||
ipctok = optarg; | |||
break; | |||
default: | |||
usage(); | |||
} | |||
} | |||
argc -= optind; | |||
argv += optind; | |||
if (argc < 1) | |||
usage(); | |||
for (int i = 0; i < argc; i++) { | |||
int64_t code; | |||
char *res; | |||
int fd; | |||
char *path; | |||
errno = vopen(&fd, O_RDONLY, "%s.i", argv[i]); | |||
if (errno == ENOENT) { | |||
printf("Testing %s for content.\n", argv[i]); | |||
gen_ifile(argv[i]); | |||
} else if (errno != 0) | |||
err(1, "open %s.i", argv[i]); | |||
else | |||
close(fd); | |||
if ((errno = canon_path(argv[i], &path)) != 0) | |||
err(1, "canon_path"); | |||
do_add(ipctok, &path, 1, &res); | |||
free(path); | |||
benc_dget_int64(benc_first(res), "code", &code); | |||
if (code == EEXIST) | |||
printf("btpd already had %s.\n", argv[i]); | |||
else if (code != 0) { | |||
printf("btpd indicates error: %s for %s.\n", | |||
strerror(code), argv[i]); | |||
} | |||
free(res); | |||
} | |||
} | |||
static struct option del_opts[] = { | |||
{ "ipc", required_argument, NULL, 1 }, | |||
{ "help", required_argument, NULL, 2}, | |||
{NULL, 0, NULL, 0} | |||
}; | |||
static void | |||
do_del(char *ipctok, uint8_t (*hashes)[20], int nhashes, char **out) | |||
{ | |||
struct ipc *ipc; | |||
do_ipc_open(ipctok, &ipc); | |||
handle_error(btpd_del(ipc, hashes, nhashes, out)); | |||
ipc_close(ipc); | |||
} | |||
static void | |||
cmd_del(int argc, char **argv) | |||
{ | |||
int ch; | |||
char *ipctok = NULL; | |||
while ((ch = getopt_long(argc, argv, "", del_opts, NULL)) != -1) { | |||
switch(ch) { | |||
case 1: | |||
ipctok = optarg; | |||
break; | |||
default: | |||
usage(); | |||
} | |||
} | |||
argc -= optind; | |||
argv += optind; | |||
if (argc < 1) | |||
usage(); | |||
uint8_t hashes[argc][20]; | |||
char *res; | |||
const char *d; | |||
for (int i = 0; i < argc; i++) { | |||
struct metainfo *mi; | |||
if ((errno = load_metainfo(argv[i], -1, 0, &mi)) != 0) | |||
err(1, "load_metainfo: %s", argv[i]); | |||
bcopy(mi->info_hash, hashes[i], 20); | |||
clear_metainfo(mi); | |||
free(mi); | |||
} | |||
do_del(ipctok, hashes, argc, &res); | |||
d = benc_first(res); | |||
for (int i = 0; i < argc; i++) { | |||
int64_t code; | |||
benc_dget_int64(d, "code", &code); | |||
if (code == ENOENT) | |||
printf("btpd didn't have %s.\n", argv[i]); | |||
else if (code != 0) { | |||
printf("btpd indicates error: %s for %s.\n", | |||
strerror(code), argv[i]); | |||
} | |||
d = benc_next(d); | |||
} | |||
free(res); | |||
} | |||
static struct option die_opts[] = { | |||
{ "ipc", required_argument, NULL, 1 }, | |||
{ "help", no_argument, NULL, 2 }, | |||
{NULL, 0, NULL, 0} | |||
}; | |||
static void | |||
do_die(char *ipctok) | |||
{ | |||
struct ipc *ipc; | |||
do_ipc_open(ipctok, &ipc); | |||
handle_error(btpd_die(ipc)); | |||
ipc_close(ipc); | |||
} | |||
static void | |||
cmd_die(int argc, char **argv) | |||
{ | |||
int ch; | |||
char *ipctok = NULL; | |||
while ((ch = getopt_long(argc, argv, "", die_opts, NULL)) != -1) { | |||
switch (ch) { | |||
case 1: | |||
ipctok = optarg; | |||
break; | |||
default: | |||
usage(); | |||
} | |||
} | |||
do_die(ipctok); | |||
} | |||
static struct option stat_opts[] = { | |||
{ "ipc", required_argument, NULL, 1 }, | |||
{ "help", no_argument, NULL, 2 }, | |||
{NULL, 0, NULL, 0} | |||
}; | |||
static void | |||
do_stat(char *ipctok, char **out) | |||
{ | |||
struct ipc *ipc; | |||
do_ipc_open(ipctok, &ipc); | |||
handle_error(btpd_stat(ipc, out)); | |||
ipc_close(ipc); | |||
} | |||
struct tor { | |||
char *path; | |||
uint8_t hash[20]; | |||
uint64_t down; | |||
uint64_t up; | |||
uint64_t npeers; | |||
uint64_t npieces; | |||
uint64_t have_npieces; | |||
uint64_t seen_npieces; | |||
}; | |||
struct tor **parse_tors(char *res, uint8_t (*hashes)[20], int nhashes) | |||
{ | |||
struct tor **tors; | |||
int64_t num; | |||
const char *p; | |||
benc_dget_int64(res, "ntorrents", &num); | |||
benc_dget_lst(res, "torrents", &p); | |||
tors = calloc(sizeof(*tors), num + 1); | |||
int i = 0; | |||
for (p = benc_first(p); p; p = benc_next(p)) { | |||
int j; | |||
const char *hash; | |||
benc_dget_str(p, "hash", &hash, NULL); | |||
for (j = 0; j < nhashes; j++) { | |||
if (bcmp(hashes[i], hash, 20) == 0) | |||
break; | |||
} | |||
if (j < nhashes || nhashes == 0) { | |||
tors[i] = calloc(sizeof(*tors[i]), 1); | |||
bcopy(hash, tors[i]->hash, 20); | |||
benc_dget_int64(p, "down", &tors[i]->down); | |||
benc_dget_int64(p, "up", &tors[i]->up); | |||
benc_dget_int64(p, "npeers", &tors[i]->npeers); | |||
benc_dget_int64(p, "npieces", &tors[i]->npieces); | |||
benc_dget_int64(p, "have npieces", &tors[i]->have_npieces); | |||
benc_dget_int64(p, "seen npieces", &tors[i]->seen_npieces); | |||
benc_dget_strz(p, "path", &tors[i]->path, NULL); | |||
i++; | |||
} | |||
} | |||
return tors; | |||
} | |||
static void | |||
free_tors(struct tor **tors) | |||
{ | |||
for (int i = 0; tors[i] != NULL; i++) { | |||
free(tors[i]->path); | |||
free(tors[i]); | |||
} | |||
free(tors); | |||
} | |||
static void | |||
print_stat(struct tor *cur, struct tor *old, int wait) | |||
{ | |||
if (old == NULL) { | |||
printf("%5.1f%% %5.1f%% %6.1fM - kB/s %6.1fM - kB/s %4u\n", | |||
100 * cur->seen_npieces / (double)cur->npieces, | |||
100 * cur->have_npieces / (double)cur->npieces, | |||
cur->down / (double)(1 << 20), | |||
cur->up / (double)(1 << 20), | |||
(unsigned)cur->npeers); | |||
} else { | |||
printf("%5.1f%% %5.1f%% %6.1fM %7.2fkB/s %6.1fM %7.2fkB/s %4u\n", | |||
100 * cur->seen_npieces / (double)cur->npieces, | |||
100 * cur->have_npieces / (double)cur->npieces, | |||
cur->down / (double)(1 << 20), | |||
(cur->down - old->down) / (double)wait / (double)(1 << 10), | |||
cur->up / (double)(1 << 20), | |||
(cur->up - old->up) / (double)wait / (double)(1 << 10), | |||
(unsigned)cur->npeers | |||
); | |||
} | |||
} | |||
static void | |||
grok_stat(char *ipctok, int iflag, int wait, | |||
uint8_t (*hashes)[20], int nhashes) | |||
{ | |||
int i, j; | |||
char *res; | |||
struct tor **cur, **old = NULL; | |||
struct tor curtot, oldtot; | |||
again: | |||
do_stat(ipctok, &res); | |||
cur = parse_tors(res, hashes, nhashes); | |||
free(res); | |||
if (iflag) { | |||
for (i = 0; cur[i] != NULL; i++) { | |||
if (old == NULL) { | |||
printf("%s:\n", rindex(cur[i]->path, '/') + 1); | |||
print_stat(cur[i], NULL, wait); | |||
} else { | |||
for (j = 0; old[j] != NULL; j++) | |||
if (bcmp(cur[i]->hash, old[j]->hash, 20) == 0) | |||
break; | |||
printf("%s:\n", rindex(cur[i]->path, '/') + 1); | |||
print_stat(cur[i], old[j], wait); | |||
} | |||
} | |||
} | |||
bzero(&curtot, sizeof(curtot)); | |||
for (i = 0; cur[i] != NULL; i++) { | |||
curtot.down += cur[i]->down; | |||
curtot.up += cur[i]->up; | |||
curtot.npeers += cur[i]->npeers; | |||
curtot.npieces += cur[i]->npieces; | |||
curtot.have_npieces += cur[i]->have_npieces; | |||
curtot.seen_npieces += cur[i]->seen_npieces; | |||
} | |||
if (iflag) | |||
printf("Total:\n"); | |||
if (old != NULL) | |||
print_stat(&curtot, &oldtot, wait); | |||
else | |||
print_stat(&curtot, NULL, wait); | |||
if (wait) { | |||
if (old != NULL) | |||
free_tors(old); | |||
old = cur; | |||
oldtot = curtot; | |||
sleep(wait); | |||
goto again; | |||
} | |||
free_tors(cur); | |||
} | |||
static void | |||
cmd_stat(int argc, char **argv) | |||
{ | |||
int ch; | |||
char *ipctok = NULL; | |||
int wait = 0; | |||
int iflag = 0; | |||
while ((ch = getopt_long(argc, argv, "iw:", stat_opts, NULL)) != -1) { | |||
switch (ch) { | |||
case 'i': | |||
iflag = 1; | |||
break; | |||
case 'w': | |||
wait = atoi(optarg); | |||
if (wait <= 0) | |||
errx(1, "-w argument must be an integer > 0."); | |||
break; | |||
case 1: | |||
ipctok = optarg; | |||
break; | |||
default: | |||
usage(); | |||
} | |||
} | |||
argc -= optind; | |||
argv += optind; | |||
if (argc > 0) { | |||
uint8_t hashes[argc][20]; | |||
for (int i = 0; i < argc; i++) { | |||
struct metainfo *mi; | |||
if ((errno = load_metainfo(argv[i], -1, 0, &mi)) != 0) | |||
err(1, "load_metainfo: %s", argv[i]); | |||
bcopy(mi->info_hash, hashes[i], 20); | |||
clear_metainfo(mi); | |||
free(mi); | |||
} | |||
grok_stat(ipctok, iflag, wait, hashes, argc); | |||
} else | |||
grok_stat(ipctok, iflag, wait, NULL, 0); | |||
} | |||
static struct option list_opts[] = { | |||
{ "ipc", required_argument, NULL, 1 }, | |||
{ "help", no_argument, NULL, 2 }, | |||
{NULL, 0, NULL, 0} | |||
}; | |||
static void | |||
cmd_list(int argc, char **argv) | |||
{ | |||
int ch; | |||
char *ipctok = NULL; | |||
while ((ch = getopt_long(argc, argv, "", list_opts, NULL)) != -1) { | |||
switch (ch) { | |||
case 1: | |||
ipctok = optarg; | |||
break; | |||
default: | |||
usage(); | |||
} | |||
} | |||
char *res; | |||
const char *p; | |||
char *path; | |||
do_stat(ipctok, &res); | |||
benc_dget_lst(res, "torrents", &p); | |||
int count = 0; | |||
for (p = benc_first(p); p; p = benc_next(p)) { | |||
count++; | |||
benc_dget_strz(p, "path", &path, NULL); | |||
printf("%s\n", path); | |||
free(path); | |||
} | |||
printf("%d torrents.\n", count); | |||
} | |||
static struct { | |||
const char *name; | |||
void (*fun)(int, char **); | |||
} cmd_table[] = { | |||
{ "add", cmd_add }, | |||
{ "del", cmd_del }, | |||
{ "die", cmd_die }, | |||
{ "list", cmd_list}, | |||
{ "stat", cmd_stat } | |||
}; | |||
static int ncmds = sizeof(cmd_table) / sizeof(cmd_table[0]); | |||
int | |||
main(int argc, char **argv) | |||
{ | |||
if (argc < 2) | |||
usage(); | |||
int found = 0; | |||
for (int i = 0; !found && i < ncmds; i++) { | |||
if (strcmp(argv[1], cmd_table[i].name) == 0) { | |||
found = 1; | |||
cmd_table[i].fun(argc - 1, argv + 1); | |||
} | |||
} | |||
if (!found) | |||
usage(); | |||
return 0; | |||
} |
@@ -0,0 +1,53 @@ | |||
#include <sys/types.h> | |||
#include <err.h> | |||
#include <errno.h> | |||
#include <getopt.h> | |||
#include <inttypes.h> | |||
#include <stdio.h> | |||
#include <stdlib.h> | |||
#include "metainfo.h" | |||
static void | |||
usage() | |||
{ | |||
fprintf(stderr, "Usage: btinfo file ...\n\n"); | |||
exit(1); | |||
} | |||
static struct option longopts[] = { | |||
{ "help", no_argument, NULL, 1 }, | |||
{ NULL, 0, NULL, 0 } | |||
}; | |||
int | |||
main(int argc, char **argv) | |||
{ | |||
int ch; | |||
while ((ch = getopt_long(argc, argv, "", longopts, NULL)) != -1) | |||
usage(); | |||
argc -= optind; | |||
argv += optind; | |||
if (argc < 1) | |||
usage(); | |||
while (argc > 0) { | |||
struct metainfo *mi; | |||
if ((errno = load_metainfo(*argv, -1, 1, &mi)) != 0) | |||
err(1, "load_metainfo: %s", *argv); | |||
print_metainfo(mi); | |||
clear_metainfo(mi); | |||
free(mi); | |||
argc--; | |||
argv++; | |||
} | |||
return 0; | |||
} |
@@ -0,0 +1,221 @@ | |||
#include <ctype.h> | |||
#include <err.h> | |||
#include <errno.h> | |||
#include <inttypes.h> | |||
#include <stdio.h> | |||
#include <stdlib.h> | |||
#include <string.h> | |||
#include <unistd.h> | |||
#include "benc.h" | |||
#include "iobuf.h" | |||
#include "btpd_if.h" | |||
int | |||
ipc_open(const char *key, struct ipc **out) | |||
{ | |||
size_t plen; | |||
size_t keylen; | |||
struct ipc *res; | |||
if (key == NULL) | |||
key = "default"; | |||
keylen = strlen(key); | |||
for (int i = 0; i < keylen; i++) | |||
if (!isalnum(key[i])) | |||
return EINVAL; | |||
res = malloc(sizeof(*res)); | |||
if (res == NULL) | |||
return ENOMEM; | |||
plen = sizeof(res->addr.sun_path); | |||
if (snprintf(res->addr.sun_path, plen, | |||
"/tmp/btpd_%u_%s", geteuid(), key) >= plen) { | |||
free(res); | |||
return ENAMETOOLONG; | |||
} | |||
res->addr.sun_family = AF_UNIX; | |||
*out = res; | |||
return 0; | |||
} | |||
int | |||
ipc_close(struct ipc *ipc) | |||
{ | |||
free(ipc); | |||
return 0; | |||
} | |||
static int | |||
ipc_connect(struct ipc *ipc, FILE **out) | |||
{ | |||
FILE *fp; | |||
int sd; | |||
int error; | |||
if ((sd = socket(PF_UNIX, SOCK_STREAM, 0)) == -1) | |||
return errno; | |||
if (connect(sd, (struct sockaddr *)&ipc->addr, sizeof(ipc->addr)) == -1) | |||
goto error; | |||
if ((fp = fdopen(sd, "r+")) == NULL) | |||
goto error; | |||
*out = fp; | |||
return 0; | |||
error: | |||
error = errno; | |||
close(sd); | |||
return error; | |||
} | |||
static int | |||
ipc_response(FILE *fp, char **out, uint32_t *len) | |||
{ | |||
uint32_t size; | |||
char *buf; | |||
if (fread(&size, sizeof(size), 1, fp) != 1) { | |||
if (ferror(fp)) | |||
return errno; | |||
else | |||
return ECONNRESET; | |||
} | |||
if (size == 0) | |||
return EINVAL; | |||
if ((buf = malloc(size)) == NULL) | |||
return ENOMEM; | |||
if (fread(buf, 1, size, fp) != size) { | |||
if (ferror(fp)) | |||
return errno; | |||
else | |||
return ECONNRESET; | |||
} | |||
*out = buf; | |||
*len = size; | |||
return 0; | |||
} | |||
static int | |||
ipc_req_res(struct ipc *ipc, | |||
const char *req, uint32_t qlen, | |||
char **res, uint32_t *rlen) | |||
{ | |||
FILE *fp; | |||
int error; | |||
if ((error = ipc_connect(ipc, &fp)) != 0) | |||
return error; | |||
if (fwrite(&qlen, sizeof(qlen), 1, fp) != 1) | |||
goto error; | |||
if (fwrite(req, 1, qlen, fp) != qlen) | |||
goto error; | |||
if (fflush(fp) != 0) | |||
goto error; | |||
if ((errno = ipc_response(fp, res, rlen)) != 0) | |||
goto error; | |||
if ((errno = benc_validate(*res, *rlen)) != 0) | |||
goto error; | |||
fclose(fp); | |||
return 0; | |||
error: | |||
error = errno; | |||
fclose(fp); | |||
return error; | |||
} | |||
int | |||
btpd_die(struct ipc *ipc) | |||
{ | |||
int error; | |||
char *response = NULL; | |||
const char shutdown[] = "l3:diee"; | |||
uint32_t size = sizeof(shutdown) - 1; | |||
uint32_t rsiz; | |||
if ((error = ipc_req_res(ipc, shutdown, size, &response, &rsiz)) != 0) | |||
return error; | |||
error = benc_validate(response, rsiz); | |||
if (error == 0) { | |||
int64_t tmp; | |||
benc_dget_int64(response, "code", &tmp); | |||
error = tmp; | |||
} | |||
free(response); | |||
return error; | |||
} | |||
int | |||
btpd_add(struct ipc *ipc, char **paths, unsigned npaths, char **out) | |||
{ | |||
int error; | |||
struct io_buffer iob; | |||
char *res = NULL; | |||
uint32_t reslen; | |||
buf_init(&iob, 1024); | |||
buf_print(&iob, "l3:add"); | |||
for (unsigned i = 0; i < npaths; i++) { | |||
int plen = strlen(paths[i]); | |||
buf_print(&iob, "%d:", plen); | |||
buf_write(&iob, paths[i], plen); | |||
} | |||
buf_print(&iob, "e"); | |||
error = ipc_req_res(ipc, iob.buf, iob.buf_off, &res, &reslen); | |||
free(iob.buf); | |||
if (error == 0) | |||
*out = res; | |||
return error; | |||
} | |||
int | |||
btpd_stat(struct ipc *ipc, char **out) | |||
{ | |||
const char cmd[] = "l4:state"; | |||
uint32_t cmdlen = sizeof(cmd) - 1; | |||
char *res; | |||
uint32_t reslen; | |||
if ((errno = ipc_req_res(ipc, cmd, cmdlen, &res, &reslen)) != 0) | |||
return errno; | |||
*out = res; | |||
return 0; | |||
} | |||
int | |||
btpd_del(struct ipc *ipc, uint8_t (*hash)[20], unsigned nhashes, char **out) | |||
{ | |||
int error; | |||
struct io_buffer iob; | |||
char *res = NULL; | |||
uint32_t reslen; | |||
buf_init(&iob, 1024); | |||
buf_write(&iob, "l3:del", 6); | |||
for (unsigned i = 0; i < nhashes; i++) { | |||
buf_write(&iob, "20:", 3); | |||
buf_write(&iob, hash[i], 20); | |||
} | |||
buf_write(&iob, "e", 1); | |||
error = ipc_req_res(ipc, iob.buf, iob.buf_off, &res, &reslen); | |||
free(iob.buf); | |||
if (error != 0) | |||
return error; | |||
*out = res; | |||
return 0; | |||
} |
@@ -0,0 +1,21 @@ | |||
#ifndef BTPD_IF_H | |||
#define BTPD_IF_H | |||
#include <sys/types.h> | |||
#include <sys/socket.h> | |||
#include <sys/un.h> | |||
struct ipc { | |||
struct sockaddr_un addr; | |||
}; | |||
int ipc_open(const char *key, struct ipc **out); | |||
int ipc_close(struct ipc *ipc); | |||
int btpd_add(struct ipc *ipc, char **path, unsigned npaths, char **out); | |||
int btpd_del(struct ipc *ipc, uint8_t (*hash)[20], | |||
unsigned nhashes, char **out); | |||
int btpd_die(struct ipc *ipc); | |||
int btpd_stat(struct ipc *ipc, char **out); | |||
#endif |
@@ -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 |
@@ -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 |
@@ -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@ |
@@ -0,0 +1,345 @@ | |||
#include <assert.h> | |||
#include <ctype.h> | |||
#include <errno.h> | |||
#include <inttypes.h> | |||
#include <stdlib.h> | |||
#include <string.h> | |||
#include "benc.h" | |||
#define benc_safeset(out, val) if ((out) != NULL) *(out) = (val) | |||
static const char *benc_validate_aux(const char *p, const char *end); | |||
int | |||
benc_validate(const char *p, size_t len) | |||
{ | |||
const char *end = p + len - 1; | |||
if (len <= 0) | |||
return EINVAL; | |||
return benc_validate_aux(p, end) == end ? 0 : EINVAL; | |||
} | |||
static const char * | |||
benc_validate_aux(const char *p, const char *end) | |||
{ | |||
size_t d = 0; | |||
switch (*p) { | |||
case 'd': | |||
d = 1; | |||
case 'l': | |||
for (p++; p <= end && *p != 'e'; p++) { | |||
if (d != 0) { | |||
if (d % 2 == 1 && !isdigit(*p)) | |||
return NULL; | |||
else | |||
d++; | |||
} | |||
if ((p = benc_validate_aux(p, end)) == NULL) | |||
return NULL; | |||
} | |||
if (p > end || (d != 0 && d % 2 != 1)) | |||
return NULL; | |||
break; | |||
case 'i': | |||
p++; | |||
if (p > end) | |||
return NULL; | |||
if (*p == '-') | |||
p++; | |||
if (p > end || !isdigit(*p)) | |||
return NULL; | |||
p++; | |||
while (p <= end && isdigit(*p)) | |||
p++; | |||
if (p > end || *p != 'e') | |||
return NULL; | |||
break; | |||
default: | |||
if (isdigit(*p)) { | |||
size_t len = 0; | |||
while (p <= end && isdigit(*p)) { | |||
len *= 10; | |||
len += *p - '0'; | |||
p++; | |||
} | |||
if (p <= end && *p == ':' && p + len <= end) | |||
p += len; | |||
else | |||
return NULL; | |||
} | |||
else | |||
return NULL; | |||
break; | |||
} | |||
return p; | |||
} | |||
size_t | |||
benc_length(const char *p) | |||
{ | |||
size_t blen; | |||
const char *next; | |||
switch (*p) { | |||
case 'd': | |||
case 'l': | |||
blen = 2; // [l|d]...e | |||
next = benc_first(p); | |||
while (*next != 'e') { | |||
size_t len = benc_length(next); | |||
blen += len; | |||
next += len; | |||
} | |||
return blen; | |||
case 'i': | |||
for (next = p + 1; *next != 'e'; next++) | |||
; | |||
return next - p + 1; | |||
default: | |||
assert(benc_str(p, &next, &blen, NULL) == 0); | |||
return next - p + blen; | |||
} | |||
} | |||
size_t | |||
benc_nelems(const char *p) | |||
{ | |||
size_t nelems = 0; | |||
for (p = benc_first(p); p != NULL; p = benc_next(p)) | |||
nelems++; | |||
return nelems; | |||
} | |||
const char * | |||
benc_first(const char *p) | |||
{ | |||
assert(benc_islst(p)); | |||
return *(p + 1) == 'e' ? NULL : p + 1; | |||
} | |||
const char * | |||
benc_next(const char *p) | |||
{ | |||
size_t blen = benc_length(p); | |||
return *(p + blen) == 'e' ? NULL : p + blen; | |||
} | |||
int | |||
benc_str(const char *p, const char **out, size_t *len, const char**next) | |||
{ | |||
size_t blen = 0; | |||
assert(isdigit(*p)); | |||
blen = *p - '0'; | |||
p++; | |||
while (isdigit(*p)) { | |||
blen *= 10; | |||
blen += *p - '0'; | |||
p++; | |||
} | |||
assert(*p == ':'); | |||
benc_safeset(len, blen); | |||
benc_safeset(out, p + 1); | |||
benc_safeset(next, *(p + blen + 1) == 'e' ? NULL : p + blen + 1); | |||
return 0; | |||
} | |||
int | |||
benc_strz(const char *p, char **out, size_t *len, const char **next) | |||
{ | |||
int err; | |||
size_t blen; | |||
const char *bstr; | |||
if ((err = benc_str(p, &bstr, &blen, next)) == 0) { | |||
if ((*out = malloc(blen + 1)) != NULL) { | |||
memcpy(*out, bstr, blen); | |||
(*out)[blen] = '\0'; | |||
benc_safeset(len, blen); | |||
} else | |||
err = ENOMEM; | |||
} | |||
return err; | |||
} | |||
int | |||
benc_stra(const char *p, char **out, size_t *len, const char **next) | |||
{ | |||
int err; | |||
size_t blen; | |||
const char *bstr; | |||
if ((err = benc_str(p, &bstr, &blen, next)) == 0) { | |||
if ((*out = malloc(blen)) != NULL) { | |||
memcpy(*out, bstr, blen); | |||
benc_safeset(len, blen); | |||
} else | |||
err = ENOMEM; | |||
} | |||
return err; | |||
} | |||
int | |||
benc_int64(const char *p, int64_t *out, const char **next) | |||
{ | |||
int sign = 1; | |||
int64_t res = 0; | |||
assert(*p == 'i'); | |||
p++; | |||
if (*p == '-') { | |||
sign = -1; | |||
p++; | |||
} | |||
assert(isdigit(*p)); | |||
res += sign * (*p - '0'); | |||
p++; | |||
while (isdigit(*p)) { | |||
res *= sign * 10; | |||
res += sign * (*p - '0'); | |||
p++; | |||
} | |||
assert(*p == 'e'); | |||
benc_safeset(out, res); | |||
benc_safeset(next, *(p + 1) == 'e' ? NULL : p + 1); | |||
return 0; | |||
} | |||
int | |||
benc_uint32(const char *p, uint32_t *out, const char **next) | |||
{ | |||
int err; | |||
int64_t res; | |||
if ((err = benc_int64(p, &res, next)) == 0) { | |||
if (res >= 0 && res <= 0xffffffffUL) | |||
*out = (uint32_t)res; | |||
else | |||
err = EINVAL; | |||
} | |||
return err; | |||
} | |||
int | |||
benc_dget_any(const char *p, const char *key, const char **val) | |||
{ | |||
int res; | |||
size_t len, blen; | |||
const char *bstr; | |||
assert(benc_isdct(p)); | |||
len = strlen(key); | |||
p = benc_first(p); | |||
while (p != NULL) { | |||
if ((res = benc_str(p, &bstr, &blen, &p)) != 0) | |||
return res; | |||
res = strncmp(bstr, key, blen); | |||
if (res == 0 && len == blen) { | |||
*val = p; | |||
return 0; | |||
} else if (res <= 0) { | |||
p = benc_next(p); | |||
} else | |||
return ENOENT; | |||
} | |||
return ENOENT; | |||
} | |||
int | |||
benc_dget_lst(const char *p, const char *key, const char **val) | |||
{ | |||
int err; | |||
if ((err = benc_dget_any(p, key, val)) == 0) | |||
if (!benc_islst(*val)) | |||
err = EINVAL; | |||
return err; | |||
} | |||
int | |||
benc_dget_dct(const char *p, const char *key, const char **val) | |||
{ | |||
int err; | |||
if ((err = benc_dget_any(p, key, val)) == 0) | |||
if (!benc_isdct(*val)) | |||
err = EINVAL; | |||
return err; | |||
} | |||
int | |||
benc_dget_str(const char *p, const char *key, const char **val, size_t *len) | |||
{ | |||
int err; | |||
const char *sp; | |||
if ((err = benc_dget_any(p, key, &sp)) == 0) | |||
err = benc_isstr(sp) ? benc_str(sp, val, len, NULL) : EINVAL; | |||
return err; | |||
} | |||
int | |||
benc_dget_stra(const char *p, const char *key, char **val, size_t *len) | |||
{ | |||
int err; | |||
const char *sp; | |||
if ((err = benc_dget_any(p, key, &sp)) == 0) | |||
err = benc_isstr(sp) ? benc_stra(sp, val, len, NULL) : EINVAL; | |||
return err; | |||
} | |||
int | |||
benc_dget_strz(const char *p, const char *key, char **val, size_t *len) | |||
{ | |||
int err; | |||
const char *sp; | |||
if ((err = benc_dget_any(p, key, &sp)) == 0) | |||
err = benc_isstr(sp) ? benc_strz(sp, val, len, NULL) : EINVAL; | |||
return err; | |||
} | |||
int | |||
benc_dget_int64(const char *p, const char *key, int64_t *val) | |||
{ | |||
int err; | |||
const char *ip; | |||
if ((err = benc_dget_any(p, key, &ip)) == 0) | |||
err = benc_isint(ip) ? benc_int64(ip, val, NULL) : EINVAL; | |||
return err; | |||
} | |||
int | |||
benc_dget_uint32(const char *p, const char *key, uint32_t *val) | |||
{ | |||
int err; | |||
const char *ip; | |||
if ((err = benc_dget_any(p, key, &ip)) == 0) | |||
err = benc_isint(ip) ? benc_uint32(ip, val, NULL) : EINVAL; | |||
return err; | |||
} | |||
int | |||
benc_islst(const char *p) | |||
{ | |||
return *p == 'l' || *p == 'd'; | |||
} | |||
int | |||
benc_isdct(const char *p) | |||
{ | |||
return *p == 'd'; | |||
} | |||
int | |||
benc_isint(const char *p) | |||
{ | |||
return *p == 'i'; | |||
} | |||
int | |||
benc_isstr(const char *p) | |||
{ | |||
return isdigit(*p); | |||
} |
@@ -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 |
@@ -0,0 +1,64 @@ | |||
#include <errno.h> | |||
#include <inttypes.h> | |||
#include <stdarg.h> | |||
#include <stdio.h> | |||
#include <stdlib.h> | |||
#include <string.h> | |||
#include "iobuf.h" | |||
#define GROWLEN (1 << 14) | |||
int | |||
buf_init(struct io_buffer *iob, size_t size) | |||
{ | |||
iob->buf_off = 0; | |||
iob->buf_len = size; | |||
iob->buf = malloc(size); | |||
if (iob->buf == NULL) | |||
return ENOMEM; | |||
else | |||
return 0; | |||
} | |||
int | |||
buf_grow(struct io_buffer *iob, size_t addlen) | |||
{ | |||
char *nbuf = realloc(iob->buf, iob->buf_len + addlen); | |||
if (nbuf == NULL) | |||
return ENOMEM; | |||
else { | |||
iob->buf = nbuf; | |||
iob->buf_len += addlen; | |||
return 0; | |||
} | |||
} | |||
int | |||
buf_print(struct io_buffer *iob, const char *fmt, ...) | |||
{ | |||
int np; | |||
va_list ap; | |||
va_start(ap, fmt); | |||
np = vsnprintf(NULL, 0, fmt, ap); | |||
va_end(ap); | |||
while (np + 1 > iob->buf_len - iob->buf_off) | |||
if (buf_grow(iob, GROWLEN) != 0) | |||
return ENOMEM; | |||
va_start(ap, fmt); | |||
vsnprintf(iob->buf + iob->buf_off, np + 1, fmt, ap); | |||
va_end(ap); | |||
iob->buf_off += np; | |||
return 0; | |||
} | |||
int | |||
buf_write(struct io_buffer *iob, const void *buf, size_t len) | |||
{ | |||
while (iob->buf_len - iob->buf_off < len) | |||
if (buf_grow(iob, GROWLEN) != 0) | |||
return ENOMEM; | |||
bcopy(buf, iob->buf + iob->buf_off, len); | |||
iob->buf_off += len; | |||
return 0; | |||
} |
@@ -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 |
@@ -0,0 +1,275 @@ | |||
#include <sys/types.h> | |||
#include <sys/mman.h> | |||
#include <sys/stat.h> | |||
#include <errno.h> | |||
#include <fcntl.h> | |||
#include <inttypes.h> | |||
#include <stdio.h> | |||
#include <stdlib.h> | |||
#include <string.h> | |||
#include <unistd.h> | |||
#include <openssl/sha.h> | |||
#include "benc.h" | |||
#include "metainfo.h" | |||
#include "subr.h" | |||
/* | |||
* d | |||
* announce = url | |||
* info = d | |||
* name = advisory file/dir save name | |||
* piece length = power of two length of each block | |||
* pieces = 20b of sha1-hash * num of pieces | |||
* length = length of file in bytes in single file download | |||
* files = l d | |||
* length = length of file in bytes | |||
* path = l path components | |||
* | |||
*/ | |||
#ifndef PRId64 | |||
#define PRId64 "lld" | |||
#endif | |||
#ifndef PRIu32 | |||
#define PRIu32 "u" | |||
#endif | |||
void | |||
print_metainfo(struct metainfo *tp) | |||
{ | |||
unsigned i; | |||
printf("Info hash: "); | |||
for (i = 0; i < 20; i++) | |||
printf("%.2x", tp->info_hash[i]); | |||
printf("\n"); | |||
printf("Tracker URL: %s\n", tp->announce); | |||
printf("Piece length: %" PRId64 "\n", (int64_t)tp->piece_length); | |||
printf("Number of pieces: %" PRIu32 "\n", tp->npieces); | |||
printf("Number of files: %u\n", tp->nfiles); | |||
printf("Advisory name: %s\n", tp->name); | |||
printf("Files:\n"); | |||
for (i = 0; i < tp->nfiles; i++) { | |||
printf("%s (%" PRId64 ")\n", | |||
tp->files[i].path, (int64_t)tp->files[i].length); | |||
} | |||
printf("Total length: %" PRId64 "\n\n", (int64_t)tp->total_length); | |||
} | |||
static int | |||
check_path(const char *path, size_t len) | |||
{ | |||
if (len == 0) | |||
return 0; | |||
else if (len == 1 && path[0] == '.') | |||
return 0; | |||
else if (len == 2 && path[0] == '.' && path[1] == '.') | |||
return 0; | |||
else if (memchr(path, '/', len) != NULL) | |||
return 0; | |||
return 1; | |||
} | |||
int | |||
fill_fileinfo(const char *fdct, struct fileinfo *tfp) | |||
{ | |||
int err; | |||
size_t npath, plen, len; | |||
const char *plst, *iter, *str; | |||
if ((err = benc_dget_off(fdct, "length", &tfp->length)) != 0) | |||
return err; | |||
if ((err = benc_dget_lst(fdct, "path", &plst)) != 0) | |||
return err; | |||
npath = plen = 0; | |||
iter = benc_first(plst); | |||
while (iter != NULL) { | |||
if (!benc_isstr(iter)) | |||
return EINVAL; | |||
benc_str(iter, &str, &len, &iter); | |||
if (!check_path(str, len)) | |||
return EINVAL; | |||
npath++; | |||
plen += len; | |||
} | |||
if (npath == 0) | |||
return EINVAL; | |||
if ((tfp->path = malloc(plen + (npath - 1) + 1)) == NULL) | |||
return ENOMEM; | |||
iter = benc_first(plst); | |||
benc_str(iter, &str, &len, &iter); | |||
memcpy(tfp->path, str, len); | |||
plen = len; | |||
npath--; | |||
while (npath > 0) { | |||
tfp->path[plen++] = '/'; | |||
benc_str(iter, &str, &len, &iter); | |||
memcpy(tfp->path + plen, str, len); | |||
plen += len; | |||
npath--; | |||
} | |||
tfp->path[plen] = '\0'; | |||
return 0; | |||
} | |||
void | |||
clear_metainfo(struct metainfo *mip) | |||
{ | |||
int i; | |||
if (mip->piece_hash != NULL) | |||
free(mip->piece_hash); | |||
if (mip->announce != NULL) | |||
free(mip->announce); | |||
if (mip->files != NULL) { | |||
for (i = 0; i < mip->nfiles; i++) { | |||
if (mip->files[i].path != NULL) | |||
free(mip->files[i].path); | |||
} | |||
free(mip->files); | |||
} | |||
if (mip->name != NULL) | |||
free(mip->name); | |||
} | |||
int | |||
fill_metainfo(const char *bep, struct metainfo *tp, int mem_hashes) | |||
{ | |||
size_t len; | |||
int err; | |||
const char *base_addr = bep; | |||
const char *hash_addr; | |||
if (!benc_isdct(bep)) | |||
return EINVAL; | |||
if ((err = benc_dget_strz(bep, "announce", &tp->announce, NULL)) != 0) | |||
goto out; | |||
if ((err = benc_dget_dct(bep, "info", &bep)) != 0) | |||
goto out; | |||
SHA1(bep, benc_length(bep), tp->info_hash); | |||
if ((err = benc_dget_off(bep, "piece length", &tp->piece_length)) != 0) | |||
goto out; | |||
if ((err = benc_dget_str(bep, "pieces", &hash_addr, &len)) != 0) | |||
goto out; | |||
if (len % 20 != 0) { | |||
err = EINVAL; | |||
goto out; | |||
} | |||
tp->npieces = len / 20; | |||
tp->pieces_off = hash_addr - base_addr; | |||
if (mem_hashes) { | |||
if ((tp->piece_hash = malloc(len)) == NULL) { | |||
err = ENOMEM; | |||
goto out; | |||
} | |||
bcopy(hash_addr, tp->piece_hash, len); | |||
} | |||
if ((err = benc_dget_strz(bep, "name", &tp->name, NULL)) != 0) | |||
goto out; | |||
err = benc_dget_off(bep, "length", &tp->total_length); | |||
if (err == 0) { | |||
tp->nfiles = 1; | |||
tp->files = calloc(1, sizeof(struct fileinfo)); | |||
if (tp->files != NULL) { | |||
tp->files[0].length = tp->total_length; | |||
tp->files[0].path = strdup(tp->name); | |||
if (tp->files[0].path == NULL) { | |||
err = ENOMEM; | |||
goto out; | |||
} | |||
} else { | |||
err = ENOMEM; | |||
goto out; | |||
} | |||
} | |||
else if (err == ENOENT) { | |||
int i; | |||
const char *flst, *fdct; | |||
if ((err = benc_dget_lst(bep, "files", &flst)) != 0) | |||
goto out; | |||
tp->nfiles = benc_nelems(flst); | |||
if (tp->nfiles < 1) { | |||
err = EINVAL; | |||
goto out; | |||
} | |||
tp->files = calloc(tp->nfiles, sizeof(struct fileinfo)); | |||
tp->total_length = 0; | |||
i = 0; | |||
for (fdct = benc_first(flst); fdct != NULL; fdct = benc_next(fdct)) { | |||
if (!benc_isdct(fdct)) { | |||
err = EINVAL; | |||
goto out; | |||
} | |||
if ((err = fill_fileinfo(fdct, &tp->files[i])) != 0) | |||
goto out; | |||
tp->total_length += tp->files[i].length; | |||
i++; | |||
} | |||
} | |||
else | |||
goto out; | |||
out: | |||
if (err != 0) | |||
clear_metainfo(tp); | |||
return err; | |||
} | |||
int | |||
load_metainfo(const char *path, off_t size, int mem_hashes, | |||
struct metainfo **res) | |||
{ | |||
char *buf; | |||
int fd, err = 0; | |||
if ((fd = open(path, O_RDONLY)) == -1) | |||
return errno; | |||
if (size <= 0) { | |||
struct stat sb; | |||
if (fstat(fd, &sb) == -1) { | |||
close(fd); | |||
return errno; | |||
} else | |||
size = sb.st_size; | |||
} | |||
if ((buf = mmap(0, size, PROT_READ, MAP_PRIVATE, fd, 0)) == MAP_FAILED) | |||
err = errno; | |||
close(fd); | |||
if (err == 0) | |||
err = benc_validate(buf, size); | |||
if (err == 0) | |||
if ((*res = calloc(1, sizeof(**res))) == NULL) | |||
err = ENOMEM; | |||
if (err == 0) | |||
if ((err = fill_metainfo(buf, *res, mem_hashes)) != 0) | |||
free(*res); | |||
munmap(buf, size); | |||
return err; | |||
} |
@@ -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 |
@@ -0,0 +1,241 @@ | |||
#include <assert.h> | |||
#include <errno.h> | |||
#include <inttypes.h> | |||
#include <fcntl.h> | |||
#include <stdlib.h> | |||
#include <unistd.h> | |||
#include <openssl/sha.h> | |||
#include "metainfo.h" | |||
#include "subr.h" | |||
#include "stream.h" | |||
struct bt_stream_ro * | |||
bts_open_ro(struct metainfo *meta, off_t off, F_fdcb fd_cb, void *fd_arg) | |||
{ | |||
struct bt_stream_ro *bts = malloc(sizeof(*bts)); | |||
if (bts == NULL) | |||
return NULL; | |||
bts->meta = meta; | |||
bts->fd_cb = fd_cb; | |||
bts->fd_arg = fd_arg; | |||
bts->t_off = 0; | |||
bts->f_off = 0; | |||
bts->index = 0; | |||
bts->fd = -1; | |||
bts_seek_ro(bts, off); | |||
return bts; | |||
} | |||
void | |||
bts_seek_ro(struct bt_stream_ro *bts, off_t off) | |||
{ | |||
struct fileinfo *files = bts->meta->files; | |||
assert(off >= 0 && off <= bts->meta->total_length); | |||
if (bts->fd != -1) { | |||
close(bts->fd); | |||
bts->fd = -1; | |||
} | |||
bts->t_off = off; | |||
bts->index = 0; | |||
while (off >= files[bts->index].length) { | |||
off -= files[bts->index].length; | |||
bts->index++; | |||
} | |||
bts->f_off = off; | |||
} | |||
int | |||
bts_read_ro(struct bt_stream_ro *bts, char *buf, size_t len) | |||
{ | |||
struct fileinfo *files = bts->meta->files; | |||
size_t boff, wantread; | |||
ssize_t didread; | |||
assert(bts->t_off + len <= bts->meta->total_length); | |||
boff = 0; | |||
while (boff < len) { | |||
if (bts->fd == -1) { | |||
int err = | |||
bts->fd_cb(files[bts->index].path, &bts->fd, bts->fd_arg); | |||
if (err != 0) | |||
return err; | |||
if (bts->f_off != 0) | |||
lseek(bts->fd, bts->f_off, SEEK_SET); | |||
} | |||
wantread = min(len - boff, files[bts->index].length - bts->f_off); | |||
again: | |||
didread = read(bts->fd, buf + boff, wantread); | |||
if (didread == -1) { | |||
if (errno == EINTR) | |||
goto again; | |||
else | |||
return errno; | |||
} | |||
boff += didread; | |||
bts->f_off += didread; | |||
bts->t_off += didread; | |||
if (bts->f_off == files[bts->index].length) { | |||
close(bts->fd); | |||
bts->fd = -1; | |||
bts->f_off = 0; | |||
bts->index++; | |||
} | |||
if (didread != wantread) | |||
return ENOENT; | |||
} | |||
return 0; | |||
} | |||
void | |||
bts_close_ro(struct bt_stream_ro *bts) | |||
{ | |||
if (bts->fd != -1) | |||
close(bts->fd); | |||
free(bts); | |||
} | |||
#define SHAFILEBUF (1 << 15) | |||
int | |||
bts_sha(struct bt_stream_ro *bts, off_t length, uint8_t *hash) | |||
{ | |||
SHA_CTX ctx; | |||
char buf[SHAFILEBUF]; | |||
size_t wantread; | |||
int err = 0; | |||
SHA1_Init(&ctx); | |||
while (length > 0) { | |||
wantread = min(length, SHAFILEBUF); | |||
if ((err = bts_read_ro(bts, buf, wantread)) != 0) | |||
break; | |||
length -= wantread; | |||
SHA1_Update(&ctx, buf, wantread); | |||
} | |||
SHA1_Final(hash, &ctx); | |||
return err; | |||
} | |||
int | |||
bts_hashes(struct metainfo *meta, | |||
F_fdcb fd_cb, | |||
void (*cb)(uint32_t, uint8_t *, void *), | |||
void *arg) | |||
{ | |||
int err = 0; | |||
uint8_t hash[SHA_DIGEST_LENGTH]; | |||
uint32_t piece; | |||
struct bt_stream_ro *bts; | |||
off_t plen = meta->piece_length; | |||
off_t llen = meta->total_length % plen; | |||
if ((bts = bts_open_ro(meta, 0, fd_cb, arg)) == NULL) | |||
return ENOMEM; | |||
for (piece = 0; piece < meta->npieces; piece++) { | |||
if (piece < meta->npieces - 1) | |||
err = bts_sha(bts, plen, hash); | |||
else | |||
err = bts_sha(bts, llen, hash); | |||
if (err == 0) | |||
cb(piece, hash, arg); | |||
else if (err == ENOENT) { | |||
cb(piece, NULL, arg); | |||
if (piece < meta->npieces - 1) | |||
bts_seek_ro(bts, (piece + 1) * plen); | |||
err = 0; | |||
} else | |||
break; | |||
} | |||
bts_close_ro(bts); | |||
return err; | |||
} | |||
struct bt_stream_wo * | |||
bts_open_wo(struct metainfo *meta, off_t off, F_fdcb fd_cb, void *fd_arg) | |||
{ | |||
struct bt_stream_wo *bts = malloc(sizeof(*bts)); | |||
if (bts == NULL) | |||
return NULL; | |||
bts->meta = meta; | |||
bts->fd_cb = fd_cb; | |||
bts->fd_arg = fd_arg; | |||
bts->t_off = 0; | |||
bts->f_off = 0; | |||
bts->index = 0; | |||
bts->fd = -1; | |||
bts_seek_ro((struct bt_stream_ro *)bts, off); | |||
return bts; | |||
} | |||
int | |||
bts_write_wo(struct bt_stream_wo *bts, const char *buf, size_t len) | |||
{ | |||
struct fileinfo *files = bts->meta->files; | |||
size_t boff, wantwrite; | |||
ssize_t didwrite; | |||
assert(bts->t_off + len <= bts->meta->total_length); | |||
boff = 0; | |||
while (boff < len) { | |||
if (bts->fd == -1) { | |||
int err = | |||
bts->fd_cb(files[bts->index].path, &bts->fd, bts->fd_arg); | |||
if (err != 0) | |||
return err; | |||
if (bts->f_off != 0) | |||
lseek(bts->fd, bts->f_off, SEEK_SET); | |||
} | |||
wantwrite = min(len - boff, files[bts->index].length - bts->f_off); | |||
didwrite = write(bts->fd, buf + boff, wantwrite); | |||
if (didwrite == -1) | |||
return errno; | |||
boff += didwrite; | |||
bts->f_off += didwrite; | |||
bts->t_off += didwrite; | |||
if (bts->f_off == files[bts->index].length) { | |||
if (fsync(bts->fd) == -1) { | |||
int err = errno; | |||
close(bts->fd); | |||
return err; | |||
} | |||
if (close(bts->fd) == -1) | |||
return errno; | |||
bts->fd = -1; | |||
bts->f_off = 0; | |||
bts->index++; | |||
} | |||
} | |||
return 0; | |||
} | |||
int | |||
bts_close_wo(struct bt_stream_wo *bts) | |||
{ | |||
int err = 0; | |||
if (bts->fd != -1) { | |||
if (fsync(bts->fd) == -1) { | |||
err = errno; | |||
close(bts->fd); | |||
} else if (close(bts->fd) == -1) | |||
err = errno; | |||
} | |||
free(bts); | |||
return err; | |||
} |
@@ -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 |
@@ -0,0 +1,141 @@ | |||
#include <sys/types.h> | |||
#include <sys/stat.h> | |||
#include <errno.h> | |||
#include <fcntl.h> | |||
#include <inttypes.h> | |||
#include <limits.h> | |||
#include <stdarg.h> | |||
#include <stdio.h> | |||
#include <stdlib.h> | |||
#include <string.h> | |||
#include <unistd.h> | |||
void | |||
set_bit(uint8_t *bits, unsigned long index) | |||
{ | |||
bits[index / 8] |= (1 << (7 - index % 8)); | |||
} | |||
void | |||
clear_bit(uint8_t *bits, unsigned long index) | |||
{ | |||
bits[index / 8] &= ~(1 << (7 - index % 8)); | |||
} | |||
int | |||
has_bit(uint8_t *bits, unsigned long index) | |||
{ | |||
return bits[index / 8] & (1 << (7 - index % 8)); | |||
} | |||
int | |||
set_nonblocking(int fd) | |||
{ | |||
int oflags; | |||
if ((oflags = fcntl(fd, F_GETFL, 0)) == -1) | |||
return errno; | |||
if (fcntl(fd, F_SETFL, oflags | O_NONBLOCK) == -1) | |||
return errno; | |||
return 0; | |||
} | |||
int | |||
set_blocking(int fd) | |||
{ | |||
int oflags; | |||
if ((oflags = fcntl(fd, F_GETFL, 0)) == -1) | |||
return errno; | |||
if (fcntl(fd, F_SETFL, oflags & ~O_NONBLOCK) == -1) | |||
return errno; | |||
return 0; | |||
} | |||
int | |||
mkdirs(char *path) | |||
{ | |||
int err = 0; | |||
char *spos = strchr(path + 1, '/'); // Must ignore the root | |||
while (spos != NULL) { | |||
*spos = '\0'; | |||
err = mkdir(path, 0777); | |||
*spos = '/'; | |||
if (err != 0 && errno != EEXIST) { | |||
err = errno; | |||
break; | |||
} | |||
spos = strchr(spos + 1, '/'); | |||
} | |||
return err; | |||
} | |||
int | |||
vopen(int *res, int flags, const char *fmt, ...) | |||
{ | |||
int fd, didmkdirs; | |||
char path[PATH_MAX + 1]; | |||
va_list ap; | |||
va_start(ap, fmt); | |||
if (vsnprintf(path, PATH_MAX, fmt, ap) >= PATH_MAX) { | |||
va_end(ap); | |||
return ENAMETOOLONG; | |||
} | |||
va_end(ap); | |||
didmkdirs = 0; | |||
again: | |||
fd = open(path, flags, 0666); | |||
if (fd < 0 && errno == ENOENT && (flags & O_CREAT) != 0 && !didmkdirs) { | |||
if (mkdirs(path) == 0) { | |||
didmkdirs = 1; | |||
goto again; | |||
} else | |||
return errno; | |||
} | |||
if (fd >= 0) { | |||
*res = fd; | |||
return 0; | |||
} else | |||
return errno; | |||
} | |||
int | |||
canon_path(const char *path, char **res) | |||
{ | |||
char rp[PATH_MAX]; | |||
if (realpath(path, rp) == NULL) | |||
return errno; | |||
#if 0 | |||
// This could be necessary on solaris. | |||
if (rp[0] != '/') { | |||
char wd[MAXPATHLEN]; | |||
if (getcwd(wd, MAXPATHLEN) == NULL) | |||
return errno; | |||
if (strlcat(wd, "/", MAXPATHLEN) >= MAXPATHLEN) | |||
return ENAMETOOLONG; | |||
if (strlcat(wd, rp, MAXPATHLEN) >= MAXPATHLEN) | |||
return ENAMETOOLONG; | |||
strcpy(rp, wd); | |||
} | |||
#endif | |||
if ((*res = strdup(rp)) == NULL) | |||
return ENOMEM; | |||
return 0; | |||
} | |||
size_t | |||
round_to_page(size_t size) | |||
{ | |||
size_t psize = getpagesize(); | |||
size_t rem = size % psize; | |||
if (rem != 0) | |||
size += psize - rem; | |||
return size; | |||
} |
@@ -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 |