git: 9front

Download patch

ref: 4476be0955c012781b048cd049fe22349f109dc4
parent: a2e0a132e2f3914b90b3ad5f3052739f553331b6
author: cinap_lenrek <cinap_lenrek@gmx.de>
date: Sun Apr 15 20:50:25 EDT 2012

ip/torrent: webseed support

--- a/sys/man/1/torrent
+++ b/sys/man/1/torrent
@@ -14,8 +14,11 @@
 .I mtpt
 ] [
 .B -t
-.I url
+.I tracker-url
 ] [
+.B -w
+.I webseed-url
+] [
 .B -s
 ] [
 .B -c
@@ -44,12 +47,21 @@
 .I file
 argument (or standard-input when omited) and writes
 a torrent file to standard-output and exits.
-A tracker
-.I url
+A
+.I tracker-url
 should be given with the
 .B -t
 option in that case. A list of trackers can be obtained
 on the web, see the examples below.
+.PP
+If the files in the torrent are also available from a url, a
+.I webseed-url
+can be passed with the
+.B -w
+option. If
+.I webseed-url
+ends with a slash, the filename, from the torrent, concatinated
+with the url forms the target url.
 .PP
 Without the
 .B -c
--- a/sys/src/cmd/ip/torrent.c
+++ b/sys/src/cmd/ip/torrent.c
@@ -65,6 +65,12 @@
 File *files;
 Stats stats;
 
+int
+finished(void)
+{
+	return nhavepieces >= npieces;
+}
+
 void
 freedict(Dict *d)
 {
@@ -647,6 +653,78 @@
 }
 
 void
+webseed(Dict *w, File *f)
+{
+	vlong off, woff;
+	int fd, n, m, r, p, x, y, err;
+	uchar buf[MAXIO];
+	Dict *w0;
+	char *s;
+
+	if(w == nil || f == nil || finished())
+		return;
+	if(rfork(RFPROC|RFMEM))
+		return;
+	w0 = w;
+Retry:
+	if(debug) fprint(2, "webseed %s %s\n", w->str, f->name);
+	s = strrchr(w->str, '/');
+	if(s && s[1] == 0)
+		fd = hopen("%s%s", w->str, f->name);
+	else
+		fd = hopen("%s", w->str);
+	if(fd < 0){
+		if(finished())
+			exits(0);
+		if((w = w->next) == w0)
+			exits(0);
+		goto Retry;
+	}
+	off = 0;
+	err = 0;
+	while(off < f->len){
+		if(finished())
+			break;
+		n = MAXIO;
+		if((f->len - off) < n)
+			n = (f->len - off);
+		if((n = read(fd, buf, n)) <= 0)
+			break;
+		woff = f->off + off;
+		x = woff / blocksize;
+		off += n;
+		y = (f->off + off) / blocksize;
+		p = woff - x*blocksize;
+		m = 0;
+		while(m < n){
+			r = pieces[x].len - p;
+			if(r > (n-m))
+				r = n-m;
+			if((havemap[x>>3] & (0x80>>(x&7))) == 0)
+				if(rwpiece(1, x, buf+m, r, p) != r)
+					goto Err;
+			if(x == y)
+				break;
+			m += r;
+			p = 0;
+			if(!havepiece(x++)){
+				if(++err > 10){
+					fprint(2, "webseed corrupt %s / %s\n",
+						w->str, f->name);
+					goto Err;
+				}
+			}
+		}
+	}
+
+	havepiece(f->off / blocksize);
+	havepiece((f->off+f->len) / blocksize);
+Err:
+	close(fd);
+	exits(0);
+}
+
+void
 tracker(char *url)
 {
 	static Dict *trackers;
@@ -746,7 +824,7 @@
 }
 
 int
-mktorrent(int fd, Dict *alist)
+mktorrent(int fd, Dict *alist, Dict *wlist)
 {
 	uchar *b, h[20];
 	Dir *d;
@@ -779,6 +857,15 @@
 			print("l%ld:%se", strlen(alist->str), alist->str);
 		print("e");
 	}
+	if(wlist){
+		if(wlist->next){
+			print("8:url-listl");
+			for(; wlist; wlist = wlist->next)
+				print("%ld:%s", strlen(wlist->str), wlist->str);
+			print("e");
+		} else
+			print("8:url-list%ld:%s", strlen(wlist->str), wlist->str);
+	}
 	print("4:info");
 	print("d");
 	print("4:name%ld:%s", strlen(d->name), d->name);
@@ -885,13 +972,13 @@
 main(int argc, char *argv[])
 {
 	int sflag, pflag, vflag, cflag, fd, i, n;
-	Dict *alist, *info, *torrent, *d, *l;
+	Dict *alist, *wlist, *info, *torrent, *d, *l;
 	char *p, *s, *e;
 	File **fp, *f;
 	vlong len;
 
 	fmtinstall('H', Hfmt);
-	alist = nil;
+	alist = wlist = nil;
 	sflag = pflag = vflag = cflag = 0;
 	ARGBEGIN {
 	case 'm':
@@ -900,6 +987,9 @@
 	case 't':
 		alist = scons(EARGF(usage()), alist);
 		break;
+	case 'w':
+		wlist = scons(EARGF(usage()), wlist);
+		break;
 	case 's':
 		sflag = 1;
 		break;
@@ -926,7 +1016,7 @@
 	if(cflag){
 		if(alist == nil)
 			alist = scons(deftrack, alist);
-		if(mktorrent(fd, alist) < 0)
+		if(mktorrent(fd, alist, wlist) < 0)
 			sysfatal("%r");
 		exits(0);
 	}
@@ -937,8 +1027,18 @@
 	for(d = dlook(torrent, "announce-list"); d && d->typ == 'l'; d = d->next)
 		for(l = d->val; l && l->typ == 'l'; l = l->next)
 			alist = scons(dstr(l->val), alist);
-	if(alist == nil)
-		sysfatal("no trackers in torrent");
+	if(d = dlook(torrent, "url-list")){
+		if(d->typ == 's')
+			wlist = scons(dstr(d->val), wlist);
+		else for(; d && d->typ == 'l'; d = d->next)
+			wlist = scons(dstr(d->val), wlist);
+		/* make wlist into a ring */
+		for(l = wlist; l && l->next; l = l->next)
+			;
+		if(l) l->next = wlist;
+	}
+	if(alist == nil && wlist == nil)
+		sysfatal("no trackers or webseeds in 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)
@@ -972,12 +1072,12 @@
 	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)
+		s = fixnamedup(f->name);
+		if(vflag) fprint(pflag ? 2 : 1, "%s\n", s);
+		if((f->fd = open(s, ORDWR)) < 0){
+			if(mkdirs(s) < 0)
 				sysfatal("mkdirs: %r");
-			if((f->fd = create(f->name, ORDWR, 0666)) < 0)
+			if((f->fd = create(s, ORDWR, 0666)) < 0)
 				sysfatal("create: %r");
 		}
 		f->off = len;
@@ -1024,16 +1124,18 @@
 		server();
 		for(; alist; alist = alist->next)
 			tracker(alist->str);
+		for(f = files, l = wlist; f && l; f = f->next, l = l->next)
+			webseed(l, f);
 		while(waitpid() != -1)
 			;
 		break;
 	default:
 		killgroup = i;
-		while((nhavepieces < npieces) || sflag){
+		do {
+			sleep(1000);
 			if(pflag)
 				print("%d %d\n", nhavepieces, npieces);
-			sleep(1000);
-		}
+		} while(!finished() || sflag);
 	}
 	postnote(PNGROUP, killgroup, "kill");
 	exits(0);
--