]> git.lizzy.rs Git - plan9front.git/blobdiff - sys/src/cmd/ip/torrent.c
ip/torrent: avoid peerid collision using truerand() instead of time(0)
[plan9front.git] / sys / src / cmd / ip / torrent.c
index 2c37e7b93d42b235f066e66e8ad03039a67622ac..fc324977bcfca04ce8a51184529c49f02f47a1cf 100644 (file)
@@ -10,11 +10,11 @@ typedef struct Stats Stats;
 
 struct Dict
 {
-       char    typ;    // i, d, s, l
        Dict    *val;
        Dict    *next;
        char    *start, *end;
        int     len;
+       char    typ;    // i, d, s, l
        char    str[];
 };
 
@@ -44,12 +44,17 @@ struct Stats
 
 enum {
        MAXIO = 16*1024,
+       SRVPROCS = 16,
+       CLIPROCS = 16,
 };
 
-int debug, sflag, pflag, vflag;
+int debug;
+int nproc = 1;
 int killgroup = -1;
 int port = 6881;
+char *deftrack = "http://exodus.desync.com/announce";
 char *mntweb = "/mnt/web";
+char *useragent = "torrent";
 uchar infohash[20];
 uchar peerid[20];
 int blocksize;
@@ -64,6 +69,12 @@ int nhavepieces;
 File *files;
 Stats stats;
 
+int
+finished(void)
+{
+       return nhavepieces >= npieces;
+}
+
 void
 freedict(Dict *d)
 {
@@ -182,11 +193,11 @@ rwpiece(int wr, int index, uchar *data, int len, int poff)
        int n, m;
        File *f;
 
-       if(len <= 0 || poff >= pieces[index].len)
+       if(len <= 0 || poff < 0 || poff >= pieces[index].len)
                return 0;
        if(len+poff > pieces[index].len)
                len = pieces[index].len - poff;
-       off = (vlong)index * blocksize;
+       off = (vlong)index * (vlong)blocksize;
        off += poff;
        for(f = files; f; f = f->next)
                if((f->off+f->len) > off)
@@ -268,11 +279,29 @@ unpack(uchar *s, int n, char *fmt, ...)
                        if(s+1 > e) goto Err;
                        *va_arg(arg, int*) = *s++;
                        break;
+               case 'w':
+                       if(s+2 > e) goto Err;
+                       *va_arg(arg, int*) = s[0]<<8 | s[1];
+                       s += 2;
+                       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;
+               case 'v':
+                       if(s+4 > e) goto Err;
+                       *va_arg(arg, vlong*) = 
+                               (vlong)s[0]<<56 | 
+                               (vlong)s[1]<<48 | 
+                               (vlong)s[2]<<40 |
+                               (vlong)s[3]<<32 |
+                               (vlong)s[4]<<24 |
+                               (vlong)s[5]<<16 | 
+                               (vlong)s[6]<<8 | 
+                               (vlong)s[7];
+                       s += 8;
+                       break;
                }
        }
        va_end(arg);
@@ -287,6 +316,7 @@ pack(uchar *s, int n, char *fmt, ...)
 {
        va_list arg;
        uchar *b, *e;
+       vlong v;
        int i;
 
        b = s;
@@ -303,6 +333,12 @@ pack(uchar *s, int n, char *fmt, ...)
                        if(s+1 > e) goto Err;
                        *s++ = i & 0xFF;
                        break;
+               case 'w':
+                       i = va_arg(arg, int);
+                       if(s+2 > e) goto Err;
+                       *s++ = (i>>8) & 0xFF;
+                       *s++ = i & 0xFF;
+                       break;
                case 'l':
                        i = va_arg(arg, int);
                        if(s+4 > e) goto Err;
@@ -311,10 +347,22 @@ pack(uchar *s, int n, char *fmt, ...)
                        *s++ = (i>>8) & 0xFF;
                        *s++ = i & 0xFF;
                        break;
+               case 'v':
+                       v = va_arg(arg, vlong);
+                       if(s+8 > e) goto Err;
+                       *s++ = (v>>56) & 0xFF;
+                       *s++ = (v>>48) & 0xFF;
+                       *s++ = (v>>40) & 0xFF;
+                       *s++ = (v>>32) & 0xFF;
+                       *s++ = (v>>24) & 0xFF;
+                       *s++ = (v>>16) & 0xFF;
+                       *s++ = (v>>8) & 0xFF;
+                       *s++ = v & 0xFF;
+                       break;
                case '*':
                        i = va_arg(arg, int);
                        if(s+i > e) goto Err;
-                       memmove(s, va_arg(arg, uchar*), i);
+                       memmove(s, va_arg(arg, void*), i);
                        s += i;
                        break;
                }
