<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv=Content-Type content="text/html; charset=utf8">
<title>/usr/web/sources/contrib/cinap_lenrek/torrent.c - Plan 9 from Bell Labs</title>
<!-- THIS FILE IS AUTOMATICALLY GENERATED. -->
<!-- EDIT sources.tr INSTEAD. -->
</meta>
</head>
<body>
<p style="margin-top: 0; margin-bottom: 0.17in"></p>
<p style="line-height: 1.2em; margin-left: 1.00in; text-indent: 0.00in; margin-right: 1.00in; margin-top: 0; margin-bottom: 0; text-align: center;">
<span style="font-size: 10pt"><a href="/plan9/">Plan 9 from Bell Labs</a>&rsquo;s /usr/web/sources/contrib/cinap_lenrek/torrent.c</span></p>
<p style="margin-top: 0; margin-bottom: 0.17in"></p>
<p style="margin-top: 0; margin-bottom: 0.17in"></p>
<center><font size=-1>
Copyright © 2009 Alcatel-Lucent.<br />
Distributed under the
<a href="/plan9/license.html">Lucent Public License version 1.02</a>.
<br />
<a href="/plan9/download.html">Download the Plan 9 distribution.</a>
</font>
</center>
<p style="margin-top: 0; margin-bottom: 0.17in"></p>
<table width="100%" cellspacing=0 border=0><tr><td align="center">
<table cellspacing=0 cellpadding=5 bgcolor="#eeeeff"><tr><td align="left">
<pre>
<!-- END HEADER -->
#include &lt;u.h&gt;
#include &lt;libc.h&gt;
#include &lt;mp.h&gt;
#include &lt;libsec.h&gt;

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

struct Dict
{
       Dict    *val;
       Dict    *next;
       char    *start, *end;
       int     len;
       char    typ;    // i, d, s, l
       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,
       SRVPROCS = 16,
       CLIPROCS = 16,
};

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;

int
finished(void)
{
       return nhavepieces &gt;= npieces;
}

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

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

       *dp = nil;
       if(s &gt;= e)
               return e;

       t = *s;
       switch(t){
       case 'd':
       case 'l':
               x = s++;
               d = nil;
               while(s &lt; 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-&gt;typ = t;
                       d-&gt;start = x;
                       if(s &lt; e){
                               s = bparse(s, e, &amp;d-&gt;val);
                               dp = &amp;d-&gt;next;
                               d-&gt;end = s;
                       }
                       x = s;
               }
               if(d)
                       d-&gt;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)) &lt; 0)
                       return e;
               s = x + n;
               if((s &gt; e) || (s &lt; x)){
                       n = e - x;
                       s = e;
               }
               t = 's';
       }
       d = mallocz(sizeof(*d) + n+1, 1);
       d-&gt;typ = t;
       memmove(d-&gt;str, x, d-&gt;len = n);
       d-&gt;str[n] = 0;
       *dp = d;
       return s;
}

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

