shithub: riscv

ref: b8136286b6044b81945983a4651fcf9fb70ddad0
dir: /sys/src/cmd/ip/torrent.c/

View raw version
#include <u.h>
#include <libc.h>
#include <mp.h>
#include <libsec.h>

typedef struct Dict Dict;
typedef struct Piece Piece;
typedef struct File File;
typedef struct Stats Stats;

struct Dict
{
	char	typ;	// i, d, s, l
	Dict	*val;
	Dict	*next;
	char	*start, *end;
	int	len;
	char	str[];
};

struct Piece
{
	uchar	*hash;
	int	len;
	int	brk;
};

struct File
{
	File	*next;
	char	*name;
	int	fd;
	vlong	off;
	vlong	len;
};

struct Stats
{
	Lock;
	vlong	up;
	vlong	down;
	vlong	left;
};

enum {
	MAXIO = 16*1024,
};

int debug;
int killgroup = -1;
int port = 6881;
char *deftrack = "http://exodus.desync.com/announce";
char *mntweb = "/mnt/web";
uchar infohash[20];
uchar peerid[20];
int blocksize;

int npieces;
Piece *pieces;

int nhavemap;
uchar *havemap;
int nhavepieces;

File *files;
Stats stats;

void
freedict(Dict *d)
{
	if(d){
		if(d->val != d)
			freedict(d->val);
		freedict(d->next);
		free(d);
	}
}

char*
bparse(char *s, char *e, Dict **dp)
{
	char *x, t;
	Dict *d;
	int n;

	*dp = nil;
	if(s >= e)
		return e;

	t = *s;
	switch(t){
	case 'd':
	case 'l':
		x = s++;
		d = nil;
		while(s < e){
			if(*s == 'e'){
				s++;
				break;
			}
			if(t == 'd'){
				s = bparse(s, e, dp);
				if((d = *dp) == nil)
					break;
			} else
				d = *dp = mallocz(sizeof(*d), 1);
			d->typ = t;
			d->start = x;
			if(s < e){
				s = bparse(s, e, &d->val);
				dp = &d->next;
				d->end = s;
			}
			x = s;
		}
		if(d)
			d->end = s;
		return s;
	case 'i':
		x = ++s;
		if((s = memchr(x, 'e', e - x)) == nil)
			return e;
		n = s - x;
		s++;
		break;
	default:
		if((x = memchr(s, ':', e - s)) == nil)
			return e;
		x++;
		if((n = atoi(s)) < 0)
			return e;
		s = x + n;
		if((s > e) || (s < x)){
			n = e - x;
			s = e;
		}
		t = 's';
	}
	d = mallocz(sizeof(*d) + n+1, 1);
	d->typ = t;
	memmove(d->str, x, d->len = n);
	d->str[n] = 0;
	*dp = d;
	return s;
}

char*
dstr(Dict *d)
{
	if(d && (d->typ == 's' || d->typ == 'i'))
		return d->str;
	return nil;
}

Dict*
dlook(Dict *d, char *s)
{
	for(; d && d->typ == 'd'; d = d->next)
		if(d->len && strcmp(d->str, s) == 0)
			return d->val;
	return nil;
}

int
readall(int fd, char **p)
{
	int n, r;

	n = 0;
	*p = nil;
	while(*p = realloc(*p, n+1024)){
		if((r = read(fd, *p+n, 1024)) <= 0)
			break;
		n += r;
	}
	return n;
}

int
rwpiece(int wr, int index, uchar *data, int len, int poff)
{
	vlong off;
	int n, m;
	File *f;

	if(len <= 0 || poff >= pieces[index].len)
		return 0;
	if(len+poff > pieces[index].len)
		len = pieces[index].len - poff;
	off = (vlong)index * blocksize;
	off += poff;
	for(f = files; f; f = f->next)
		if((f->off+f->len) > off)
			break;
	off -= f->off;
	n = ((off + len) > f->len) ? f->len - off : len;
	if((n = (wr ? pwrite(f->fd, data, n, off) : pread(f->fd, data, n, off))) <= 0)
		return -1;
	if((m = rwpiece(wr, index, data + n, len - n, poff + n)) < 0)
		return -1;
	return n+m;
}