@@ -355,9 +403,9 @@ peer(int fd, int incoming, char *addr)
                                return 1;
                        if(memcmp(buf, "\x13BitTorrent protocol", 20))
                                return 0;
-                       if(debug) fprint(2, "peer %s: <- handshake\n", addr);
                        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))
@@ -376,7 +424,7 @@ peer(int fd, int incoming, char *addr)
 
        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, havemap);
+       n = pack(buf, sizeof(buf), "lb*", nhavemap+1, 0x05, nhavemap, told);
        if(write(fd, buf, n) != n)
                goto Out;
 
@@ -403,11 +451,15 @@ peer(int fd, int incoming, char *addr)
                }
                if(!hechoking && mewant){
                        x = workpiece;
-                       if(x >= 0 && pieces[x].brk < pieces[x].len)
-                               {}
-                       else x = pickpiece(map);
+                       if(x < 0 || (havemap[x>>3]&(0x80>>(x&7))) != 0)
+                               x = pickpiece(map);
                        if(x >= 0){
+                               workpiece = x;
                                o = pieces[x].brk;
+                               if(o < 0 || o >= pieces[x].len){
+                                       pieces[x].brk = 0;
+                                       o = 0;
+                               }
                                l = pieces[x].len - o;
                                if(l > MAXIO)
                                        l = MAXIO;
@@ -415,7 +467,6 @@ peer(int fd, int incoming, char *addr)
                                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){
@@ -503,13 +554,22 @@ peer(int fd, int incoming, char *addr)
                        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))))
+                       if((havemap[x>>3]&(0x80>>(x&7))) != 0)
                                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;
-                               }
+                       if(o < 0 || o >= pieces[x].len)
+                               continue;
+                       if(o+n > pieces[x].len)
+                               n = o - pieces[x].len;
+                       if((o > pieces[x].brk) || (o+n <= pieces[x].brk))
+                               continue;
+                       n = rwpiece(1, x, p, n, o);
+                       if(n <= 0)
+                               continue;
+                       pieces[x].brk = o+n;
+                       if(o+n >= pieces[x].len && !havepiece(x)){
+                               /* backoff from this piece for a while */
+                               if(x == workpiece)
+                                       workpiece = -1;
                        }
                        break;
                case 0x08:      // Cancel <index> <begin> <length>
@@ -535,10 +595,11 @@ void
 server(void)
 {
        char addr[64], adir[40], ldir[40];
-       int afd, lfd, dfd;
+       int afd, lfd, dfd, pid, nprocs;
        NetConnInfo *ni;
 
        afd = -1;
+       nprocs = 0;
        for(port=6881; port<6890; port++){
                snprint(addr, sizeof(addr), "tcp!*!%d", port);
                if((afd = announce(addr, adir)) >= 0)
@@ -555,7 +616,13 @@ server(void)
                        fprint(2, "listen: %r");
                        break;
                }
-               if(rfork(RFFDG|RFPROC|RFMEM)){
+               while(nprocs >= SRVPROCS)
+                       if(waitpid() > 0)
+                               nprocs--;
+               nprocs++;
+               if(pid = rfork(RFFDG|RFPROC|RFMEM)){
+                       if(pid < 0)
+                               nprocs--;
                        close(lfd);
                        continue;
                }
@@ -565,8 +632,8 @@ server(void)
                }
                ni = getnetconninfo(ldir, dfd);
                peer(dfd, 1, ni ? ni->raddr : "???");
-               if(ni) freenetconninfo(ni);     
-               break;
+               if(ni) freenetconninfo(ni);
+               break;  
        }
        exits(0);
 }
