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 |