int
havepiece(int x)
{
	uchar *p, m, hash[20];
	int n;

	m = 0x80>>(x&7);
	if(havemap[x>>3] & m)
		return 1;
	p = malloc(blocksize);
	n = pieces[x].len;
	if(rwpiece(0, x, p, n, 0) != n){
		free(p);
		return 0;
	}
	sha1(p, n, hash, nil);
	free(p);
	if(memcmp(hash, pieces[x].hash, 20))
		return 0;
	lock(&stats);
	if((havemap[x>>3] & m) == 0){
		havemap[x>>3] |= m;
		nhavepieces++;
		stats.left -= pieces[x].len;
	}
	unlock(&stats);
	return 1;
}

int
pickpiece(uchar *map)
{
	int i, x, r, k;
	uchar m;

	r = -1;
	k = 0;
	for(i = 0; i<nhavemap; i++){
		if(map[i] == 0)
			continue;
		for(x = i<<3, m = 0x80; m; m >>= 1, x++){
			if((~map[i] | havemap[i]) & m)
				continue;
			if(nrand(++k) == 0)
				r = x;
		}
	}
	return r;
}

int
unpack(uchar *s, int n, char *fmt, ...)
{
	va_list arg;
	uchar *b, *e;

	b = s;
	e = b + n;
	va_start(arg, fmt);
	for(; *fmt; fmt++) {
		switch(*fmt){
		case '_':
			s++;
			break;
		case 'b':
			if(s+1 > e) goto Err;
			*va_arg(arg, int*) = *s++;
			break;
		case 'l':
			if(s+4 > e) goto Err;
			*va_arg(arg, int*) = s[0]<<24 | s[1]<<16 | s[2]<<8 | s[3];
			s += 4;
			break;
		}
	}
	va_end(arg);
	return s - b;
Err:
	va_end(arg);
	return -1;
}

int
pack(uchar *s, int n, char *fmt, ...)
{
	va_list arg;
	uchar *b, *e;
	int i;

	b = s;
	e = b + n;
	va_start(arg, fmt);
	for(; *fmt; fmt++) {
		switch(*fmt){
		case '_':
			i = 0;
			if(0){
		case 'b':
			i = va_arg(arg, int);
			}
			if(s+1 > e) goto Err;
			*s++ = i & 0xFF;
			break;
		case 'l':
			i = va_arg(arg, int);
			if(s+4 > e) goto Err;
			*s++ = (i>>24) & 0xFF;
			*s++ = (i>>16) & 0xFF;
			*s++ = (i>>8) & 0xFF;
			*s++ = i & 0xFF;
			break;
		case '*':
			i = va_arg(arg, int);
			if(s+i > e) goto Err;
			memmove(s, va_arg(arg, uchar*), i);
			s += i;
			break;
		}
	}
	va_end(arg);
	return s - b;
Err:
	va_end(arg);
	return -1;
}