Dict*
dlook(Dict *d, char *s)
{
       for(; d &amp;&amp; d-&gt;typ == 'd'; d = d-&gt;next)
               if(d-&gt;len &amp;&amp; strcmp(d-&gt;str, s) == 0)
                       return d-&gt;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)) &lt;= 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 &lt;= 0 || poff &gt;= pieces[index].len)
               return 0;
       if(len+poff &gt; pieces[index].len)
               len = pieces[index].len - poff;
       off = (vlong)index * blocksize;
       off += poff;
       for(f = files; f; f = f-&gt;next)
               if((f-&gt;off+f-&gt;len) &gt; off)
                       break;
       off -= f-&gt;off;
       n = ((off + len) &gt; f-&gt;len) ? f-&gt;len - off : len;
       if((n = (wr ? pwrite(f-&gt;fd, data, n, off) : pread(f-&gt;fd, data, n, off))) &lt;= 0)
               return -1;
       if((m = rwpiece(wr, index, data + n, len - n, poff + n)) &lt; 0)
               return -1;
       return n+m;
}

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

       m = 0x80&gt;&gt;(x&amp;7);
       if(havemap[x&gt;&gt;3] &amp; 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(&amp;stats);
       if((havemap[x&gt;&gt;3] &amp; m) == 0){
               havemap[x&gt;&gt;3] |= m;
               nhavepieces++;
               stats.left -= pieces[x].len;
       }
       unlock(&amp;stats);
       return 1;
}

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

       r = -1;
       k = 0;
       for(i = 0; i&lt;nhavemap; i++){
               if(map[i] == 0)
                       continue;
               for(x = i&lt;&lt;3, m = 0x80; m; m &gt;&gt;= 1, x++){
                       if((~map[i] | havemap[i]) &amp; 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 &gt; e) goto Err;
                       *va_arg(arg, int*) = *s++;
                       break;
               case 'w':
                       if(s+2 &gt; e) goto Err;
                       *va_arg(arg, int*) = s[0]&lt;&lt;8 | s[1];
                       s += 2;
                       break;
               case 'l':
                       if(s+4 &gt; e) goto Err;
                       *va_arg(arg, int*) = s[0]&lt;&lt;24 | s[1]&lt;&lt;16 | s[2]&lt;&lt;8 | s[3];
                       s += 4;
                       break;
               case 'v':
                       if(s+4 &gt; e) goto Err;
                       *va_arg(arg, vlong*) =
                               (vlong)s[0]&lt;&lt;56 |
                               (vlong)s[1]&lt;&lt;48 |
                               (vlong)s[2]&lt;&lt;40 |
                               (vlong)s[3]&lt;&lt;32 |
                               (vlong)s[4]&lt;&lt;24 |
                               (vlong)s[5]&lt;&lt;16 |
                               (vlong)s[6]&lt;&lt;8 |
                               (vlong)s[7];
                       s += 8;
                       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;
       vlong v;
       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 &gt; e) goto Err;
                       *s++ = i &amp; 0xFF;
                       break;
               case 'w':
                       i = va_arg(arg, int);
                       if(s+2 &gt; e) goto Err;
                       *s++ = (i&gt;&gt;8) &amp; 0xFF;
                       *s++ = i &amp; 0xFF;
                       break;
               case 'l':
                       i = va_arg(arg, int);
                       if(s+4 &gt; e) goto Err;
                       *s++ = (i&gt;&gt;24) &amp; 0xFF;
                       *s++ = (i&gt;&gt;16) &amp; 0xFF;
                       *s++ = (i&gt;&gt;8) &amp; 0xFF;
                       *s++ = i &amp; 0xFF;
                       break;
               case 'v':
                       v = va_arg(arg, vlong);
                       if(s+8 &gt; e) goto Err;
                       *s++ = (v&gt;&gt;56) &amp; 0xFF;
                       *s++ = (v&gt;&gt;48) &amp; 0xFF;
                       *s++ = (v&gt;&gt;40) &amp; 0xFF;
                       *s++ = (v&gt;&gt;32) &amp; 0xFF;
                       *s++ = (v&gt;&gt;24) &amp; 0xFF;
                       *s++ = (v&gt;&gt;16) &amp; 0xFF;
                       *s++ = (v&gt;&gt;8) &amp; 0xFF;
                       *s++ = v &amp; 0xFF;
                       break;
               case '*':
                       i = va_arg(arg, int);
                       if(s+i &gt; e) goto Err;
                       memmove(s, va_arg(arg, void*), 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&lt;2; i++){
               if((incoming &amp;&amp; i) || (!incoming &amp;&amp; !i)){
                       if(debug) fprint(2, "peer %s: -&gt; 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 &amp;&amp; !i) || (!incoming &amp;&amp; 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: &lt;- 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: -&gt; 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&lt;nhavemap; i++){
                       if(told[i] != havemap[i]){
                               for(x = i&lt;&lt;3, m = 0x80; m; m &gt;&gt;= 1, x++){
                                       if((~havemap[i] | told[i] | map[i]) &amp; m)
                                               continue;
                                       told[i] |= m;
                                       if(debug) fprint(2, "peer %s: -&gt; 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 &amp;&amp; (map[i] &amp; ~havemap[i])){
                               mewant = 1;
                               if(debug) fprint(2, "peer %s: -&gt; interested\n", addr);
                               n = pack(buf, sizeof(buf), "lb", 1, 0x02);
                               if(write(fd, buf, n) != n)
                                       goto Out;
                       }
               }
               if(!hechoking &amp;&amp; mewant){
                       x = workpiece;
                       if(x &gt;= 0 &amp;&amp; pieces[x].brk &lt; pieces[x].len)
                               {}
                       else x = pickpiece(map);
                       if(x &gt;= 0){
                               o = pieces[x].brk;
                               l = pieces[x].len - o;
                               if(l &gt; MAXIO)
                                       l = MAXIO;
                               if(debug) fprint(2, "peer %s: -&gt; 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 &amp;&amp; hewant){
                       mechoking = 0;
                       if(debug) fprint(2, "peer %s: -&gt; 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", &amp;n);
               if(n &lt; 0 || n &gt; 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: &lt;- choke\n", addr);
                       break;
               case 0x01:      // Unchoke
                       hechoking = 0;
                       if(debug) fprint(2, "peer %s: &lt;- unchoke\n", addr);
                       break;
               case 0x02:      // Interested
                       hewant = 1;
                       if(debug) fprint(2, "peer %s: &lt;- interested\n", addr);
                       break;
               case 0x03:      // Notinterested
                       hewant = 0;
                       if(debug) fprint(2, "peer %s: &lt;- notinterested\n", addr);
                       break;
               case 0x04:      // Have &lt;piceindex&gt;
                       if(unpack(p, n, "l", &amp;x) &lt; 0)
                               goto Out;
                       if(debug) fprint(2, "peer %s: &lt;- have %d\n", addr, x);
                       if(x &lt; 0 || x &gt;= npieces)
                               continue;
                       map[x&gt;&gt;3] |= 0x80&gt;&gt;(x&amp;7);
                       break;
               case 0x05:      // Bitfield
                       if(debug) fprint(2, "peer %s: &lt;- bitfield %d\n", addr, n);
                       if(n != nhavemap)
                               continue;
                       memmove(map, p, n);
                       break;
               case 0x06:      // Request &lt;index&gt; &lt;begin&gt; &lt;length&gt;
                       if(unpack(p, n, "lll", &amp;x, &amp;o, &amp;l) &lt; 0)
                               goto Out;
                       if(debug) fprint(2, "peer %s: &lt;- request %d %d %d\n", addr, x, o, l);
                       if(x &lt; 0 || x &gt;= npieces)
                               continue;
                       if(!hewant || mechoking || (~havemap[x&gt;&gt;3]&amp;(0x80&gt;&gt;(x&amp;7))))
                               continue;
                       if(debug) fprint(2, "peer %s: -&gt; piece %d %d\n", addr, x, o);
                       n = 4+1+4+4;
                       if(l &gt; MAXIO)
                               l = MAXIO;
                       if((l = rwpiece(0, x, buf + n, l, o)) &lt;= 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(&amp;stats);
                       stats.up += n;
                       unlock(&amp;stats);
                       break;
               case 0x07:      // Piece &lt;index&gt; &lt;begin&gt; &lt;block&gt;
                       if(unpack(p, n, "ll", &amp;x, &amp;o) != 8)
                               goto Out;
                       p += 8;
                       n -= 8;
                       lock(&amp;stats);
                       stats.down += n;
                       unlock(&amp;stats);
                       if(debug) fprint(2, "peer %s: &lt;- piece %d %d %d\n", addr, x, o, n);
                       if(x &lt; 0 || x &gt;= npieces)
                               continue;
                       if((pieces[x].brk != o) || (havemap[x&gt;&gt;3]&amp;(0x80&gt;&gt;(x&amp;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 &lt;index&gt; &lt;begin&gt; &lt;length&gt;
                       if(unpack(p, n, "lll", &amp;x, &amp;o, &amp;l) &lt; 0)
                               goto Out;
                       if(debug) fprint(2, "peer %s: &lt;- cancel %d %d %d\n", addr, x, o, l);
                       break;
               case 0x09:      // Port &lt;port&gt;
                       if(unpack(p, n, "l", &amp;x) &lt; 0)
                               goto Out;
                       if(debug) fprint(2, "peer %s: &lt;- 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, pid, nprocs;
       NetConnInfo *ni;

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

void
client(char *ip, char *port)
{
       static Dict *peerqh, *peerqt;
       static QLock peerslk;
       static int nprocs;
       char *addr;
       Dict *d;
       int fd;

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

       d = mallocz(sizeof(*d) + 64, 1);
       snprint(addr = d-&gt;str, 64, "tcp!%s!%s", ip, port);
       qlock(&amp;peerslk);
       if(dlook(peerqh, addr)){
               qunlock(&amp;peerslk);
               free(d);
               return;
       }
       d-&gt;len = strlen(addr);
       d-&gt;typ = 'd';
       d-&gt;val = d;
       /* enqueue to front */
       if((d-&gt;next = peerqh) == nil)
               peerqt = d;
       peerqh = d;
       if(nprocs &gt;= CLIPROCS){
               qunlock(&amp;peerslk);
               return;
       }
       nprocs++;
       qunlock(&amp;peerslk);
       if(rfork(RFFDG|RFPROC|RFMEM|RFNOWAIT))
               return;

       for(;;){
               qlock(&amp;peerslk);
               /* dequeue and put to tail */
               if(d = peerqh){
                       if((peerqh = d-&gt;next) == nil)
                               peerqt = nil;
                       d-&gt;next = nil;
                       if(peerqt)
                               peerqt-&gt;next = d;
                       else
                               peerqh = d;
                       peerqt = d;
               } else
                       nprocs--;
               qunlock(&amp;peerslk);
               if(d == nil)
                       exits(0);
               addr = d-&gt;str;
               if(debug) fprint(2, "client %s\n", addr);
               if((fd = dial(addr, nil, nil, nil)) &gt;= 0){
                       peer(fd, 0, addr);
                       close(fd);
               }
               sleep(1000+nrand(5000));
       }
}

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)) &lt; 0)
               return -1;
       if((n = read(ctlfd, buf, sizeof buf-1)) &lt;= 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)) &lt; 0)
               goto ErrOut;
       close(ctlfd);
       return fd;
}

void
webseed(Dict *w, File *f)
{
       int fd, err, n, m, o, p, x, y;
       uchar buf[MAXIO];
       vlong off, len;
       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-&gt;str, f-&gt;name);
       s = strrchr(w-&gt;str, '/');
       if(s &amp;&amp; s[1] == 0)
               fd = hopen("%s%s", w-&gt;str, f-&gt;name);
       else
               fd = hopen("%s", w-&gt;str);
       if(fd &lt; 0){
Error:
               if(debug) fprint(2, "webseed %s %s: %r\n", w-&gt;str, f-&gt;name);
               if(finished())
                       exits(0);
               if((w = w-&gt;next) == w0)
                       exits(0);
               goto Retry;
       }

       err = 0;
       off = f-&gt;off;
       len = f-&gt;len;
       while(len &gt; 0 &amp;&amp; !finished()){
               m = sizeof(buf);
               if(len &lt; m)
                       m = len;
               if((n = read(fd, buf, m)) &lt;= 0)
                       break;

               x = off / blocksize;
               p = off - (vlong)x*blocksize;
               off += n;
               len -= n;
               y = off / blocksize;

               o = 0;
               while(n &gt; 0){
                       m = pieces[x].len - p;
                       if(m &gt; n)
                               m = n;
                       if((havemap[x&gt;&gt;3] &amp; (0x80&gt;&gt;(x&amp;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 &gt; 10){
                               close(fd);
                               werrstr("file corrupted");
                               goto Error;
                       }
               }
       }
       havepiece(off / blocksize);
       havepiece(f-&gt;off / blocksize);
       close(fd);
       exits(0);
}

void
clients4(uchar *p, int len)
{
       char ip[16], port[6];

       while(len &gt;= 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]&lt;&lt;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 = "&amp;event=started";
       for(;;){
               vlong up, down, left;

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

               d = nil;
               if((fd = hopen("%s?info_hash=%.*H&amp;peer_id=%.*H&amp;port=%d&amp;"
                       "uploaded=%lld&amp;downloaded=%lld&amp;left=%lld&amp;compact=1&amp;no_peer_id=1%s",
                       url, sizeof(infohash), infohash, sizeof(peerid), peerid, port,
                       up, down, left, event)) &gt;= 0){
                       event = "";
                       n = readall(fd, &amp;p);
                       close(fd);
                       bparse(p, p+n, &amp;d);
                       free(p);
               } else if(debug) fprint(2, "tracker %s: %r\n", url);
               if(l = dlook(d, "peers")){
                       if(l-&gt;typ == 's')
                               clients4((uchar*)l-&gt;str, l-&gt;len);
                       else for(; l &amp;&amp; l-&gt;typ == 'l'; l = l-&gt;next)
                               client(dstr(dlook(l-&gt;val, "ip")), dstr(dlook(l-&gt;val, "port")));
               }
               n = 0;
               if(p = dstr(dlook(d, "interval")))
                       n = atoi(p);
               if(n &lt; 10 | n &gt; 60*60)
                       n = 2*60;
               freedict(d);
               sleep(n * 1000 + nrand(5000));
       }
}

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) &lt; 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)) &lt; 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))) &lt;= 0)
                               goto Sleep;
                       if(unpack(buf, n, "llv", &amp;a, &amp;i, &amp;connid) &lt; 0)
                               continue;
                       if(a == 0 &amp;&amp; i == transid)
                               break;
               }
               alarm(0);

               /* announce */
               transid = rand();
               lock(&amp;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(&amp;stats);

               interval = 0;
               alarm(30000);
               if(write(fd, buf, n) != n)
                       goto Sleep;
               for(;;){
                       if((n = read(fd, buf, sizeof(buf))) &lt;= 0)
                               goto Sleep;
                       if((m = unpack(buf, n, "lll________", &amp;a, &amp;i, &amp;interval)) &lt; 0)
                               continue;
                       if(a == 1 &amp;&amp; i == transid){
                               clients4(buf+m, n - m);
                               break;
                       }
               }
               event = 0;
Sleep:
               alarm(0);
               if(fd &gt;= 0)
                       close(fd);
               if(interval &lt; 10 | interval &gt; 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(&amp;trackerslk);
       if(dlook(trackers, url)){
               qunlock(&amp;trackerslk);
               return;
       }
       n = strlen(url);
       d = mallocz(sizeof(*d) + n+1, 1);
       strcpy(d-&gt;str, url);
       d-&gt;len = n;
       d-&gt;typ = 'd';
       d-&gt;val = d;
       d-&gt;next = trackers;
       trackers = d;
       url = d-&gt;str;
       qunlock(&amp;trackerslk);
       if(!cistrncmp(url, "udp:", 4))
               udptracker(url);
       else
               webtracker(url);
}

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

int
mktorrent(int fd, Dict *alist, Dict *wlist)
{
       uchar *b, h[20];
       Dir *d;
       int n;

       if((d = dirfstat(fd)) == nil)
               return -1;
       if(d-&gt;qid.type &amp; QTDIR){
               free(d);
               werrstr("file is a directory");
               return -1;
       }
       if(d-&gt;length == 0){
               free(d);
               werrstr("empty file");
               return -1;
       }
       npieces = 1;
       for(blocksize = 256*1024;;blocksize&lt;&lt;=1){
               npieces = (d-&gt;length + blocksize-1) / blocksize;
               if(npieces &lt;= 8*1024 || blocksize &gt;= 2*1024*1024)
                       break;
       }

       /*
        * keys in dictionaries have to be ordered alphabetically
        */
       print("d8:announce%ld:%s", strlen(alist-&gt;str), alist-&gt;str);
       if(alist-&gt;next){
               print("13:announce-listl");
               print("l%ld:%se", strlen(alist-&gt;str), alist-&gt;str);
               for(alist = alist-&gt;next; alist; alist = alist-&gt;next)
                       print("l%ld:%se", strlen(alist-&gt;str), alist-&gt;str);
               print("e");
       }

       print("4:infod");
       print("6:lengthi%llde", d-&gt;length);
       print("4:name%ld:%s", strlen(d-&gt;name), d-&gt;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)) &gt; 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-&gt;next){
                       print("8:url-listl");
                       for(; wlist; wlist = wlist-&gt;next)
                               print("%ld:%s", strlen(wlist-&gt;str), wlist-&gt;str);
                       print("e");
               } else
                       print("8:url-list%ld:%s", strlen(wlist-&gt;str), wlist-&gt;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)) &lt; 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(&amp;r, s);
               if(r == ' ')
                       r = 0xa0;
               if((n + runelen(r)) &gt;= l){
                       l += 64;
                       d = realloc(d, l);
               }
               n += runetochar(d + n, &amp;r);
       }
       d[n] = 0;
       return cleanname(d);
}

