Ver código fonte

Import btpd-0.1.

git-svn-id: file:///home/rnyberg/svngit/btpd/releases/0.1@1 76a1f634-46fa-0310-9943-bd1476092a85
master
Richard Nyberg 19 anos atrás
commit
dd0d462afa
37 arquivos alterados com 5748 adições e 0 exclusões
  1. +54
    -0
      COPYRIGHT
  2. +2
    -0
      Makefile.am
  3. +59
    -0
      README
  4. +21
    -0
      TODO
  5. +15
    -0
      btpd/Makefile.am
  6. +386
    -0
      btpd/btpd.c
  7. +91
    -0
      btpd/btpd.h
  8. +225
    -0
      btpd/cli_if.c
  9. +960
    -0
      btpd/net.c
  10. +97
    -0
      btpd/net.h
  11. +178
    -0
      btpd/peer.c
  12. +64
    -0
      btpd/peer.h
  13. +706
    -0
      btpd/policy.c
  14. +22
    -0
      btpd/policy.h
  15. +81
    -0
      btpd/queue.h
  16. +244
    -0
      btpd/torrent.c
  17. +67
    -0
      btpd/torrent.h
  18. +300
    -0
      btpd/tracker_req.c
  19. +13
    -0
      btpd/tracker_req.h
  20. +11
    -0
      cli/Makefile.am
  21. +577
    -0
      cli/btcli.c
  22. +53
    -0
      cli/btinfo.c
  23. +221
    -0
      cli/btpd_if.c
  24. +21
    -0
      cli/btpd_if.h
  25. +65
    -0
      configure.ac
  26. +4
    -0
      hack.mk
  27. +8
    -0
      misc/Makefile.am
  28. +345
    -0
      misc/benc.c
  29. +37
    -0
      misc/benc.h
  30. +64
    -0
      misc/iobuf.c
  31. +15
    -0
      misc/iobuf.h
  32. +275
    -0
      misc/metainfo.c
  33. +28
    -0
      misc/metainfo.h
  34. +241
    -0
      misc/stream.c
  35. +36
    -0
      misc/stream.h
  36. +141
    -0
      misc/subr.c
  37. +21
    -0
      misc/subr.h

+ 54
- 0
COPYRIGHT Ver arquivo

@@ -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.

+ 2
- 0
Makefile.am Ver arquivo

@@ -0,0 +1,2 @@
SUBDIRS=misc btpd cli
EXTRA_DIST=COPYRIGHT

+ 59
- 0
README Ver arquivo

@@ -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.

+ 21
- 0
TODO Ver arquivo

@@ -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...

+ 15
- 0
btpd/Makefile.am Ver arquivo

@@ -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@

+ 386
- 0
btpd/btpd.c Ver arquivo

@@ -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;
}

+ 91
- 0
btpd/btpd.h Ver arquivo

@@ -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

+ 225
- 0
btpd/cli_if.c Ver arquivo

@@ -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);
}

+ 960
- 0
btpd/net.c Ver arquivo

@@ -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);
}
}
}

+ 97
- 0
btpd/net.h Ver arquivo

@@ -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

+ 178
- 0
btpd/peer.c Ver arquivo

@@ -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);
}

+ 64
- 0
btpd/peer.h Ver arquivo

@@ -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

+ 706
- 0
btpd/policy.c Ver arquivo

@@ -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);
}
}

+ 22
- 0
btpd/policy.h Ver arquivo

@@ -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

+ 81
- 0
btpd/queue.h Ver arquivo

@@ -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

+ 244
- 0
btpd/torrent.c Ver arquivo

@@ -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;
}

+ 67
- 0
btpd/torrent.h Ver arquivo

@@ -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

+ 300
- 0
btpd/tracker_req.c Ver arquivo

@@ -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);
}
}

+ 13
- 0
btpd/tracker_req.h Ver arquivo

@@ -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

+ 11
- 0
cli/Makefile.am Ver arquivo

@@ -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@

+ 577
- 0
cli/btcli.c Ver arquivo

@@ -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;
}

+ 53
- 0
cli/btinfo.c Ver arquivo

@@ -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;
}

+ 221
- 0
cli/btpd_if.c Ver arquivo

@@ -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;
}

+ 21
- 0
cli/btpd_if.h Ver arquivo

@@ -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

+ 65
- 0
configure.ac Ver arquivo

@@ -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

+ 4
- 0
hack.mk Ver arquivo

@@ -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

+ 8
- 0
misc/Makefile.am Ver arquivo

@@ -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@

+ 345
- 0
misc/benc.c Ver arquivo

@@ -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);
}

+ 37
- 0
misc/benc.h Ver arquivo

@@ -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

+ 64
- 0
misc/iobuf.c Ver arquivo

@@ -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;
}

+ 15
- 0
misc/iobuf.h Ver arquivo

@@ -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

+ 275
- 0
misc/metainfo.c Ver arquivo

@@ -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;
}

+ 28
- 0
misc/metainfo.h Ver arquivo

@@ -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

+ 241
- 0
misc/stream.c Ver arquivo

@@ -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;
}

+ 36
- 0
misc/stream.h Ver arquivo

@@ -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

+ 141
- 0
misc/subr.c Ver arquivo

@@ -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;
}

+ 21
- 0
misc/subr.h Ver arquivo

@@ -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

Carregando…
Cancelar
Salvar