int
peer(int fd, int incoming, char *addr)
{
	uchar buf[64+MAXIO], *map, *told, *p, m;
	int mechoking, hechoking;
	int mewant, hewant;
	int workpiece;
	int i, o, l, x, n;

	if(debug) fprint(2, "peer %s: %s connected\n", addr, incoming ? "incoming" : "outgoing");

	for(i=0; i<2; i++){
		if((incoming && i) || (!incoming && !i)){
			if(debug) fprint(2, "peer %s: -> handshake\n", addr);
			n = pack(buf, sizeof(buf), "*________**", 
				20, "\x13BitTorrent protocol",
				sizeof(infohash), infohash,
				sizeof(peerid), peerid);
			if(write(fd, buf, n) != n)
				return 1;
		}
		if((incoming && !i) || (!incoming && i)){
			n = 20 + 8 + sizeof(infohash);
			if((n = readn(fd, buf, n)) != n)
				return 1;
			if(memcmp(buf, "\x13BitTorrent protocol", 20))
				return 0;
			if(memcmp(infohash, buf + 20 + 8, sizeof(infohash)))
				return 0;
			if(debug) fprint(2, "peer %s: <- handshake\n", addr);
		}
	}
	if(readn(fd, buf, sizeof(peerid)) != sizeof(peerid))
		return 1;
	if(memcmp(peerid, buf, sizeof(peerid)) == 0)
		return 0;
	if(debug) fprint(2, "peer %s: peerid %.*s\n", addr, sizeof(peerid), (char*)buf);

	mechoking = 1;
	hechoking = 1;
	mewant = 0;
	hewant = 0;
	workpiece = -1;
	map = mallocz(nhavemap, 1);
	told = malloc(nhavemap);

	if(debug) fprint(2, "peer %s: -> bitfield %d\n", addr, nhavemap);
	memmove(told, havemap, nhavemap);
	n = pack(buf, sizeof(buf), "lb*", nhavemap+1, 0x05, nhavemap, told);
	if(write(fd, buf, n) != n)
		goto Out;

	for(;;){
		for(i=0; i<nhavemap; i++){
			if(told[i] != havemap[i]){
				for(x = i<<3, m = 0x80; m; m >>= 1, x++){
					if((~havemap[i] | told[i] | map[i]) & m)
						continue;
					told[i] |= m;
					if(debug) fprint(2, "peer %s: -> have %d\n", addr, x);
					n = pack(buf, sizeof(buf), "lbl", 1+4, 0x04, x);
					if(write(fd, buf, n) != n)
						goto Out;
				}
			}
			if(!mewant && (map[i] & ~havemap[i])){
				mewant = 1;
				if(debug) fprint(2, "peer %s: -> interested\n", addr);
				n = pack(buf, sizeof(buf), "lb", 1, 0x02);
				if(write(fd, buf, n) != n)
					goto Out;
			}
		}
		if(!hechoking && mewant){
			x = workpiece;
			if(x >= 0 && pieces[x].brk < pieces[x].len)
				{}
			else x = pickpiece(map);
			if(x >= 0){
				o = pieces[x].brk;
				l = pieces[x].len - o;
				if(l > MAXIO)
					l = MAXIO;
				if(debug) fprint(2, "peer %s: -> request %d %d %d\n", addr, x, o, l);
				n = pack(buf, sizeof(buf), "lblll", 1+4+4+4, 0x06, x, o, l);
				if(write(fd, buf, n) != n)
					goto Out;
				workpiece = x;
			}
		}
		if(mechoking && hewant){
			mechoking = 0;
			if(debug) fprint(2, "peer %s: -> unchoke\n", addr);
			n = pack(buf, sizeof(buf), "lb", 1, 0x01);
			if(write(fd, buf, n) != n)
				goto Out;
		}

		if(readn(fd, buf, 4) != 4)
			break;
		unpack(buf, 4, "l", &n);
		if(n < 0 || n > sizeof(buf))
			break;
		if(n == 0)
			continue;
		if(readn(fd, buf, n) != n)
			break;

		n--;
		p = buf+1;
		switch(*buf){
		case 0x00:	// Choke
			hechoking = 1;
			workpiece = -1;
			if(debug) fprint(2, "peer %s: <- choke\n", addr);
			break;
		case 0x01:	// Unchoke
			hechoking = 0;
			if(debug) fprint(2, "peer %s: <- unchoke\n", addr);
			break;
		case 0x02:	// Interested
			hewant = 1;
			if(debug) fprint(2, "peer %s: <- interested\n", addr);
			break;
		case 0x03:	// Notinterested
			hewant = 0;
			if(debug) fprint(2, "peer %s: <- notinterested\n", addr);
			break;
		case 0x04:	// Have <piceindex>
			if(unpack(p, n, "l", &x) < 0)
				goto Out;
			if(debug) fprint(2, "peer %s: <- have %d\n", addr, x);
			if(x < 0 || x >= npieces)
				continue;
			map[x>>3] |= 0x80>>(x&7);
			break;
		case 0x05:	// Bitfield
			if(debug) fprint(2, "peer %s: <- bitfield %d\n", addr, n);
			if(n != nhavemap)
				continue;
			memmove(map, p, n);
			break;
		case 0x06:	// Request <index> <begin> <length>
			if(unpack(p, n, "lll", &x, &o, &l) < 0)
				goto Out;
			if(debug) fprint(2, "peer %s: <- request %d %d %d\n", addr, x, o, l);
			if(x < 0 || x >= npieces)
				continue;
			if(!hewant || mechoking || (~havemap[x>>3]&(0x80>>(x&7))))
				continue;
			if(debug) fprint(2, "peer %s: -> piece %d %d\n", addr, x, o);
			n = 4+1+4+4;
			if(l > MAXIO)
				l = MAXIO;
			if((l = rwpiece(0, x, buf + n, l, o)) <= 0)
				continue;
			n = pack(buf, sizeof(buf), "lbll", 1+4+4+l, 0x07, x, o);
			n += l;
			if(write(fd, buf, n) != n)
				goto Out;
			lock(&stats);
			stats.up += n;
			unlock(&stats);
			break;
		case 0x07:	// Piece <index> <begin> <block>
			if(unpack(p, n, "ll", &x, &o) != 8)
				goto Out;
			p += 8;
			n -= 8;
			lock(&stats);
			stats.down += n;
			unlock(&stats);
			if(debug) fprint(2, "peer %s: <- piece %d %d %d\n", addr, x, o, n);
			if(x < 0 || x >= npieces)
				continue;
			if((pieces[x].brk != o) || (havemap[x>>3]&(0x80>>(x&7))))
				continue;
			if(rwpiece(1, x, p, n, o) == n){
				if((pieces[x].brk = o+n) == pieces[x].len){
					if(!havepiece(x))
						pieces[x].brk = 0;
				}
			}
			break;
		case 0x08:	// Cancel <index> <begin> <length>
			if(unpack(p, n, "lll", &x, &o, &l) < 0)
				goto Out;
			if(debug) fprint(2, "peer %s: <- cancel %d %d %d\n", addr, x, o, l);
			break;
		case 0x09:	// Port <port>
			if(unpack(p, n, "l", &x) < 0)
				goto Out;
			if(debug) fprint(2, "peer %s: <- port %d\n", addr, x);
			break;
		}
	}

Out:
	free(told);
	free(map);
	return 1;
}