int
catch(void *, char *msg)
{
       if(strstr(msg, "alarm"))
               return 1;
       postnote(PNGROUP, killgroup, "kill");
       return 0;
}

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

Dict*
scons(char *s, Dict *t)
{
       Dict *l;

       if(s == nil)
               return t;
       for(l = t; l; l = l-&gt;next)
               if(strcmp(l-&gt;str, s) == 0)
                       return t;
       l = mallocz(sizeof(*l) + strlen(s)+1, 1);
       l-&gt;next = t;
       strcpy(l-&gt;str, s);
       return l;
}

void
main(int argc, char *argv[])
{
       int sflag, pflag, vflag, cflag, fd, i, n;
       Dict *alist, *wlist, *info, *torrent, *d, *l;
       char *p, *s, *e;
       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;
       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)) &lt; 0)
                       sysfatal("open: %r");
       if(cflag){
               if(alist == nil)
                       alist = scons(deftrack, alist);
               if(mktorrent(fd, alist, wlist) &lt; 0)
                       sysfatal("%r");
               exits(0);
       }
       if((n = readall(fd, &amp;p)) &lt;= 0)
               sysfatal("read torrent: %r");
       bparse(p, p+n, &amp;torrent);

       alist = scons(dstr(dlook(torrent, "announce")), alist);
       for(d = dlook(torrent, "announce-list"); d &amp;&amp; d-&gt;typ == 'l'; d = d-&gt;next)
               for(l = d-&gt;val; l &amp;&amp; l-&gt;typ == 'l'; l = l-&gt;next)
                       alist = scons(dstr(l-&gt;val), alist);

       if(d = dlook(torrent, "url-list")){
               if(d-&gt;typ == 's')
                       wlist = scons(dstr(d-&gt;val), wlist);
               else for(l = d; l &amp;&amp; l-&gt;typ == 'l'; l = l-&gt;next)
                       wlist = scons(dstr(l-&gt;val), wlist);
               /* make wlist into a ring */
               for(l = wlist; l &amp;&amp; l-&gt;next; l = l-&gt;next)
                       ;
               if(l) l-&gt;next = wlist;
       }

       if(alist == nil &amp;&amp; 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-&gt;start; d &amp;&amp; d-&gt;typ == 'd'; d = d-&gt;next)
               e = d-&gt;end;
       sha1((uchar*)s, e - s, (uchar*)infohash, nil);
       free(p);

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

                       if((s = dstr(dlook(d-&gt;val, "length"))) == nil)
                               continue;
                       f = mallocz(sizeof(*f), 1);
                       f-&gt;len = atoll(s);
                       f-&gt;name = dstr(dlook(info, "name"));
                       for(di = dlook(d-&gt;val, "path"); di &amp;&amp; di-&gt;typ == 'l'; di = di-&gt;next)
                               if(s = dstr(di-&gt;val))
                                       f-&gt;name = f-&gt;name ? smprint("%s/%s", f-&gt;name, s) : s;
                       *fp = f;
                       fp = &amp;f-&gt;next;
               }
       } else if(s = dstr(dlook(info, "length"))){
               f = mallocz(sizeof(*f), 1);
               f-&gt;len = atoll(s);
               f-&gt;name = dstr(dlook(info, "name"));
               *fp = f;
       }
       len = 0;
       for(f = files; f; f = f-&gt;next){
               if(f-&gt;name == nil || f-&gt;len &lt;= 0)
                       sysfatal("bogus file entry in meta info");
               s = fixnamedup(f-&gt;name);
               if(vflag) fprint(pflag ? 2 : 1, "%s\n", s);
               if((f-&gt;fd = open(s, ORDWR)) &lt; 0){
                       if(mkdirs(s) &lt; 0)
                               sysfatal("mkdirs: %r");
                       if((f-&gt;fd = create(s, ORDWR, 0666)) &lt; 0)
                               sysfatal("create: %r");
               }
               f-&gt;off = len;
               len += f-&gt;len;
       }
       if(len &lt;= 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)) &lt;= 0)
               sysfatal("bogus piece length in meta info");
       d = dlook(info, "pieces");
       if(d == nil || d-&gt;typ != 's' || d-&gt;len &lt;= 0 || d-&gt;len % 20)
               sysfatal("bad or no pices in meta info");
       npieces = d-&gt;len / 20;
       pieces = mallocz(sizeof(Piece) * npieces, 1);
       nhavemap = (npieces+7) / 8;
       havemap = mallocz(nhavemap, 1);
       for(i = 0; i&lt;npieces; i++){
               pieces[i].hash = (uchar*)d-&gt;str + i*20;
               if(len &lt; 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&lt;npieces; i++)
               havepiece(i);

       srand(time(0));
       atnotify(catch, 1);
       switch(i = rfork(RFPROC|RFMEM|RFNOTEG)){
       case -1:
               sysfatal("fork: %r");
       case 0:
               memmove(peerid, "-NF9001-", 8);
               for(i=8; i&lt;sizeof(peerid); i++)
                       peerid[i] = nrand(10)+'0';
               server();
               for(; alist; alist = alist-&gt;next)
                       tracker(alist-&gt;str);
               for(f = files, l = wlist; f &amp;&amp; l; f = f-&gt;next, l = l-&gt;next)
                       webseed(l, f);
               while(waitpid() != -1)
                       ;
               break;
       default:
               killgroup = i;
               do {
                       sleep(1000);
                       if(pflag)
                               print("%d %d\n", nhavepieces, npieces);
               } while(!finished() || sflag);
       }
       postnote(PNGROUP, killgroup, "kill");
       exits(0);
}
<!-- BEGIN TAIL -->
</pre>
</td></tr></table>
</td></tr></table>
<p style="margin-top: 0; margin-bottom: 0.17in"></p>
<p style="line-height: 1.2em; margin-left: 1.00in; text-indent: 0.00in; margin-right: 1.00in; margin-top: 0; margin-bottom: 0; text-align: center;">
<span style="font-size: 10pt"></span></p>
<p style="margin-top: 0; margin-bottom: 0.50in"></p>
<p style="margin-top: 0; margin-bottom: 0.33in"></p>
<center><table border="0"><tr>
<td valign="middle"><a href="http://www.alcatel-lucent.com/"><img border="0" src="/plan9/img/logo_ft.gif" alt="Bell Labs" />
</a></td>
<td valign="middle"><a href="http://www.opensource.org"><img border="0" alt="OSI certified" src="/plan9/img/osi-certified-60x50.gif" />
</a></td>
<td><img style="padding-right: 45px;" alt="Powered by Plan 9" src="/plan9/img/power36.gif" />
</td>
</tr></table></center>
<p style="margin-top: 0; margin-bottom: 0.17in"></p>
<center>
<span style="font-size: 10pt">(<a href="/plan9/">Return to Plan 9 Home Page</a>)</span>
</center>
<p style="margin-top: 0; margin-bottom: 0.17in"></p>
<center><font size=-1>
<span style="font-size: 10pt"><a href="http://www.lucent.com/copyright.html">Copyright</a></span>
<span style="font-size: 10pt">© 2009 Alcatel-Lucent.</span>
<span style="font-size: 10pt">All Rights Reserved.</span>
<br />
<span style="font-size: 10pt">Comments to</span>
<span style="font-size: 10pt"><a href="mailto:[email protected]">[email protected]</a>.</span>
</font></center>
</body>
</html>