@@ -574,11 +641,12 @@ server(void)
 void
 client(char *ip, char *port)
 {
-       static Dict *peers;
+       static Dict *peerqh, *peerqt;
        static QLock peerslk;
-       int try, fd;
+       static int nprocs;
        char *addr;
        Dict *d;
+       int fd;
 
        if(ip == nil || port == nil)
                return;
@@ -586,7 +654,7 @@ client(char *ip, char *port)
        d = mallocz(sizeof(*d) + 64, 1);
        snprint(addr = d->str, 64, "tcp!%s!%s", ip, port);
        qlock(&peerslk);
-       if(dlook(peers, addr)){
+       if(dlook(peerqh, addr)){
                qunlock(&peerslk);
                free(d);
                return;
@@ -594,23 +662,44 @@ client(char *ip, char *port)
        d->len = strlen(addr);
        d->typ = 'd';
        d->val = d;
-       d->next = peers;
-       peers = d;
+       /* enqueue to front */
+       if((d->next = peerqh) == nil)
+               peerqt = d;
+       peerqh = d;
+       if(nprocs >= CLIPROCS){
+               qunlock(&peerslk);
+               return;
+       }
+       nprocs++;
        qunlock(&peerslk);
-
-       if(debug) fprint(2, "client %s\n", addr);
-
-       if(rfork(RFFDG|RFPROC|RFMEM))
+       if(rfork(RFFDG|RFPROC|RFMEM|RFNOWAIT))
                return;
-       for(try = 0; try < 10; try++){
+
+       for(;;){
+               qlock(&peerslk);
+               /* dequeue and put to tail */
+               if(d = peerqh){
+                       if((peerqh = d->next) == nil)
+                               peerqt = nil;
+                       d->next = nil;
+                       if(peerqt)
+                               peerqt->next = d;
+                       else
+                               peerqh = d;
+                       peerqt = d;
+               } else
+                       nprocs--;
+               qunlock(&peerslk);
+               if(d == nil)
+                       exits(0);
+               addr = d->str;
+               if(debug) fprint(2, "client %s\n", addr);
                if((fd = dial(addr, nil, nil, nil)) >= 0){
-                       if(!peer(fd, 0, addr))
-                               break;
+                       peer(fd, 0, addr);
                        close(fd);
                }
-               sleep((1000<<try)+nrand(5000));
+               sleep(1000+nrand(5000));
        }
-       exits(0);
 }
 
 int
@@ -638,6 +727,10 @@ hopen(char *url, ...)
                close(ctlfd);
                return -1;
        }
+       if(useragent != nil && useragent[0] != '\0'){
+               n = snprint(buf, sizeof buf, "useragent %s", useragent);
+               write(ctlfd, buf, n);
+       }
        snprint(buf, sizeof buf, "%s/%d/body", mntweb, conn);
        if((fd = open(buf, OREAD)) < 0)
                goto ErrOut;
@@ -646,39 +739,105 @@ hopen(char *url, ...)
 }
 
 void
-tracker(char *url)
+webseed(Dict *w, File *f)
 {
-       static Dict *trackers;
-       static QLock trackerslk;
+       int fd, err, n, m, o, p, x, y;
+       uchar buf[MAXIO];
+       vlong off, len;
+       Dict *w0;
+       char *s;
 
-       Dict *d, *l;
-       int n, fd;
-       char *p;
-
-       if(url == nil)
+       if(w == nil || f == nil || finished())
                return;
-
-       qlock(&trackerslk);
-       if(dlook(trackers, url)){
-               qunlock(&trackerslk);
+       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){
+Error:
+               if(debug) fprint(2, "webseed %s %s: %r\n", w->str, f->name);
+               if(finished())
+                       exits(0);
+               if((w = w->next) == w0)
+                       exits(0);
+               goto Retry;
        }
-       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);
+       err = 0;
+       off = f->off;
+       len = f->len;
+       while(len > 0 && !finished()){
+               m = sizeof(buf);
+               if(len < m)
+                       m = len;
+               if((n = read(fd, buf, m)) <= 0)
+                       break;
+
+               x = off / blocksize;
+               p = off - (vlong)x*blocksize;
+               off += n;
+               len -= n;
+               y = off / blocksize;
+
+               o = 0;
+               while(n > 0){
+                       m = pieces[x].len - p;
+                       if(m > n)
+                               m = n;
+                       if((havemap[x>>3] & (0x80>>(x&7))) == 0)
+                               rwpiece(1, x, buf+o, m, p);
+                       if(x == y)
+                               break;
+                       o += m;
+                       n -= m;
+                       p = 0;
+                       if(havepiece(x++))
+                               continue;
+                       if(++err > 10){
+                               close(fd);
+                               werrstr("file corrupted");
+                               goto Error;
+                       }
+               }
+       }
+       havepiece(off / blocksize);
+       havepiece(f->off / blocksize);
+       close(fd);
+       exits(0);
+}
+
+void
+clients4(uchar *p, int len)
+{
+       char ip[16], port[6];
+
+       while(len >= 6){
+               len -= 6;
+               snprint(ip, sizeof(ip), "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
+               snprint(port, sizeof(port), "%d", p[4]<<8 | p[5]);
+               p += 6;
+               client(ip, port);
+       }
+}
+
+void
+webtracker(char *url)
+{
+       char *event, *p;
+       Dict *d, *l;
+       int n, fd;
 
        if(rfork(RFPROC|RFMEM))
                return;
+       if(debug) fprint(2, "webtracker %s\n", url);
 
+       event = "&event=started";
        for(;;){
                vlong up, down, left;
 
@@ -690,31 +849,28 @@ tracker(char *url)
 
                d = nil;
                if((fd = hopen("%s?info_hash=%.*H&peer_id=%.*H&port=%d&"
-                       "uploaded=%lld&downloaded=%lld&left=%lld&"
-                       "compact=1",
+                       "uploaded=%lld&downloaded=%lld&left=%lld&compact=1&no_peer_id=1%s",
                        url, sizeof(infohash), infohash, sizeof(peerid), peerid, port,
-                       up, down, left)) >= 0){
+                       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);
+               } else if(debug) fprint(2, "tracker %s: %r\n", url);
+               /* check errors and warnings */
+               if(p = dstr(dlook(d, "failure reason"))) {
+                       if(debug)
+                               fprint(2, "tracker failure: %s\n", p);
+                       exits(0);
                }
+               if(p = dstr(dlook(d, "warning message")))
+                       if(debug)
+                               fprint(2, "tracker warning: %s\n", p);
                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)
+                       if(l->typ == 's')
+                               clients4((uchar*)l->str, l->len);
+                       else for(; l && l->typ == 'l'; l = l->next)
                                client(dstr(dlook(l->val, "ip")), dstr(dlook(l->val, "port")));
                }
                n = 0;