void
server(void)
{
	char addr[64], adir[40], ldir[40];
	int afd, lfd, dfd;
	NetConnInfo *ni;

	afd = -1;
	for(port=6881; port<6890; port++){
		snprint(addr, sizeof(addr), "tcp!*!%d", port);
		if((afd = announce(addr, adir)) >= 0)
			break;
	}
	if(afd < 0){
		fprint(2, "announce: %r");
		return;
	}
	if(rfork(RFFDG|RFPROC|RFMEM))
		return;
	for(;;){
		if((lfd = listen(adir, ldir)) < 0){
			fprint(2, "listen: %r");
			break;
		}
		if(rfork(RFFDG|RFPROC|RFMEM)){
			close(lfd);
			continue;
		}
		if((dfd = accept(lfd, ldir)) < 0){
			fprint(2, "accept: %r");
			break;
		}
		ni = getnetconninfo(ldir, dfd);
		peer(dfd, 1, ni ? ni->raddr : "???");
		if(ni) freenetconninfo(ni);	
		break;
	}
	exits(0);
}

void
client(char *ip, char *port)
{
	static Dict *peers;
	static QLock peerslk;
	int try, fd;
	char *addr;
	Dict *d;

	if(ip == nil || port == nil)
		return;

	d = mallocz(sizeof(*d) + 64, 1);
	snprint(addr = d->str, 64, "tcp!%s!%s", ip, port);
	qlock(&peerslk);
	if(dlook(peers, addr)){
		qunlock(&peerslk);
		free(d);
		return;
	}
	d->len = strlen(addr);
	d->typ = 'd';
	d->val = d;
	d->next = peers;
	peers = d;
	qunlock(&peerslk);

	if(debug) fprint(2, "client %s\n", addr);

	if(rfork(RFFDG|RFPROC|RFMEM))
		return;
	for(try = 0; try < 10; try++){
		if((fd = dial(addr, nil, nil, nil)) >= 0){
			if(!peer(fd, 0, addr))
				break;
			close(fd);
		}
		sleep((1000<<try)+nrand(5000));
	}
	exits(0);
}