@@ -727,6 +883,135 @@ tracker(char *url)
        }
 }
 
+int
+udpaddr(char addr[64], int naddr, char *url)
+{
+       int port;
+       char *x;
+
+       if((url = strchr(url, ':')) == nil)
+               return -1;
+       url++;
+       while(*url == '/')
+               url++;
+       if(x = strchr(url, ':')){
+               port = atoi(x+1);
+       } else {
+               port = 80;
+               if((x = strchr(url, '/')) == nil)
+                       x = strchr(url, 0);
+       }
+       snprint(addr, naddr, "udp!%.*s!%d", (int)(x-url), url, port);
+       return 0;
+}
+
+void
+udptracker(char *url)
+{
+       int fd, event, n, m, a, i;
+       int transid, interval;
+       vlong connid;
+       uchar buf[MAXIO];
+       char addr[64];
+
+       if(udpaddr(addr, sizeof(addr), url) < 0)
+               return;
+       if(rfork(RFPROC|RFMEM))
+               return;
+       if(debug) fprint(2, "udptracker %s\n", addr);
+
+       event = 1;
+       for(;;){
+               alarm(30000);
+               if((fd = dial(addr, 0, 0, 0)) < 0)
+                       goto Sleep;
+
+               /* connect */
+               transid = rand();
+               n = pack(buf, sizeof(buf), "vll", 0x41727101980LL, 0, transid);
+               if(write(fd, buf, n) != n)
+                       goto Sleep;
+               for(;;){
+                       if((n = read(fd, buf, sizeof(buf))) <= 0)
+                               goto Sleep;
+                       if(unpack(buf, n, "llv", &a, &i, &connid) < 0)
+                               continue;
+                       if(a == 0 && i == transid)
+                               break;
+               }
+               alarm(0);
+
+               /* announce */
+               transid = rand();
+               lock(&stats);
+               n = pack(buf, sizeof(buf), "vll**vvvl____llw",
+                       connid, 1, transid,
+                       sizeof(infohash), infohash,
+                       sizeof(peerid), peerid,
+                       stats.down,
+                       stats.left,
+                       stats.up,
+                       event,
+                       0, -1,
+                       port);
+               unlock(&stats);
+
+               interval = 0;
+               alarm(30000);
+               if(write(fd, buf, n) != n)
+                       goto Sleep;
+               for(;;){
+                       if((n = read(fd, buf, sizeof(buf))) <= 0)
+                               goto Sleep;
+                       if((m = unpack(buf, n, "lll________", &a, &i, &interval)) < 0)
+                               continue;
+                       if(a == 1 && i == transid){
+                               clients4(buf+m, n - m);
+                               break;
+                       }
+               }
+               event = 0;
+Sleep:
+               alarm(0);
+               if(fd >= 0)
+                       close(fd);
+               if(interval < 10 | interval > 60*60)
+                       interval = 2*60;
+               sleep(interval * 1000 + nrand(5000));
+       }
+}
+
+void
+tracker(char *url)
+{
+       static Dict *trackers;
+       static QLock trackerslk;
+       Dict *d;
+       int n;
+
+       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(!cistrncmp(url, "udp:", 4))
+               udptracker(url);
+       else
+               webtracker(url);
+}
+
 int
 Hfmt(Fmt *f)
 {
@@ -737,7 +1022,7 @@ Hfmt(Fmt *f)
        else
                e = s + strlen((char*)s);
        for(; s < e; s++)
-               if(fmtprint(f, ((*s >= '0' && *s <= '9') || 
+               if(fmtprint(f, *s && ((*s >= '0' && *s <= '9') || 
                        (*s >= 'a' && *s <= 'z') ||
                        (*s >= 'A' && *s <= 'Z') || 
                        strchr(".-_~", *s)) ? "%c" : "%%%.2x", *s) < 0)
@@ -746,8 +1031,129 @@ Hfmt(Fmt *f)
 }
 
 int
-killnote(void *, char *)
+mktorrent(int fd, Dict *alist, Dict *wlist)
 {
+       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;
+       }
+       for(blocksize = 256*1024;;blocksize<<=1){
+               npieces = (d->length + blocksize-1) / blocksize;
+               if(npieces <= 8*1024 || blocksize >= 2*1024*1024)
+                       break;
+       }
+
+       /*
+        * keys in dictionaries have to be ordered alphabetically
+        */
+       print("d8:announce%ld:%s", strlen(alist->str), alist->str);
+       if(alist->next){
+               print("13:announce-listl");
+               print("l%ld:%se", strlen(alist->str), alist->str);
+               for(alist = alist->next; alist; alist = alist->next)
+                       print("l%ld:%se", strlen(alist->str), alist->str);
+               print("e");
+       }
+
+       print("4:infod");
+       print("6:lengthi%llde", d->length);
+       print("4:name%ld:%s", strlen(d->name), d->name);
+       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--;
+       }
+       if(npieces){
+               werrstr("read failed: %r");
+               return -1;
+       }
+       free(b);
+       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("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
+catch(void *, char *msg)
+{
+       if(strstr(msg, "alarm"))
+               return 1;
        postnote(PNGROUP, killgroup, "kill");
        return 0;
 }
@@ -755,25 +1161,49 @@ killnote(void *, char *)
 void
 usage(void)
 {
-       fprint(2, "usage: %s [ -vsdp ] [ -m mtpt ] [ torrentfile ]\n", argv0);
+       fprint(2, "usage: %s [ -vsdpc ] [ -m mtpt ] [ -t tracker-url ] "
+                 "[ -w webseed-url ] [ -i peerid ] [ -A useragent ] [ file ]\n", argv0);
        exits("usage");
 }
 
+Dict*
+scons(char *s, Dict *t)
+{
+       Dict *l;
+
+       if(s == nil)
+               return t;
+       for(l = t; l; l = l->next)
+               if(strcmp(l->str, s) == 0)
+                       return t;
+       l = mallocz(sizeof(*l) + strlen(s)+1, 1);
+       l->next = t;
+       strcpy(l->str, s);
+       return l;
+}
+
 void
 main(int argc, char *argv[])
 {
-       Dict *info, *torrent, *d;
-       File **fp, *f;
+       int sflag, pflag, vflag, cflag, fd, i, n;
+       Dict *alist, *wlist, *info, *torrent, *d, *l;
        char *p, *s, *e;
-       int fd, i, n;
+       File **fp, *f;
        vlong len;
 
        fmtinstall('H', Hfmt);
-
+       alist = wlist = nil;
+       sflag = pflag = vflag = cflag = 0;
        ARGBEGIN {
        case 'm':
                mntweb = EARGF(usage());
                break;
+       case 't':
+               alist = scons(EARGF(usage()), alist);
+               break;
+       case 'w':
+               wlist = scons(EARGF(usage()), wlist);
+               break;
        case 's':
                sflag = 1;
                break;
@@ -783,20 +1213,62 @@ main(int argc, char *argv[])
        case 'v':
                vflag = 1;
                break;
+       case 'c':
+               cflag = 1;
+               break;
        case 'd':
                debug++;
                break;
+       case 'i':
+               strncpy((char*)peerid, EARGF(usage()), sizeof(peerid));
+               break;
+       case 'A':
+               useragent = EARGF(usage());
+               break;
        default:
                usage();
        } ARGEND;
 
+       if((s = getenv("NPROC")) != 0){
+               if((nproc = atoi(s)) <= 0)
+                       nproc = 1;
+               free(s);
+       }
+
        fd = 0;
        if(*argv)
                if((fd = open(*argv, OREAD)) < 0)
-                       sysfatal("open torrent: %r");
+                       sysfatal("open: %r");
+       if(cflag){
+               if(alist == nil)
+                       alist = scons(deftrack, alist);
+               if(mktorrent(fd, alist, wlist) < 0)
+                       sysfatal("%r");
+               exits(0);
+       }
        if((n = readall(fd, &p)) <= 0)
                sysfatal("read torrent: %r");
        bparse(p, p+n, &torrent);
+
+       alist = scons(dstr(dlook(torrent, "announce")), alist);
+       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(d = dlook(torrent, "url-list")){
+               if(d->typ == 's')
+                       wlist = scons(dstr(d->val), wlist);
+               else for(l = d; l && l->typ == 'l'; l = l->next)
+                       wlist = scons(dstr(l->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)
@@ -830,10 +1302,14 @@ main(int argc, char *argv[])
        for(f = files; f; f = f->next){
                if(f->name == nil || f->len <= 0)
                        sysfatal("bogus file entry in meta info");
-               if(vflag) fprint(pflag ? 2 : 1, "%s\n", f->name);
-               if((f->fd = open(f->name, ORDWR)) < 0)
-                       if((f->fd = create(f->name, ORDWR, 0666)) < 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(s, ORDWR, 0666)) < 0)
                                sysfatal("create: %r");
+               }
                f->off = len;
                len += f->len;
        }
@@ -863,33 +1339,44 @@ main(int argc, char *argv[])
        if(len)
                sysfatal("pieces do not match file length");
 
-       for(i = 0; i<npieces; i++)
-               havepiece(i);
+       for(i=0; i<nproc; i++){
+               switch(rfork(RFPROC|RFMEM)){
+               case -1:
+                       sysfatal("fork: %r");
+               case 0:
+                       for(; i<npieces; i+=nproc)
+                               havepiece(i);
+                       exits(0);
+               }
+       }
+       while(waitpid() >= 0)
+               ;
 
-       srand(time(0));
-       atnotify(killnote, 1);
+       srand(truerand());
+       atnotify(catch, 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++)
+               if(peerid[0] == 0)
+                       strncpy((char*)peerid, "-NF9001-", 9);
+               for(i=sizeof(peerid)-1; i >= 0 && peerid[i] == 0; i--)
                        peerid[i] = nrand(10)+'0';
                server();
-               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));
+               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);