int
hopen(char *url, ...)
{
	int conn, ctlfd, fd, n;
	char buf[1024+1];
	va_list arg;

	snprint(buf, sizeof buf, "%s/clone", mntweb);
	if((ctlfd = open(buf, ORDWR)) < 0)
		return -1;
	if((n = read(ctlfd, buf, sizeof buf-1)) <= 0){
		close(ctlfd);
		return -1;
	}
	buf[n] = 0;
	conn = atoi(buf);
	va_start(arg, url);
	strcpy(buf, "url ");
	n = 4+vsnprint(buf+4, sizeof(buf)-4, url, arg);
	va_end(arg);
	if(write(ctlfd, buf, n) != n){
	ErrOut:
		close(ctlfd);
		return -1;
	}
	snprint(buf, sizeof buf, "%s/%d/body", mntweb, conn);
	if((fd = open(buf, OREAD)) < 0)
		goto ErrOut;
	close(ctlfd);
	return fd;
}

void
tracker(char *url)
{
	static Dict *trackers;
	static QLock trackerslk;

	char *event, *p;
	Dict *d, *l;
	int n, fd;

	if(url == nil)
		return;

	qlock(&trackerslk);
	if(dlook(trackers, url)){
		qunlock(&trackerslk);
		return;
	}
	n = strlen(url);
	d = mallocz(sizeof(*d) + n+1, 1);
	strcpy(d->str, url);
	d->len = n;
	d->typ = 'd';
	d->val = d;
	d->next = trackers;
	trackers = d;
	url = d->str;
	qunlock(&trackerslk);

	if(debug) fprint(2, "tracker %s\n", url);

	if(rfork(RFPROC|RFMEM))
		return;

	event = "&event=started";
	for(;;){
		vlong up, down, left;

		lock(&stats);
		up = stats.up;
		down = stats.down;
		left = stats.left;
		unlock(&stats);

		d = nil;
		if((fd = hopen("%s?info_hash=%.*H&peer_id=%.*H&port=%d&"
			"uploaded=%lld&downloaded=%lld&left=%lld&compact=1&no_peer_id=1%s",
			url, sizeof(infohash), infohash, sizeof(peerid), peerid, port,
			up, down, left, event)) >= 0){
			event = "";
			n = readall(fd, &p);
			close(fd);
			bparse(p, p+n, &d);
			free(p);
		} else if(debug) fprint(2, "tracker %s: %r\n", url);
		if(l = dlook(d, "peers")){
			if(l->typ == 's'){
				uchar *b, *e;

				b = (uchar*)l->str;
				e = b + l->len;
				for(; b+6 <= e; b += 6){
					char ip[16], port[6];

					snprint(ip, sizeof(ip), "%d.%d.%d.%d", b[0], b[1], b[2], b[3]);
					snprint(port, sizeof(port), "%d", b[4]<<8 | b[5]);
					client(ip, port);
				}
			} else for(; l && l->typ == 'l'; l = l->next)
				client(dstr(dlook(l->val, "ip")), dstr(dlook(l->val, "port")));
		}
		n = 0;
		if(p = dstr(dlook(d, "interval")))
			n = atoi(p);
		if(n < 10 | n > 60*60)
			n = 2*60;
		freedict(d);
		sleep(n * 1000 + nrand(5000));
	}
}

int
Hfmt(Fmt *f)
{
	uchar *s, *e;
	s = va_arg(f->args, uchar*);
	if(f->flags & FmtPrec)
		e = s + f->prec;
	else
		e = s + strlen((char*)s);
	for(; s < e; s++)
		if(fmtprint(f, *s && ((*s >= '0' && *s <= '9') || 
			(*s >= 'a' && *s <= 'z') ||
			(*s >= 'A' && *s <= 'Z') || 
			strchr(".-_~", *s)) ? "%c" : "%%%.2x", *s) < 0)
			return -1;
	return 0;
}

int
mktorrent(int fd, char *url)
{
	uchar *b, h[20];
	Dir *d;
	int n;

	if((d = dirfstat(fd)) == nil)
		return -1;
	if(d->qid.type == QTDIR){
		free(d);
		werrstr("file is a directory");
		return -1;
	}
	if(d->length == 0){
		free(d);
		werrstr("empty file");
		return -1;
	}
	npieces = 1;
	for(blocksize = 256*1024;;blocksize<<=1){
		npieces = (d->length + blocksize-1) / blocksize;
		if(npieces <= 8*1024 || blocksize >= 2*1024*1024)
			break;
	}
	print("d");
	print("8:announce%ld:%s", strlen(url), url);
	print("4:info");
	print("d");
	print("4:name%ld:%s", strlen(d->name), d->name);
	print("6:lengthi%llde", d->length);
	print("12:piece lengthi%de", blocksize);
	print("6:pieces%d:", npieces*sizeof(h));
	free(d);
	b = malloc(blocksize);
	while((n = readn(fd, b, blocksize)) > 0){
		sha1(b, n, h, nil);
		if(write(1, h, sizeof(h)) != sizeof(h)){
			free(b);
			return -1;
		}
		npieces--;
	}
	free(b);
	if(npieces){
		werrstr("read failed: %r");
		return -1;
	}
	print("e");
	print("e");
	return 0;
}

int
mkdirs(char *s)
{
	char *p;
	int f;

	if(access(s, AEXIST) == 0)
		return 0;
	for(p=strchr(s+1, '/'); p; p=strchr(p+1, '/')){
		*p = 0;
		if(access(s, AEXIST)){
			if((f = create(s, OREAD, DMDIR | 0777)) < 0){
				*p = '/';
				return -1;
			}
			close(f);
		}
		*p = '/';
	}
	return 0;
}

char*
fixnamedup(char *s)
{
	int n, l;
	char *d;
	Rune r;

	n = 0;
	d = strdup(s);
	l = strlen(d);
	while(*s){
		s += chartorune(&r, s);
		if(r == ' ')
			r = 0xa0;
		if((n + runelen(r)) >= l){
			l += 64;
			d = realloc(d, l);
		}
		n += runetochar(d + n, &r);
	}
	d[n] = 0;
	return cleanname(d);
}

int
killnote(void *, char *)
{
	postnote(PNGROUP, killgroup, "kill");
	return 0;
}

void
usage(void)
{
	fprint(2, "usage: %s [ -vsdpc ] [ -m mtpt ] [ -t url ] [ file ]\n", argv0);
	exits("usage");
}

void
main(int argc, char *argv[])
{
	int sflag, pflag, vflag, cflag, fd, i, n;
	Dict *info, *torrent, *d;
	char *p, *s, *e, *url;
	File **fp, *f;
	vlong len;

	fmtinstall('H', Hfmt);
	url = nil;
	sflag = pflag = vflag = cflag = 0;
	ARGBEGIN {
	case 'm':
		mntweb = EARGF(usage());
		break;
	case 't':
		url = EARGF(usage());
		break;
	case 's':
		sflag = 1;
		break;
	case 'p':
		pflag = 1;
		break;
	case 'v':
		vflag = 1;
		break;
	case 'c':
		cflag = 1;
		break;
	case 'd':
		debug++;
		break;
	default:
		usage();
	} ARGEND;

	fd = 0;
	if(*argv)
		if((fd = open(*argv, OREAD)) < 0)
			sysfatal("open: %r");
	if(cflag){
		if(url == nil)
			url = deftrack;
		if(mktorrent(fd, url) < 0)
			sysfatal("%r");
		exits(0);
	}
	if((n = readall(fd, &p)) <= 0)
		sysfatal("read torrent: %r");
	bparse(p, p+n, &torrent);
	if((d = info = dlook(torrent, "info")) == nil)
		sysfatal("no meta info in torrent");
	for(s = e = d->start; d && d->typ == 'd'; d = d->next)
		e = d->end;
	sha1((uchar*)s, e - s, (uchar*)infohash, nil);
	free(p);

	fp = &files;
	if(d = dlook(info, "files")){		
		for(; d && d->typ == 'l'; d = d->next){
			Dict *di;

			if((s = dstr(dlook(d->val, "length"))) == nil)
				continue;
			f = mallocz(sizeof(*f), 1);
			f->len = atoll(s);
			f->name = dstr(dlook(info, "name"));
			for(di = dlook(d->val, "path"); di && di->typ == 'l'; di = di->next)
				if(s = dstr(di->val))
					f->name = f->name ? smprint("%s/%s", f->name, s) : s;
			*fp = f;
			fp = &f->next;
		}
	} else if(s = dstr(dlook(info, "length"))){
		f = mallocz(sizeof(*f), 1);
		f->len = atoll(s);
		f->name = dstr(dlook(info, "name"));
		*fp = f;
	}
	len = 0;
	for(f = files; f; f = f->next){
		if(f->name == nil || f->len <= 0)
			sysfatal("bogus file entry in meta info");
		f->name = fixnamedup(f->name);
		if(vflag) fprint(pflag ? 2 : 1, "%s\n", f->name);
		if((f->fd = open(f->name, ORDWR)) < 0){
			if(mkdirs(f->name) < 0)
				sysfatal("mkdirs: %r");
			if((f->fd = create(f->name, ORDWR, 0666)) < 0)
				sysfatal("create: %r");
		}
		f->off = len;
		len += f->len;
	}
	if(len <= 0)
		sysfatal("no files in torrent");

	if((s = dstr(dlook(info, "piece length"))) == nil)
		sysfatal("missing piece length in meta info");
	if((blocksize = atoi(s)) <= 0)
		sysfatal("bogus piece length in meta info");
	d = dlook(info, "pieces");
	if(d == nil || d->typ != 's' || d->len <= 0 || d->len % 20)
		sysfatal("bad or no pices in meta info");
	npieces = d->len / 20;
	pieces = mallocz(sizeof(Piece) * npieces, 1);
	nhavemap = (npieces+7) / 8;
	havemap = mallocz(nhavemap, 1);
	for(i = 0; i<npieces; i++){
		pieces[i].hash = (uchar*)d->str + i*20;
		if(len < blocksize)
			pieces[i].len = len;
		else
			pieces[i].len = blocksize;
		len -= pieces[i].len;
		stats.left += pieces[i].len;
	}
	if(len)
		sysfatal("pieces do not match file length");

	for(i = 0; i<npieces; i++)
		havepiece(i);

	srand(time(0));
	atnotify(killnote, 1);
	switch(i = rfork(RFPROC|RFMEM|RFNOTEG)){
	case -1:
		sysfatal("fork: %r");
	case 0:
		memmove(peerid, "-NF9001-", 8);
		for(i=8; i<sizeof(peerid); i++)
			peerid[i] = nrand(10)+'0';
		server();
		tracker(url);
		tracker(dstr(dlook(torrent, "announce")));
		for(d = dlook(torrent, "announce-list"); d && d->typ == 'l'; d = d->next)
			if(d->val && d->val->typ == 'l')
				tracker(dstr(d->val->val));
		while(waitpid() != -1)
			;
		break;
	default:
		killgroup = i;
		while((nhavepieces < npieces) || sflag){
			if(pflag)
				print("%d %d\n", nhavepieces, npieces);
			sleep(1000);
		}
	}
	postnote(PNGROUP, killgroup, "kill");
	exits(0);
}