Commits

Jeff Sickel committed dcd4ffa Merge

merge devip

Comments (0)

Files changed (29)

+# This file lists people who have contributed code to
+# drawterm, as recorded by the Mercurial and CVS logs.
+# The list is incomplete but a start.
+# My thanks to the people not listed here too.
+
+Andrey Mirtchovski
+Erik Quanstrom <quanstro@quanstro.net>
+David du Colombier <0intro@gmail.com>
+Russ Cox <rsc@swtch.com>
+Yaroslav <yarikos@gmail.com>
 	libdraw/libdraw.a\
 	gui-$(GUI)/libgui.a\
 	libc/libc.a\
+	libip/libip.a\
 
 # stupid gcc
 LIBS=$(LIBS1) $(LIBS1) $(LIBS1) libmachdep.a
 libc/libc.a:
 	(cd libc; $(MAKE))
 
+libip/libip.a:
+	(cd libip; $(MAKE))
+
 gui-$(GUI)/libgui.a:
 	(cd gui-$(GUI); $(MAKE))
 static char	*system;
 static int	cflag;
 extern int	dbg;
-extern char*	base;	// fs base for devroot
+extern char*   base;   // fs base for devroot 
 
 static char	*srvname = "ncpu";
 static char	*ealgs = "rc4_256 sha1";
 	if(mountfactotum() < 0){
 		if(secstoreserver == nil)
 			secstoreserver = authserver;
-	        if(havesecstore(secstoreserver, user)){
-	                s = secstorefetch(secstoreserver, user, nil);
-	                if(s){
-	                        if(strlen(s) >= sizeof secstorebuf)
-	                                sysfatal("secstore data too big");
-	                        strcpy(secstorebuf, s);
-	                }
-	        }
+	 	if(havesecstore(secstoreserver, user)){
+			s = secstorefetch(secstoreserver, user, nil);
+			if(s){
+				if(strlen(s) >= sizeof secstorebuf)
+					sysfatal("secstore data too big");
+				strcpy(secstorebuf, s);
+			}
+		}
 	}
 
 	if((err = rexcall(&data, system, srvname)))
 
 	if((afd = open("/mnt/factotum/ctl", ORDWR)) >= 0)
 		return p9anyfactotum(fd, afd);
+	werrstr("");
 
 	if(readstr(fd, buf, sizeof buf) < 0)
 		fatal(1, "cannot read p9any negotiation");
 	if(write(fd, buf2, strlen(buf2)+1) != strlen(buf2)+1)
 		fatal(1, "cannot write user/domain choice in p9any");
 	if(v2){
-		if(readstr(fd, buf, sizeof buf) != 3)
-			fatal(1, "cannot read OK in p9any");
+		if(readstr(fd, buf, sizeof buf) < 0)
+			fatal(1, "cannot read OK in p9any: got %d %s", n, buf);
 		if(memcmp(buf, "OK\0", 3) != 0)
 			fatal(1, "did not get OK in p9any");
 	}

exportfs/exportfs.c

 	if(messagesize == 0){
 		messagesize = iounit(netfd);
 		if(messagesize == 0)
-			messagesize = 8192+IOHDRSZ;
+			messagesize = 8*8192+IOHDRSZ;
 	}
 
 	Workq = emallocz(sizeof(Fsrpc)*Nr_workbufs);

exportfs/exportsrv.c

 
 int iounit(int x)
 {
-	return 8192+IOHDRSZ;
+	return 8*8192+IOHDRSZ;
 }
 
 void*
 		a[2] = text;
 		a[3] = compoundtext;
 
-		XChangeProperty(xd, xe->requestor, xe->property, xe->target,
-			8, PropModeReplace, (uchar*)a, sizeof a);
+		XChangeProperty(xd, xe->requestor, xe->property, XA_ATOM,
+			32, PropModeReplace, (uchar*)a, sizeof a);
 	}else if(xe->target == XA_STRING || xe->target == utf8string || xe->target == text || xe->target == compoundtext){
 	text:
 		/* if the target is STRING we're supposed to reply with Latin1 XXX */
+enum 
+{
+	IPaddrlen=	16,
+	IPv4addrlen=	4,
+	IPv4off=	12,
+};
+
+uchar*	defmask(uchar*);
+void	maskip(uchar*, uchar*, uchar*);
+int	eipfmt(Fmt*);
+int	isv4(uchar*);
+vlong	parseip(uchar*, char*);
+vlong	parseipmask(uchar*, char*);
+char*	v4parseip(uchar*, char*);
+char*	v4parsecidr(uchar*, uchar*, char*);
+
+void	hnputv(void*, uvlong);
+void	hnputl(void*, uint);
+void	hnputs(void*, ushort);
+uvlong	nhgetv(void*);
+uint	nhgetl(void*);
+ushort	nhgets(void*);
+
+int	v6tov4(uchar*, uchar*);
+void	v4tov6(uchar*, uchar*);
+
+#define	ipcmp(x, y) memcmp(x, y, IPaddrlen)
+#define	ipmove(x, y) memmove(x, y, IPaddrlen)
+
+extern uchar IPv4bcast[IPaddrlen];
+extern uchar IPv4bcastobs[IPaddrlen];
+extern uchar IPv4allsys[IPaddrlen];
+extern uchar IPv4allrouter[IPaddrlen];
+extern uchar IPnoaddr[IPaddrlen];
+extern uchar v4prefix[IPaddrlen];
+extern uchar IPallbits[IPaddrlen];
+
+#define CLASS(p) ((*(uchar*)(p))>>6)

kern/devfs-posix.c

 
 	uif = c->aux;
 
+	fspath(c, 0, old);
+	if(~d.mode != 0 && (int)(d.mode&0777) != (int)(stbuf.st_mode&0777)) {
+		if(chmod(old, d.mode&0777) < 0)
+			error(strerror(errno));
+		uif->mode &= ~0777;
+		uif->mode |= d.mode&0777;
+	}
+
 	if(d.name[0] && strcmp(d.name, lastelem(c)) != 0) {
 		fspath(c, 0, old);
 		strcpy(new, old);
 			error(strerror(errno));
 	}
 
-	fspath(c, 0, old);
-	if(~d.mode != 0 && (int)(d.mode&0777) != (int)(stbuf.st_mode&0777)) {
-		if(chmod(old, d.mode&0777) < 0)
-			error(strerror(errno));
-		uif->mode &= ~0777;
-		uif->mode |= d.mode&0777;
-	}
 /*
 	p = name2pass(gid, d.gid);
 	if(p == 0)

kern/devip-posix.c

 #include <netinet/in.h>
 #include <netinet/tcp.h>
 #include <netdb.h>
+#include <arpa/inet.h>
 
 #include "u.h"
 #include "lib.h"
 #include "dat.h"
 #include "fns.h"
 #include "error.h"
+#include "ip.h"
 
 #include "devip.h"
 
 #undef accept
 #undef bind
 
+static int
+family(unsigned char *addr)
+{
+	if(isv4(addr))
+		return AF_INET;
+	return AF_INET6;
+}
+
 void
 osipinit(void)
 {
 }
 
 int
-so_socket(int type)
+so_socket(int type, unsigned char *addr)
 {
 	int fd, one;
 
 		break;
 	}
 
-	fd = socket(AF_INET, type, 0);
+	fd = socket(family(addr), type, 0);
 	if(fd < 0)
 		oserror();
 
 	return fd;
 }
 
+void
+so_connect(int fd, unsigned char *raddr, unsigned short rport)
+{
+	struct sockaddr_storage ss;
 
-void
-so_connect(int fd, unsigned long raddr, unsigned short rport)
-{
-	struct sockaddr_in sin;
+	memset(&ss, 0, sizeof(ss));
 
-	memset(&sin, 0, sizeof(sin));
-	sin.sin_family = AF_INET;
-	hnputs(&sin.sin_port, rport);
-	hnputl(&sin.sin_addr.s_addr, raddr);
+	ss.ss_family = family(raddr);
 
-	if(connect(fd, (struct sockaddr*)&sin, sizeof(sin)) < 0)
+	switch(ss.ss_family){
+	case AF_INET:
+		hnputs(&((struct sockaddr_in*)&ss)->sin_port, rport);
+		v6tov4((unsigned char*)&((struct sockaddr_in*)&ss)->sin_addr.s_addr, raddr);
+		break;
+	case AF_INET6:
+		hnputs(&((struct sockaddr_in6*)&ss)->sin6_port, rport);
+		memcpy(&((struct sockaddr_in6*)&ss)->sin6_addr.s6_addr, raddr, sizeof(struct in6_addr));
+		break;
+	}
+
+	if(connect(fd, (struct sockaddr*)&ss, sizeof(ss)) < 0)
 		oserror();
 }
 
 void
-so_getsockname(int fd, unsigned long *laddr, unsigned short *lport)
+so_getsockname(int fd, unsigned char *laddr, unsigned short *lport)
 {
 	socklen_t len;
-	struct sockaddr_in sin;
+	struct sockaddr_storage ss;
 
-	len = sizeof(sin);
-	if(getsockname(fd, (struct sockaddr*)&sin, &len) < 0)
+	len = sizeof(ss);
+	if(getsockname(fd, (struct sockaddr*)&ss, &len) < 0)
 		oserror();
 
-	if(sin.sin_family != AF_INET || len != sizeof(sin))
-		error("not AF_INET");
-
-	*laddr = nhgetl(&sin.sin_addr.s_addr);
-	*lport = nhgets(&sin.sin_port);
+	switch(ss.ss_family){
+	case AF_INET:
+		v4tov6(laddr, (unsigned char*)&((struct sockaddr_in*)&ss)->sin_addr.s_addr);
+		*lport = nhgets(&((struct sockaddr_in*)&ss)->sin_port);
+		break;
+	case AF_INET6:
+		memcpy(laddr, &((struct sockaddr_in6*)&ss)->sin6_addr.s6_addr, sizeof(struct in6_addr));
+		*lport = nhgets(&((struct sockaddr_in6*)&ss)->sin6_port);
+		break;
+	default:
+		error("not AF_INET or AF_INET6");
+	}
 }
 
 void
 }
 
 int
-so_accept(int fd, unsigned long *raddr, unsigned short *rport)
+so_accept(int fd, unsigned char *raddr, unsigned short *rport)
 {
 	int nfd;
 	socklen_t len;
-	struct sockaddr_in sin;
+	struct sockaddr_storage ss;
 
-	len = sizeof(sin);
-	nfd = accept(fd, (struct sockaddr*)&sin, &len);
+	len = sizeof(ss);
+	nfd = accept(fd, (struct sockaddr*)&ss, &len);
 	if(nfd < 0)
 		oserror();
 
-	if(sin.sin_family != AF_INET || len != sizeof(sin))
-		error("not AF_INET");
-
-	*raddr = nhgetl(&sin.sin_addr.s_addr);
-	*rport = nhgets(&sin.sin_port);
+	switch(ss.ss_family){
+	case AF_INET:
+		v4tov6(raddr, (unsigned char*)&((struct sockaddr_in*)&ss)->sin_addr.s_addr);
+		*rport = nhgets(&((struct sockaddr_in*)&ss)->sin_port);
+		break;
+	case AF_INET6:
+		memcpy(raddr, &((struct sockaddr_in6*)&ss)->sin6_addr.s6_addr, sizeof(struct in6_addr));
+		*rport = nhgets(&((struct sockaddr_in6*)&ss)->sin6_port);
+		break;
+	default:
+		error("not AF_INET or AF_INET6");
+	}
 	return nfd;
 }
 
 void
-so_bind(int fd, int su, unsigned short port)
+so_bind(int fd, int su, unsigned short port, unsigned char *addr)
 {
 	int i, one;
-	struct sockaddr_in sin;
+	struct sockaddr_storage ss;
 
 	one = 1;
 	if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char*)&one, sizeof(one)) < 0){
 
 	if(su) {
 		for(i = 600; i < 1024; i++) {
-			memset(&sin, 0, sizeof(sin));
-			sin.sin_family = AF_INET;
-			sin.sin_port = i;
+			memset(&ss, 0, sizeof(ss));
+			ss.ss_family = family(addr);
 
-			if(bind(fd, (struct sockaddr*)&sin, sizeof(sin)) >= 0)	
+			switch(ss.ss_family){
+			case AF_INET:
+				((struct sockaddr_in*)&ss)->sin_port = i;
+				break;
+			case AF_INET6:
+				((struct sockaddr_in6*)&ss)->sin6_port = i;
+				break;
+			}
+
+			if(bind(fd, (struct sockaddr*)&ss, sizeof(ss)) >= 0)	
 				return;
 		}
 		oserror();
 	}
 
-	memset(&sin, 0, sizeof(sin));
-	sin.sin_family = AF_INET;
-	hnputs(&sin.sin_port, port);
+	memset(&ss, 0, sizeof(ss));
+	ss.ss_family = family(addr);
 
-	if(bind(fd, (struct sockaddr*)&sin, sizeof(sin)) < 0)
+	switch(ss.ss_family){
+	case AF_INET:
+		hnputs(&((struct sockaddr_in*)&ss)->sin_port, port);
+		break;
+	case AF_INET6:
+		hnputs(&((struct sockaddr_in6*)&ss)->sin6_port, port);
+		break;
+	}
+
+	if(bind(fd, (struct sockaddr*)&ss, sizeof(ss)) < 0)
 		oserror();
 }
 
 char*
 hostlookup(char *host)
 {
-	char buf[100];
+	char buf[INET6_ADDRSTRLEN];
 	uchar *p;
 	struct hostent *he;
+	struct addrinfo *result;
 
 	he = gethostbyname(host);
 	if(he != 0 && he->h_addr_list[0]) {
 		p = (uchar*)he->h_addr_list[0];
 		sprint(buf, "%ud.%ud.%ud.%ud", p[0], p[1], p[2], p[3]);
+	} else if(getaddrinfo(host, NULL, NULL, &result) == 0) {
+		switch (result->ai_family) {
+		case AF_INET:
+			inet_ntop(AF_INET, &((struct sockaddr_in*)result->ai_addr)->sin_addr, buf, sizeof(buf));
+			break;
+		case AF_INET6:
+			inet_ntop(AF_INET6, &((struct sockaddr_in6*)result->ai_addr)->sin6_addr, buf, sizeof(buf));
+			break;
+		default:
+			return nil;
+		}
 	} else
-		strcpy(buf, host);
+		return nil;
 
 	return strdup(buf);
 }

kern/devip-win32.c

 #include <windows.h>
+#include <ws2tcpip.h>
 #include "u.h"
 #include "lib.h"
 #include "dat.h"
 #include "fns.h"
 #include "error.h"
+#include "ip.h"
 
 #include "devip.h"
 
 #undef accept
 #undef bind
 
+static int
+family(unsigned char *addr)
+{
+	if(isv4(addr))
+		return AF_INET;
+	return AF_INET6;
+}
+
 void
 osipinit(void)
 {
 }
 
 int
-so_socket(int type)
+so_socket(int type, unsigned char *addr)
 {
 	int fd, one;
 
 		break;
 	}
 
-	fd = socket(AF_INET, type, 0);
+	fd = socket(family(addr), type, 0);
 	if(fd < 0)
 		oserror();
 
 
 
 void
-so_connect(int fd, unsigned long raddr, unsigned short rport)
+so_connect(int fd, unsigned char *raddr, unsigned short rport)
 {
-	struct sockaddr_in sin;
+	struct sockaddr_storage ss;
 
-	memset(&sin, 0, sizeof(sin));
-	sin.sin_family = AF_INET;
-	hnputs(&sin.sin_port, rport);
-	hnputl(&sin.sin_addr.s_addr, raddr);
+	memset(&ss, 0, sizeof(ss));
 
-	if(connect(fd, (struct sockaddr*)&sin, sizeof(sin)) < 0)
+	ss.ss_family = family(raddr);
+
+	switch(ss.ss_family){
+	case AF_INET:
+		hnputs(&((struct sockaddr_in*)&ss)->sin_port, rport);
+		v6tov4((unsigned char*)&((struct sockaddr_in*)&ss)->sin_addr.s_addr, raddr);
+		break;
+	case AF_INET6:
+		hnputs(&((struct sockaddr_in6*)&ss)->sin6_port, rport);
+		memcpy(&((struct sockaddr_in6*)&ss)->sin6_addr.s6_addr, raddr, sizeof(struct in6_addr));
+		break;
+	}
+
+	if(connect(fd, (struct sockaddr*)&ss, sizeof(ss)) < 0)
 		oserror();
 }
 
 void
-so_getsockname(int fd, unsigned long *laddr, unsigned short *lport)
+so_getsockname(int fd, unsigned char *laddr, unsigned short *lport)
 {
 	int len;
-	struct sockaddr_in sin;
+	struct sockaddr_storage ss;
 
-	len = sizeof(sin);
-	if(getsockname(fd, (struct sockaddr*)&sin, &len) < 0)
+	len = sizeof(ss);
+	if(getsockname(fd, (struct sockaddr*)&ss, &len) < 0)
 		oserror();
 
-	if(sin.sin_family != AF_INET || len != sizeof(sin))
-		error("not AF_INET");
-
-	*laddr = nhgetl(&sin.sin_addr.s_addr);
-	*lport = nhgets(&sin.sin_port);
+	switch(ss.ss_family){
+	case AF_INET:
+		v4tov6(laddr, (unsigned char*)&((struct sockaddr_in*)&ss)->sin_addr.s_addr);
+		*lport = nhgets(&((struct sockaddr_in*)&ss)->sin_port);
+		break;
+	case AF_INET6:
+		memcpy(laddr, &((struct sockaddr_in6*)&ss)->sin6_addr.s6_addr, sizeof(struct in6_addr));
+		*lport = nhgets(&((struct sockaddr_in6*)&ss)->sin6_port);
+		break;
+	default:
+		error("not AF_INET or AF_INET6");
+	}
 }
 
 void
 }
 
 int
-so_accept(int fd, unsigned long *raddr, unsigned short *rport)
+so_accept(int fd, unsigned char *raddr, unsigned short *rport)
 {
-	int nfd, len;
-	struct sockaddr_in sin;
+	int nfd;
+	int len;
+	struct sockaddr_storage ss;
 
-	len = sizeof(sin);
-	nfd = accept(fd, (struct sockaddr*)&sin, &len);
+	len = sizeof(ss);
+	nfd = accept(fd, (struct sockaddr*)&ss, &len);
 	if(nfd < 0)
 		oserror();
 
-	if(sin.sin_family != AF_INET || len != sizeof(sin))
-		error("not AF_INET");
-
-	*raddr = nhgetl(&sin.sin_addr.s_addr);
-	*rport = nhgets(&sin.sin_port);
+	switch(ss.ss_family){
+	case AF_INET:
+		v4tov6(raddr, (unsigned char*)&((struct sockaddr_in*)&ss)->sin_addr.s_addr);
+		*rport = nhgets(&((struct sockaddr_in*)&ss)->sin_port);
+		break;
+	case AF_INET6:
+		memcpy(raddr, &((struct sockaddr_in6*)&ss)->sin6_addr.s6_addr, sizeof(struct in6_addr));
+		*rport = nhgets(&((struct sockaddr_in6*)&ss)->sin6_port);
+		break;
+	default:
+		error("not AF_INET or AF_INET6");
+	}
 	return nfd;
 }
 
 void
-so_bind(int fd, int su, unsigned short port)
+so_bind(int fd, int su, unsigned short port, unsigned char *addr)
 {
 	int i, one;
-	struct sockaddr_in sin;
+	struct sockaddr_storage ss;
 
 	one = 1;
 	if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char*)&one, sizeof(one)) < 0){
 		oserrstr();
-		print("setsockopt: %s", up->errstr);
+		print("setsockopt: %r");
 	}
 
 	if(su) {
 		for(i = 600; i < 1024; i++) {
-			memset(&sin, 0, sizeof(sin));
-			sin.sin_family = AF_INET;
-			sin.sin_port = i;
+			memset(&ss, 0, sizeof(ss));
+			ss.ss_family = family(addr);
 
-			if(bind(fd, (struct sockaddr*)&sin, sizeof(sin)) >= 0)	
+			switch(ss.ss_family){
+			case AF_INET:
+				((struct sockaddr_in*)&ss)->sin_port = i;
+				break;
+			case AF_INET6:
+				((struct sockaddr_in6*)&ss)->sin6_port = i;
+				break;
+			}
+
+			if(bind(fd, (struct sockaddr*)&ss, sizeof(ss)) >= 0)	
 				return;
 		}
 		oserror();
 	}
 
-	memset(&sin, 0, sizeof(sin));
-	sin.sin_family = AF_INET;
-	hnputs(&sin.sin_port, port);
+	memset(&ss, 0, sizeof(ss));
+	ss.ss_family = family(addr);
 
-	if(bind(fd, (struct sockaddr*)&sin, sizeof(sin)) < 0)
+	switch(ss.ss_family){
+	case AF_INET:
+		hnputs(&((struct sockaddr_in*)&ss)->sin_port, port);
+		break;
+	case AF_INET6:
+		hnputs(&((struct sockaddr_in6*)&ss)->sin6_port, port);
+		break;
+	}
+
+	if(bind(fd, (struct sockaddr*)&ss, sizeof(ss)) < 0)
 		oserror();
 }
 
 {
 	char buf[100];
 	uchar *p;
-	HOSTENT *he;
+	struct hostent *he;
 
 	he = gethostbyname(host);
 	if(he != 0 && he->h_addr_list[0]) {
 #include "dat.h"
 #include "fns.h"
 #include "error.h"
+#include "ip.h"
 
 #include "devip.h"
 
-void		hnputl(void *p, unsigned long v);
-void		hnputs(void *p, unsigned short v);
-unsigned long	nhgetl(void *p);
-unsigned short	nhgets(void *p);
-unsigned long	parseip(char *to, char *from);
 void	csclose(Chan*);
 long	csread(Chan*, void*, long, vlong);
 long	cswrite(Chan*, void*, long, vlong);
 #define CONV(x) 	((int)(((x).path >> 4)&0xfff))
 #define PROTO(x) 	((int)(((x).path >> 16)&0xff))
 #define QID(p, c, y) 	(((p)<<16) | ((c)<<4) | (y))
+#define ipzero(x)	memset(x, 0, IPaddrlen)
 
 typedef struct Proto	Proto;
 typedef struct Conv	Conv;
 	int	perm;
 	char	owner[KNAMELEN];
 	char*	state;
-	ulong	laddr;
+	uchar	laddr[IPaddrlen];
 	ushort	lport;
-	ulong	raddr;
+	uchar	raddr[IPaddrlen];
 	ushort	rport;
 	int	restricted;
 	char	cerr[KNAMELEN];
 
 static	int	np;
 static	Proto	proto[MAXPROTO];
-int	eipfmt(Fmt*);
 
 static	Conv*	protoclone(Proto*, char*, int);
 static	void	setladdr(Conv*);
 ipopen(Chan *c, int omode)
 {
 	Proto *p;
-	ulong raddr;
+	uchar raddr[IPaddrlen];
 	ushort rport;
 	int perm, sfd;
 	Conv *cv, *lcv;
 	case Qlisten:
 		p = &proto[PROTO(c->qid)];
 		lcv = p->conv[CONV(c->qid)];
-		sfd = so_accept(lcv->sfd, &raddr, &rport);
+		sfd = so_accept(lcv->sfd, raddr, &rport);
 		cv = protoclone(p, up->user, sfd);
 		if(cv == 0) {
 			close(sfd);
 			error(Enodev);
 		}
-		cv->raddr = raddr;
+		ipmove(cv->raddr, raddr);
 		cv->rport = rport;
 		setladdr(cv);
 		cv->state = "Established";
 		strcpy(cc->owner, "network");
 		cc->perm = 0666;
 		cc->state = "Closed";
-		cc->laddr = 0;
-		cc->raddr = 0;
+		ipzero(cc->laddr);
+		ipzero(cc->raddr);
 		cc->lport = 0;
 		cc->rport = 0;
 		close(cc->sfd);
 	int r;
 	Conv *c;
 	Proto *x;
-	uchar ip[4];
+	uchar ip[IPaddrlen];
 	char buf[128], *p;
 
 /*print("ipread %s %lux\n", c2name(ch), (long)ch->qid.path);*/
 		return readstr(offset, p, n, buf);
 	case Qremote:
 		c = proto[PROTO(ch->qid)].conv[CONV(ch->qid)];
-		hnputl(ip, c->raddr);
+		ipmove(ip, c->raddr);
 		sprint(buf, "%I!%d\n", ip, c->rport);
 		return readstr(offset, p, n, buf);
 	case Qlocal:
 		c = proto[PROTO(ch->qid)].conv[CONV(ch->qid)];
-		hnputl(ip, c->laddr);
+		ipmove(ip, c->laddr);
 		sprint(buf, "%I!%d\n", ip, c->lport);
 		return readstr(offset, p, n, buf);
 	case Qstatus:
 static void
 setladdr(Conv *c)
 {
-	so_getsockname(c->sfd, &c->laddr, &c->lport);
+	so_getsockname(c->sfd, c->laddr, &c->lport);
 }
 
 static void
 	if(c->restricted == 0 && c->lport == 0)
 		return;
 
-	so_bind(c->sfd, c->restricted, c->lport);
+	if(c->sfd == -1)
+		c->sfd = so_socket(c->p->stype, c->laddr);
+
+	so_bind(c->sfd, c->restricted, c->lport, c->laddr);
 }
 
 static void
 setladdrport(Conv *c, char *str)
 {
-	char *p, addr[4];
+	char *p;
+	uchar addr[IPaddrlen];
 
 	p = strchr(str, '!');
 	if(p == 0) {
 		p = str;
-		c->laddr = 0;
+		ipzero(c->laddr);
 	}
 	else {
 		*p++ = 0;
 		parseip(addr, str);
-		c->laddr = nhgetl((uchar*)addr);
+		ipmove(c->laddr, addr);
 	}
 	if(*p == '*')
 		c->lport = 0;
 static char*
 setraddrport(Conv *c, char *str)
 {
-	char *p, addr[4];
+	char *p;
+	uchar addr[IPaddrlen];
 
 	p = strchr(str, '!');
 	if(p == 0)
 		return "malformed address";
 	*p++ = 0;
 	parseip(addr, str);
-	c->raddr = nhgetl((uchar*)addr);
+	ipmove(c->raddr, addr);
 	c->rport = atoi(p);
 	p = strchr(p, '!');
 	if(p) {
 				setlport(c);
 				break;
 			}
+			if(c->sfd == -1)
+				c->sfd = so_socket(c->p->stype, c->raddr);
 			so_connect(c->sfd, c->raddr, c->rport);
 			setladdr(c);
 			c->state = "Established";
 	c->perm = 0660;
 	c->state = "Closed";
 	c->restricted = 0;
-	c->laddr = 0;
-	c->raddr = 0;
+	ipzero(c->laddr);
+	ipzero(c->raddr);
 	c->lport = 0;
 	c->rport = 0;
 	c->sfd = nfd;
-	if(nfd == -1)
-		c->sfd = so_socket(p->stype);
 
 	unlock(&c->r.lk);
 	unlock(&p->l);
 	return c;
 }
 
-enum
-{
-	Isprefix= 16,
-};
-
-uchar prefixvals[256] =
-{
-/*0x00*/ 0 | Isprefix,
-		   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-/*0x10*/	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-/*0x20*/	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-/*0x30*/	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-/*0x40*/	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-/*0x50*/	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-/*0x60*/	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-/*0x70*/	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-/*0x80*/ 1 | Isprefix,
-		   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-/*0x90*/	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-/*0xA0*/	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-/*0xB0*/	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-/*0xC0*/ 2 | Isprefix,
-		   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-/*0xD0*/	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-/*0xE0*/ 3 | Isprefix,
-		   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-/*0xF0*/ 4 | Isprefix,
-		   0, 0, 0, 0, 0, 0, 0, 
-/*0xF8*/ 5 | Isprefix,
-		   0, 0, 0, 
-/*0xFC*/ 6 | Isprefix,
-		   0,
-/*0xFE*/ 7 | Isprefix,
-/*0xFF*/ 8 | Isprefix,
-};
-
-int
-eipfmt(Fmt *f)
-{
-	char buf[5*8];
-	static char *efmt = "%.2lux%.2lux%.2lux%.2lux%.2lux%.2lux";
-	static char *ifmt = "%d.%d.%d.%d";
-	uchar *p, ip[16];
-	ulong ul;
-
-	switch(f->r) {
-	case 'E':		/* Ethernet address */
-		p = va_arg(f->args, uchar*);
-		snprint(buf, sizeof buf, efmt, p[0], p[1], p[2], p[3], p[4], p[5]);
-		return fmtstrcpy(f, buf);
-
-	case 'I':
-		ul = va_arg(f->args, ulong);
-		hnputl(ip, ul);
-		snprint(buf, sizeof buf, ifmt, ip[0], ip[1], ip[2], ip[3]);
-		return fmtstrcpy(f, buf);
-	}
-	return fmtstrcpy(f, "(eipfmt)");
-}
-
-void
-hnputl(void *p, unsigned long v)
-{
-	unsigned char *a;
-
-	a = p;
-	a[0] = v>>24;
-	a[1] = v>>16;
-	a[2] = v>>8;
-	a[3] = v;
-}
-
-void
-hnputs(void *p, unsigned short v)
-{
-	unsigned char *a;
-
-	a = p;
-	a[0] = v>>8;
-	a[1] = v;
-}
-
-unsigned long
-nhgetl(void *p)
-{
-	unsigned char *a;
-	a = p;
-	return (a[0]<<24)|(a[1]<<16)|(a[2]<<8)|(a[3]<<0);
-}
-
-unsigned short
-nhgets(void *p)
-{
-	unsigned char *a;
-	a = p;
-	return (a[0]<<8)|(a[1]<<0);
-}
-
-#define CLASS(p) ((*(unsigned char*)(p))>>6)
-
-unsigned long
-parseip(char *to, char *from)
-{
-	int i;
-	char *p;
-
-	p = from;
-	memset(to, 0, 4);
-	for(i = 0; i < 4 && *p; i++){
-		to[i] = strtoul(p, &p, 10);
-		if(*p != '.' && *p != 0){
-			memset(to, 0, 4);
-			return 0;
-		}
-		if(*p == '.')
-			p++;
-	}
-	switch(CLASS(to)){
-	case 0:	/* class A - 1 byte net */
-	case 1:
-		if(i == 3){
-			to[3] = to[2];
-			to[2] = to[1];
-			to[1] = 0;
-		} else if (i == 2){
-			to[3] = to[1];
-			to[1] = 0;
-		}
-		break;
-	case 2:	/* class B - 2 byte net */
-		if(i == 3){
-			to[3] = to[2];
-			to[2] = 0;
-		}
-		break;
-	}
-	return nhgetl(to);
-}
-
 void
 csclose(Chan *c)
 {
 	return 0;
 }
 
-static ulong
-lookuphost(char *s)
+static int
+lookuphost(char *s, uchar *to)
 {
-	char to[4];
-	ulong ip;
-
-	memset(to, 0, sizeof to);
+	ipzero(to);
+	if(parseip(to, s) != -1)
+		return 0;
+	if((s = hostlookup(s)) == nil)
+		return -1;
 	parseip(to, s);
-	ip = nhgetl(to);
-	if(ip != 0)
-		return ip;
-	if((s = hostlookup(s)) == nil)
-		return 0;
-	parseip(to, s);
-	ip = nhgetl(to);
 	free(s);
-	return ip;
+	return 0;
 }
 
 long
 {
 	char *f[4];
 	char *s, *ns;
-	ulong ip;
+	uchar ip[IPaddrlen];
 	int nf, port;
 
 	s = malloc(n+1);
 	if(port <= 0)
 		error("no translation for port found");
 
-	ip = lookuphost(f[1]);
-	if(ip == 0)
+	if(lookuphost(f[1], ip) < 0)
 		error("no translation for host found");
 
 	ns = smprint("/net/%s/clone %I!%d", f[0], ip, port);
 	S_UDP
 };
 
-int		so_socket(int type);
-void		so_connect(int, unsigned long, unsigned short);
-void		so_getsockname(int, unsigned long*, unsigned short*);
-void		so_bind(int, int, unsigned short);
+int		so_socket(int, unsigned char*);
+void		so_connect(int, unsigned char*, unsigned short);
+void		so_getsockname(int, unsigned char*, unsigned short*);
+void		so_bind(int, int, unsigned short, unsigned char*);
 void		so_listen(int);
 int		so_send(int, void*, int, int);
 int		so_recv(int, void*, int, int);
-int		so_accept(int, unsigned long*, unsigned short*);
+int		so_accept(int, unsigned char*, unsigned short*);
 int		so_getservbyname(char*, char*, char*);
 int		so_gethostbyname(char*, char**, int);
 
 Segment*	dupseg(Segment**, int, int);
 Segment*	newseg(int, ulong, ulong);
 Segment*	seg(Proc*, ulong, int);
-void		hnputv(void*, vlong);
-void		hnputl(void*, ulong);
-void		hnputs(void*, ushort);
-vlong		nhgetv(void*);
-ulong		nhgetl(void*);
-ushort		nhgets(void*);
 ulong	ticks(void);
 void	osproc(Proc*);
 void	osnewproc(Proc*);
File contents unchanged.

lib/codereview/codereview.cfg

+# defaultcc: plan9port-dev@googlegroups.com

lib/codereview/codereview.py

+# coding=utf-8
+# (The line above is necessary so that I can use 世界 in the
+# *comment* below without Python getting all bent out of shape.)
+
+# Copyright 2007-2009 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#	http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+'''Mercurial interface to codereview.appspot.com.
+
+To configure, set the following options in
+your repository's .hg/hgrc file.
+
+	[extensions]
+	codereview = /path/to/codereview.py
+
+	[codereview]
+	server = codereview.appspot.com
+
+The server should be running Rietveld; see http://code.google.com/p/rietveld/.
+
+In addition to the new commands, this extension introduces
+the file pattern syntax @nnnnnn, where nnnnnn is a change list
+number, to mean the files included in that change list, which
+must be associated with the current client.
+
+For example, if change 123456 contains the files x.go and y.go,
+"hg diff @123456" is equivalent to"hg diff x.go y.go".
+'''
+
+import sys
+
+if __name__ == "__main__":
+	print >>sys.stderr, "This is a Mercurial extension and should not be invoked directly."
+	sys.exit(2)
+
+# We require Python 2.6 for the json package.
+if sys.version < '2.6':
+	print >>sys.stderr, "The codereview extension requires Python 2.6 or newer."
+	print >>sys.stderr, "You are running Python " + sys.version
+	sys.exit(2)
+
+import json
+import os
+import re
+import stat
+import subprocess
+import threading
+import time
+
+from mercurial import commands as hg_commands
+from mercurial import util as hg_util
+
+defaultcc = None
+codereview_disabled = None
+real_rollback = None
+releaseBranch = None
+server = "codereview.appspot.com"
+server_url_base = None
+
+#######################################################################
+# Normally I would split this into multiple files, but it simplifies
+# import path headaches to keep it all in one file.  Sorry.
+# The different parts of the file are separated by banners like this one.
+
+#######################################################################
+# Helpers
+
+def RelativePath(path, cwd):
+	n = len(cwd)
+	if path.startswith(cwd) and path[n] == '/':
+		return path[n+1:]
+	return path
+
+def Sub(l1, l2):
+	return [l for l in l1 if l not in l2]
+
+def Add(l1, l2):
+	l = l1 + Sub(l2, l1)
+	l.sort()
+	return l
+
+def Intersect(l1, l2):
+	return [l for l in l1 if l in l2]
+
+#######################################################################
+# RE: UNICODE STRING HANDLING
+#
+# Python distinguishes between the str (string of bytes)
+# and unicode (string of code points) types.  Most operations
+# work on either one just fine, but some (like regexp matching)
+# require unicode, and others (like write) require str.
+#
+# As befits the language, Python hides the distinction between
+# unicode and str by converting between them silently, but
+# *only* if all the bytes/code points involved are 7-bit ASCII.
+# This means that if you're not careful, your program works
+# fine on "hello, world" and fails on "hello, 世界".  And of course,
+# the obvious way to be careful - use static types - is unavailable.
+# So the only way is trial and error to find where to put explicit
+# conversions.
+#
+# Because more functions do implicit conversion to str (string of bytes)
+# than do implicit conversion to unicode (string of code points),
+# the convention in this module is to represent all text as str,
+# converting to unicode only when calling a unicode-only function
+# and then converting back to str as soon as possible.
+
+def typecheck(s, t):
+	if type(s) != t:
+		raise hg_util.Abort("type check failed: %s has type %s != %s" % (repr(s), type(s), t))
+
+# If we have to pass unicode instead of str, ustr does that conversion clearly.
+def ustr(s):
+	typecheck(s, str)
+	return s.decode("utf-8")
+
+# Even with those, Mercurial still sometimes turns unicode into str
+# and then tries to use it as ascii.  Change Mercurial's default.
+def set_mercurial_encoding_to_utf8():
+	from mercurial import encoding
+	encoding.encoding = 'utf-8'
+
+set_mercurial_encoding_to_utf8()
+
+# Even with those we still run into problems.
+# I tried to do things by the book but could not convince
+# Mercurial to let me check in a change with UTF-8 in the
+# CL description or author field, no matter how many conversions
+# between str and unicode I inserted and despite changing the
+# default encoding.  I'm tired of this game, so set the default
+# encoding for all of Python to 'utf-8', not 'ascii'.
+def default_to_utf8():
+	import sys
+	stdout, __stdout__ = sys.stdout, sys.__stdout__
+	reload(sys)  # site.py deleted setdefaultencoding; get it back
+	sys.stdout, sys.__stdout__ = stdout, __stdout__
+	sys.setdefaultencoding('utf-8')
+
+default_to_utf8()
+
+#######################################################################
+# Status printer for long-running commands
+
+global_status = None
+
+def set_status(s):
+	# print >>sys.stderr, "\t", time.asctime(), s
+	global global_status
+	global_status = s
+
+class StatusThread(threading.Thread):
+	def __init__(self):
+		threading.Thread.__init__(self)
+	def run(self):
+		# pause a reasonable amount of time before
+		# starting to display status messages, so that
+		# most hg commands won't ever see them.
+		time.sleep(30)
+
+		# now show status every 15 seconds
+		while True:
+			time.sleep(15 - time.time() % 15)
+			s = global_status
+			if s is None:
+				continue
+			if s == "":
+				s = "(unknown status)"
+			print >>sys.stderr, time.asctime(), s
+
+def start_status_thread():
+	t = StatusThread()
+	t.setDaemon(True)  # allowed to exit if t is still running
+	t.start()
+
+#######################################################################
+# Change list parsing.
+#
+# Change lists are stored in .hg/codereview/cl.nnnnnn
+# where nnnnnn is the number assigned by the code review server.
+# Most data about a change list is stored on the code review server
+# too: the description, reviewer, and cc list are all stored there.
+# The only thing in the cl.nnnnnn file is the list of relevant files.
+# Also, the existence of the cl.nnnnnn file marks this repository
+# as the one where the change list lives.
+
+emptydiff = """Index: ~rietveld~placeholder~
+===================================================================
+diff --git a/~rietveld~placeholder~ b/~rietveld~placeholder~
+new file mode 100644
+"""
+
+class CL(object):
+	def __init__(self, name):
+		typecheck(name, str)
+		self.name = name
+		self.desc = ''
+		self.files = []
+		self.reviewer = []
+		self.cc = []
+		self.url = ''
+		self.local = False
+		self.web = False
+		self.copied_from = None	# None means current user
+		self.mailed = False
+		self.private = False
+		self.lgtm = []
+
+	def DiskText(self):
+		cl = self
+		s = ""
+		if cl.copied_from:
+			s += "Author: " + cl.copied_from + "\n\n"
+		if cl.private:
+			s += "Private: " + str(self.private) + "\n"
+		s += "Mailed: " + str(self.mailed) + "\n"
+		s += "Description:\n"
+		s += Indent(cl.desc, "\t")
+		s += "Files:\n"
+		for f in cl.files:
+			s += "\t" + f + "\n"
+		typecheck(s, str)
+		return s
+
+	def EditorText(self):
+		cl = self
+		s = _change_prolog
+		s += "\n"
+		if cl.copied_from:
+			s += "Author: " + cl.copied_from + "\n"
+		if cl.url != '':
+			s += 'URL: ' + cl.url + '	# cannot edit\n\n'
+		if cl.private:
+			s += "Private: True\n"
+		s += "Reviewer: " + JoinComma(cl.reviewer) + "\n"
+		s += "CC: " + JoinComma(cl.cc) + "\n"
+		s += "\n"
+		s += "Description:\n"
+		if cl.desc == '':
+			s += "\t<enter description here>\n"
+		else:
+			s += Indent(cl.desc, "\t")
+		s += "\n"
+		if cl.local or cl.name == "new":
+			s += "Files:\n"
+			for f in cl.files:
+				s += "\t" + f + "\n"
+			s += "\n"
+		typecheck(s, str)
+		return s
+
+	def PendingText(self, quick=False):
+		cl = self
+		s = cl.name + ":" + "\n"
+		s += Indent(cl.desc, "\t")
+		s += "\n"
+		if cl.copied_from:
+			s += "\tAuthor: " + cl.copied_from + "\n"
+		if not quick:
+			s += "\tReviewer: " + JoinComma(cl.reviewer) + "\n"
+			for (who, line) in cl.lgtm:
+				s += "\t\t" + who + ": " + line + "\n"
+			s += "\tCC: " + JoinComma(cl.cc) + "\n"
+		s += "\tFiles:\n"
+		for f in cl.files:
+			s += "\t\t" + f + "\n"
+		typecheck(s, str)
+		return s
+
+	def Flush(self, ui, repo):
+		if self.name == "new":
+			self.Upload(ui, repo, gofmt_just_warn=True, creating=True)
+		dir = CodeReviewDir(ui, repo)
+		path = dir + '/cl.' + self.name
+		f = open(path+'!', "w")
+		f.write(self.DiskText())
+		f.close()
+		if sys.platform == "win32" and os.path.isfile(path):
+			os.remove(path)
+		os.rename(path+'!', path)
+		if self.web and not self.copied_from:
+			EditDesc(self.name, desc=self.desc,
+				reviewers=JoinComma(self.reviewer), cc=JoinComma(self.cc),
+				private=self.private)
+
+	def Delete(self, ui, repo):
+		dir = CodeReviewDir(ui, repo)
+		os.unlink(dir + "/cl." + self.name)
+
+	def Subject(self):
+		s = line1(self.desc)
+		if len(s) > 60:
+			s = s[0:55] + "..."
+		if self.name != "new":
+			s = "code review %s: %s" % (self.name, s)
+		typecheck(s, str)
+		return s
+
+	def Upload(self, ui, repo, send_mail=False, gofmt=True, gofmt_just_warn=False, creating=False, quiet=False):
+		if not self.files and not creating:
+			ui.warn("no files in change list\n")
+		if ui.configbool("codereview", "force_gofmt", True) and gofmt:
+			CheckFormat(ui, repo, self.files, just_warn=gofmt_just_warn)
+		set_status("uploading CL metadata + diffs")
+		os.chdir(repo.root)
+		form_fields = [
+			("content_upload", "1"),
+			("reviewers", JoinComma(self.reviewer)),
+			("cc", JoinComma(self.cc)),
+			("description", self.desc),
+			("base_hashes", ""),
+		]
+
+		if self.name != "new":
+			form_fields.append(("issue", self.name))
+		vcs = None
+		# We do not include files when creating the issue,
+		# because we want the patch sets to record the repository
+		# and base revision they are diffs against.  We use the patch
+		# set message for that purpose, but there is no message with
+		# the first patch set.  Instead the message gets used as the
+		# new CL's overall subject.  So omit the diffs when creating
+		# and then we'll run an immediate upload.
+		# This has the effect that every CL begins with an empty "Patch set 1".
+		if self.files and not creating:
+			vcs = MercurialVCS(upload_options, ui, repo)
+			data = vcs.GenerateDiff(self.files)
+			files = vcs.GetBaseFiles(data)
+			if len(data) > MAX_UPLOAD_SIZE:
+				uploaded_diff_file = []
+				form_fields.append(("separate_patches", "1"))
+			else:
+				uploaded_diff_file = [("data", "data.diff", data)]
+		else:
+			uploaded_diff_file = [("data", "data.diff", emptydiff)]
+		
+		if vcs and self.name != "new":
+			form_fields.append(("subject", "diff -r " + vcs.base_rev + " " + ui.expandpath("default")))
+		else:
+			# First upload sets the subject for the CL itself.
+			form_fields.append(("subject", self.Subject()))
+		ctype, body = EncodeMultipartFormData(form_fields, uploaded_diff_file)
+		response_body = MySend("/upload", body, content_type=ctype)
+		patchset = None
+		msg = response_body
+		lines = msg.splitlines()
+		if len(lines) >= 2:
+			msg = lines[0]
+			patchset = lines[1].strip()
+			patches = [x.split(" ", 1) for x in lines[2:]]
+		if response_body.startswith("Issue updated.") and quiet:
+			pass
+		else:
+			ui.status(msg + "\n")
+		set_status("uploaded CL metadata + diffs")
+		if not response_body.startswith("Issue created.") and not response_body.startswith("Issue updated."):
+			raise hg_util.Abort("failed to update issue: " + response_body)
+		issue = msg[msg.rfind("/")+1:]
+		self.name = issue
+		if not self.url:
+			self.url = server_url_base + self.name
+		if not uploaded_diff_file:
+			set_status("uploading patches")
+			patches = UploadSeparatePatches(issue, rpc, patchset, data, upload_options)
+		if vcs:
+			set_status("uploading base files")
+			vcs.UploadBaseFiles(issue, rpc, patches, patchset, upload_options, files)
+		if send_mail:
+			set_status("sending mail")
+			MySend("/" + issue + "/mail", payload="")
+		self.web = True
+		set_status("flushing changes to disk")
+		self.Flush(ui, repo)
+		return
+
+	def Mail(self, ui, repo):
+		pmsg = "Hello " + JoinComma(self.reviewer)
+		if self.cc:
+			pmsg += " (cc: %s)" % (', '.join(self.cc),)
+		pmsg += ",\n"
+		pmsg += "\n"
+		repourl = ui.expandpath("default")
+		if not self.mailed:
+			pmsg += "I'd like you to review this change to\n" + repourl + "\n"
+		else:
+			pmsg += "Please take another look.\n"
+		typecheck(pmsg, str)
+		PostMessage(ui, self.name, pmsg, subject=self.Subject())
+		self.mailed = True
+		self.Flush(ui, repo)
+
+def GoodCLName(name):
+	typecheck(name, str)
+	return re.match("^[0-9]+$", name)
+
+def ParseCL(text, name):
+	typecheck(text, str)
+	typecheck(name, str)
+	sname = None
+	lineno = 0
+	sections = {
+		'Author': '',
+		'Description': '',
+		'Files': '',
+		'URL': '',
+		'Reviewer': '',
+		'CC': '',
+		'Mailed': '',
+		'Private': '',
+	}
+	for line in text.split('\n'):
+		lineno += 1
+		line = line.rstrip()
+		if line != '' and line[0] == '#':
+			continue
+		if line == '' or line[0] == ' ' or line[0] == '\t':
+			if sname == None and line != '':
+				return None, lineno, 'text outside section'
+			if sname != None:
+				sections[sname] += line + '\n'
+			continue
+		p = line.find(':')
+		if p >= 0:
+			s, val = line[:p].strip(), line[p+1:].strip()
+			if s in sections:
+				sname = s
+				if val != '':
+					sections[sname] += val + '\n'
+				continue
+		return None, lineno, 'malformed section header'
+
+	for k in sections:
+		sections[k] = StripCommon(sections[k]).rstrip()
+
+	cl = CL(name)
+	if sections['Author']:
+		cl.copied_from = sections['Author']
+	cl.desc = sections['Description']
+	for line in sections['Files'].split('\n'):
+		i = line.find('#')
+		if i >= 0:
+			line = line[0:i].rstrip()
+		line = line.strip()
+		if line == '':
+			continue
+		cl.files.append(line)
+	cl.reviewer = SplitCommaSpace(sections['Reviewer'])
+	cl.cc = SplitCommaSpace(sections['CC'])
+	cl.url = sections['URL']
+	if sections['Mailed'] != 'False':
+		# Odd default, but avoids spurious mailings when
+		# reading old CLs that do not have a Mailed: line.
+		# CLs created with this update will always have 
+		# Mailed: False on disk.
+		cl.mailed = True
+	if sections['Private'] in ('True', 'true', 'Yes', 'yes'):
+		cl.private = True
+	if cl.desc == '<enter description here>':
+		cl.desc = ''
+	return cl, 0, ''
+
+def SplitCommaSpace(s):
+	typecheck(s, str)
+	s = s.strip()
+	if s == "":
+		return []
+	return re.split(", *", s)
+
+def CutDomain(s):
+	typecheck(s, str)
+	i = s.find('@')
+	if i >= 0:
+		s = s[0:i]
+	return s
+
+def JoinComma(l):
+	for s in l:
+		typecheck(s, str)
+	return ", ".join(l)
+
+def ExceptionDetail():
+	s = str(sys.exc_info()[0])
+	if s.startswith("<type '") and s.endswith("'>"):
+		s = s[7:-2]
+	elif s.startswith("<class '") and s.endswith("'>"):
+		s = s[8:-2]
+	arg = str(sys.exc_info()[1])
+	if len(arg) > 0:
+		s += ": " + arg
+	return s
+
+def IsLocalCL(ui, repo, name):
+	return GoodCLName(name) and os.access(CodeReviewDir(ui, repo) + "/cl." + name, 0)
+
+# Load CL from disk and/or the web.
+def LoadCL(ui, repo, name, web=True):
+	typecheck(name, str)
+	set_status("loading CL " + name)
+	if not GoodCLName(name):
+		return None, "invalid CL name"
+	dir = CodeReviewDir(ui, repo)
+	path = dir + "cl." + name
+	if os.access(path, 0):
+		ff = open(path)
+		text = ff.read()
+		ff.close()
+		cl, lineno, err = ParseCL(text, name)
+		if err != "":
+			return None, "malformed CL data: "+err
+		cl.local = True
+	else:
+		cl = CL(name)
+	if web:
+		set_status("getting issue metadata from web")
+		d = JSONGet(ui, "/api/" + name + "?messages=true")
+		set_status(None)
+		if d is None:
+			return None, "cannot load CL %s from server" % (name,)
+		if 'owner_email' not in d or 'issue' not in d or str(d['issue']) != name:
+			return None, "malformed response loading CL data from code review server"
+		cl.dict = d
+		cl.reviewer = d.get('reviewers', [])
+		cl.cc = d.get('cc', [])
+		if cl.local and cl.copied_from and cl.desc:
+			# local copy of CL written by someone else
+			# and we saved a description.  use that one,
+			# so that committers can edit the description
+			# before doing hg submit.
+			pass
+		else:
+			cl.desc = d.get('description', "")
+		cl.url = server_url_base + name
+		cl.web = True
+		cl.private = d.get('private', False) != False
+		cl.lgtm = []
+		for m in d.get('messages', []):
+			if m.get('approval', False) == True:
+				who = re.sub('@.*', '', m.get('sender', ''))
+				text = re.sub("\n(.|\n)*", '', m.get('text', ''))
+				cl.lgtm.append((who, text))
+
+	set_status("loaded CL " + name)
+	return cl, ''
+
+class LoadCLThread(threading.Thread):
+	def __init__(self, ui, repo, dir, f, web):
+		threading.Thread.__init__(self)
+		self.ui = ui
+		self.repo = repo
+		self.dir = dir
+		self.f = f
+		self.web = web
+		self.cl = None
+	def run(self):
+		cl, err = LoadCL(self.ui, self.repo, self.f[3:], web=self.web)
+		if err != '':
+			self.ui.warn("loading "+self.dir+self.f+": " + err + "\n")
+			return
+		self.cl = cl
+
+# Load all the CLs from this repository.
+def LoadAllCL(ui, repo, web=True):
+	dir = CodeReviewDir(ui, repo)
+	m = {}
+	files = [f for f in os.listdir(dir) if f.startswith('cl.')]
+	if not files:
+		return m
+	active = []
+	first = True
+	for f in files:
+		t = LoadCLThread(ui, repo, dir, f, web)
+		t.start()
+		if web and first:
+			# first request: wait in case it needs to authenticate
+			# otherwise we get lots of user/password prompts
+			# running in parallel.
+			t.join()
+			if t.cl:
+				m[t.cl.name] = t.cl
+			first = False
+		else:
+			active.append(t)
+	for t in active:
+		t.join()
+		if t.cl:
+			m[t.cl.name] = t.cl
+	return m
+
+# Find repository root.  On error, ui.warn and return None
+def RepoDir(ui, repo):
+	url = repo.url();
+	if not url.startswith('file:'):
+		ui.warn("repository %s is not in local file system\n" % (url,))
+		return None
+	url = url[5:]
+	if url.endswith('/'):
+		url = url[:-1]
+	typecheck(url, str)
+	return url
+
+# Find (or make) code review directory.  On error, ui.warn and return None
+def CodeReviewDir(ui, repo):
+	dir = RepoDir(ui, repo)
+	if dir == None:
+		return None
+	dir += '/.hg/codereview/'
+	if not os.path.isdir(dir):
+		try:
+			os.mkdir(dir, 0700)
+		except:
+			ui.warn('cannot mkdir %s: %s\n' % (dir, ExceptionDetail()))
+			return None
+	typecheck(dir, str)
+	return dir
+
+# Turn leading tabs into spaces, so that the common white space
+# prefix doesn't get confused when people's editors write out 
+# some lines with spaces, some with tabs.  Only a heuristic
+# (some editors don't use 8 spaces either) but a useful one.
+def TabsToSpaces(line):
+	i = 0
+	while i < len(line) and line[i] == '\t':
+		i += 1
+	return ' '*(8*i) + line[i:]
+
+# Strip maximal common leading white space prefix from text
+def StripCommon(text):
+	typecheck(text, str)
+	ws = None
+	for line in text.split('\n'):
+		line = line.rstrip()
+		if line == '':
+			continue
+		line = TabsToSpaces(line)
+		white = line[:len(line)-len(line.lstrip())]
+		if ws == None:
+			ws = white
+		else:
+			common = ''
+			for i in range(min(len(white), len(ws))+1):
+				if white[0:i] == ws[0:i]:
+					common = white[0:i]
+			ws = common
+		if ws == '':
+			break
+	if ws == None:
+		return text
+	t = ''
+	for line in text.split('\n'):
+		line = line.rstrip()
+		line = TabsToSpaces(line)
+		if line.startswith(ws):
+			line = line[len(ws):]
+		if line == '' and t == '':
+			continue
+		t += line + '\n'
+	while len(t) >= 2 and t[-2:] == '\n\n':
+		t = t[:-1]
+	typecheck(t, str)
+	return t
+
+# Indent text with indent.
+def Indent(text, indent):
+	typecheck(text, str)
+	typecheck(indent, str)
+	t = ''
+	for line in text.split('\n'):
+		t += indent + line + '\n'
+	typecheck(t, str)
+	return t
+
+# Return the first line of l
+def line1(text):
+	typecheck(text, str)
+	return text.split('\n')[0]
+
+_change_prolog = """# Change list.
+# Lines beginning with # are ignored.
+# Multi-line values should be indented.
+"""
+
+desc_re = '^(.+: |(tag )?(release|weekly)\.|fix build|undo CL)'
+
+desc_msg = '''Your CL description appears not to use the standard form.
+
+The first line of your change description is conventionally a
+one-line summary of the change, prefixed by the primary affected package,
+and is used as the subject for code review mail; the rest of the description
+elaborates.
+
+Examples:
+
+	encoding/rot13: new package
+
+	math: add IsInf, IsNaN
+	
+	net: fix cname in LookupHost
+
+	unicode: update to Unicode 5.0.2
+
+'''
+
+def promptyesno(ui, msg):
+	return ui.promptchoice(msg, ["&yes", "&no"], 0) == 0
+
+def promptremove(ui, repo, f):
+	if promptyesno(ui, "hg remove %s (y/n)?" % (f,)):
+		if hg_commands.remove(ui, repo, 'path:'+f) != 0:
+			ui.warn("error removing %s" % (f,))
+
+def promptadd(ui, repo, f):
+	if promptyesno(ui, "hg add %s (y/n)?" % (f,)):
+		if hg_commands.add(ui, repo, 'path:'+f) != 0:
+			ui.warn("error adding %s" % (f,))
+
+def EditCL(ui, repo, cl):
+	set_status(None)	# do not show status
+	s = cl.EditorText()
+	while True:
+		s = ui.edit(s, ui.username())
+		
+		# We can't trust Mercurial + Python not to die before making the change,
+		# so, by popular demand, just scribble the most recent CL edit into
+		# $(hg root)/last-change so that if Mercurial does die, people
+		# can look there for their work.
+		try:
+			f = open(repo.root+"/last-change", "w")
+			f.write(s)
+			f.close()
+		except:
+			pass
+
+		clx, line, err = ParseCL(s, cl.name)
+		if err != '':
+			if not promptyesno(ui, "error parsing change list: line %d: %s\nre-edit (y/n)?" % (line, err)):
+				return "change list not modified"
+			continue
+		
+		# Check description.
+		if clx.desc == '':
+			if promptyesno(ui, "change list should have a description\nre-edit (y/n)?"):
+				continue
+		elif re.search('<enter reason for undo>', clx.desc):
+			if promptyesno(ui, "change list description omits reason for undo\nre-edit (y/n)?"):
+				continue
+		elif not re.match(desc_re, clx.desc.split('\n')[0]):
+			if promptyesno(ui, desc_msg + "re-edit (y/n)?"):
+				continue
+
+		# Check file list for files that need to be hg added or hg removed
+		# or simply aren't understood.
+		pats = ['path:'+f for f in clx.files]
+		changed = hg_matchPattern(ui, repo, *pats, modified=True, added=True, removed=True)
+		deleted = hg_matchPattern(ui, repo, *pats, deleted=True)
+		unknown = hg_matchPattern(ui, repo, *pats, unknown=True)
+		ignored = hg_matchPattern(ui, repo, *pats, ignored=True)
+		clean = hg_matchPattern(ui, repo, *pats, clean=True)
+		files = []
+		for f in clx.files:
+			if f in changed:
+				files.append(f)
+				continue
+			if f in deleted:
+				promptremove(ui, repo, f)
+				files.append(f)
+				continue
+			if f in unknown:
+				promptadd(ui, repo, f)
+				files.append(f)
+				continue
+			if f in ignored:
+				ui.warn("error: %s is excluded by .hgignore; omitting\n" % (f,))
+				continue
+			if f in clean:
+				ui.warn("warning: %s is listed in the CL but unchanged\n" % (f,))
+				files.append(f)
+				continue
+			p = repo.root + '/' + f
+			if os.path.isfile(p):
+				ui.warn("warning: %s is a file but not known to hg\n" % (f,))
+				files.append(f)
+				continue
+			if os.path.isdir(p):
+				ui.warn("error: %s is a directory, not a file; omitting\n" % (f,))
+				continue
+			ui.warn("error: %s does not exist; omitting\n" % (f,))
+		clx.files = files
+
+		cl.desc = clx.desc
+		cl.reviewer = clx.reviewer
+		cl.cc = clx.cc
+		cl.files = clx.files
+		cl.private = clx.private
+		break
+	return ""
+
+# For use by submit, etc. (NOT by change)
+# Get change list number or list of files from command line.
+# If files are given, make a new change list.
+def CommandLineCL(ui, repo, pats, opts, defaultcc=None):
+	if len(pats) > 0 and GoodCLName(pats[0]):
+		if len(pats) != 1:
+			return None, "cannot specify change number and file names"
+		if opts.get('message'):
+			return None, "cannot use -m with existing CL"
+		cl, err = LoadCL(ui, repo, pats[0], web=True)
+		if err != "":
+			return None, err
+	else:
+		cl = CL("new")
+		cl.local = True
+		cl.files = ChangedFiles(ui, repo, pats, taken=Taken(ui, repo))
+		if not cl.files:
+			return None, "no files changed"
+	if opts.get('reviewer'):
+		cl.reviewer = Add(cl.reviewer, SplitCommaSpace(opts.get('reviewer')))
+	if opts.get('cc'):
+		cl.cc = Add(cl.cc, SplitCommaSpace(opts.get('cc')))
+	if defaultcc:
+		cl.cc = Add(cl.cc, defaultcc)
+	if cl.name == "new":
+		if opts.get('message'):
+			cl.desc = opts.get('message')
+		else:
+			err = EditCL(ui, repo, cl)
+			if err != '':
+				return None, err
+	return cl, ""
+
+#######################################################################
+# Change list file management
+
+# Return list of changed files in repository that match pats.
+# The patterns came from the command line, so we warn
+# if they have no effect or cannot be understood.
+def ChangedFiles(ui, repo, pats, taken=None):
+	taken = taken or {}
+	# Run each pattern separately so that we can warn about
+	# patterns that didn't do anything useful.
+	for p in pats:
+		for f in hg_matchPattern(ui, repo, p, unknown=True):
+			promptadd(ui, repo, f)
+		for f in hg_matchPattern(ui, repo, p, removed=True):
+			promptremove(ui, repo, f)
+		files = hg_matchPattern(ui, repo, p, modified=True, added=True, removed=True)
+		for f in files:
+			if f in taken:
+				ui.warn("warning: %s already in CL %s\n" % (f, taken[f].name))
+		if not files:
+			ui.warn("warning: %s did not match any modified files\n" % (p,))
+
+	# Again, all at once (eliminates duplicates)
+	l = hg_matchPattern(ui, repo, *pats, modified=True, added=True, removed=True)
+	l.sort()
+	if taken:
+		l = Sub(l, taken.keys())
+	return l
+
+# Return list of changed files in repository that match pats and still exist.
+def ChangedExistingFiles(ui, repo, pats, opts):
+	l = hg_matchPattern(ui, repo, *pats, modified=True, added=True)
+	l.sort()
+	return l
+
+# Return list of files claimed by existing CLs
+def Taken(ui, repo):
+	all = LoadAllCL(ui, repo, web=False)
+	taken = {}
+	for _, cl in all.items():
+		for f in cl.files:
+			taken[f] = cl
+	return taken
+
+# Return list of changed files that are not claimed by other CLs
+def DefaultFiles(ui, repo, pats):
+	return ChangedFiles(ui, repo, pats, taken=Taken(ui, repo))
+
+#######################################################################
+# File format checking.
+
+def CheckFormat(ui, repo, files, just_warn=False):
+	set_status("running gofmt")
+	CheckGofmt(ui, repo, files, just_warn)
+	CheckTabfmt(ui, repo, files, just_warn)
+
+# Check that gofmt run on the list of files does not change them
+def CheckGofmt(ui, repo, files, just_warn):
+	files = gofmt_required(files)
+	if not files:
+		return
+	cwd = os.getcwd()
+	files = [RelativePath(repo.root + '/' + f, cwd) for f in files]
+	files = [f for f in files if os.access(f, 0)]
+	if not files:
+		return
+	try:
+		cmd = subprocess.Popen(["gofmt", "-l"] + files, shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=sys.platform != "win32")
+		cmd.stdin.close()
+	except:
+		raise hg_util.Abort("gofmt: " + ExceptionDetail())
+	data = cmd.stdout.read()
+	errors = cmd.stderr.read()
+	cmd.wait()
+	set_status("done with gofmt")
+	if len(errors) > 0:
+		ui.warn("gofmt errors:\n" + errors.rstrip() + "\n")
+		return
+	if len(data) > 0:
+		msg = "gofmt needs to format these files (run hg gofmt):\n" + Indent(data, "\t").rstrip()
+		if just_warn:
+			ui.warn("warning: " + msg + "\n")
+		else:
+			raise hg_util.Abort(msg)
+	return
+
+# Check that *.[chys] files indent using tabs.
+def CheckTabfmt(ui, repo, files, just_warn):
+	files = [f for f in files if f.startswith('src/') and re.search(r"\.[chys]$", f) and not re.search(r"\.tab\.[ch]$", f)]
+	if not files:
+		return
+	cwd = os.getcwd()
+	files = [RelativePath(repo.root + '/' + f, cwd) for f in files]
+	files = [f for f in files if os.access(f, 0)]
+	badfiles = []
+	for f in files:
+		try:
+			for line in open(f, 'r'):
+				# Four leading spaces is enough to complain about,
+				# except that some Plan 9 code uses four spaces as the label indent,
+				# so allow that.
+				if line.startswith('    ') and not re.match('    [A-Za-z0-9_]+:', line):
+					badfiles.append(f)
+					break
+		except:
+			# ignore cannot open file, etc.
+			pass
+	if len(badfiles) > 0:
+		msg = "these files use spaces for indentation (use tabs instead):\n\t" + "\n\t".join(badfiles)
+		if just_warn:
+			ui.warn("warning: " + msg + "\n")
+		else:
+			raise hg_util.Abort(msg)
+	return
+
+#######################################################################
+# CONTRIBUTORS file parsing
+
+contributorsCache = None
+contributorsURL = None
+
+def ReadContributors(ui, repo):
+	global contributorsCache
+	if contributorsCache is not None:
+		return contributorsCache
+
+	try:
+		if contributorsURL is not None:
+			opening = contributorsURL
+			f = urllib2.urlopen(contributorsURL)
+		else:
+			opening = repo.root + '/CONTRIBUTORS'
+			f = open(repo.root + '/CONTRIBUTORS', 'r')
+	except:
+		ui.write("warning: cannot open %s: %s\n" % (opening, ExceptionDetail()))
+		return
+
+	contributors = {}
+	for line in f:
+		# CONTRIBUTORS is a list of lines like:
+		#	Person <email>
+		#	Person <email> <alt-email>
+		# The first email address is the one used in commit logs.
+		if line.startswith('#'):
+			continue
+		m = re.match(r"([^<>]+\S)\s+(<[^<>\s]+>)((\s+<[^<>\s]+>)*)\s*$", line)
+		if m:
+			name = m.group(1)
+			email = m.group(2)[1:-1]
+			contributors[email.lower()] = (name, email)
+			for extra in m.group(3).split():
+				contributors[extra[1:-1].lower()] = (name, email)
+
+	contributorsCache = contributors
+	return contributors
+
+def CheckContributor(ui, repo, user=None):
+	set_status("checking CONTRIBUTORS file")
+	user, userline = FindContributor(ui, repo, user, warn=False)
+	if not userline:
+		raise hg_util.Abort("cannot find %s in CONTRIBUTORS" % (user,))
+	return userline
+
+def FindContributor(ui, repo, user=None, warn=True):
+	if not user:
+		user = ui.config("ui", "username")
+		if not user:
+			raise hg_util.Abort("[ui] username is not configured in .hgrc")
+	user = user.lower()
+	m = re.match(r".*<(.*)>", user)
+	if m:
+		user = m.group(1)
+
+	contributors = ReadContributors(ui, repo)
+	if user not in contributors:
+		if warn:
+			ui.warn("warning: cannot find %s in CONTRIBUTORS\n" % (user,))
+		return user, None
+	
+	user, email = contributors[user]
+	return email, "%s <%s>" % (user, email)
+
+#######################################################################
+# Mercurial helper functions.
+# Read http://mercurial.selenic.com/wiki/MercurialApi before writing any of these.
+# We use the ui.pushbuffer/ui.popbuffer + hg_commands.xxx tricks for all interaction
+# with Mercurial.  It has proved the most stable as they make changes.
+
+hgversion = hg_util.version()
+
+# We require Mercurial 1.9 and suggest Mercurial 2.0.
+# The details of the scmutil package changed then,
+# so allowing earlier versions would require extra band-aids below.
+# Ubuntu 11.10 ships with Mercurial 1.9.1 as the default version.
+hg_required = "1.9"
+hg_suggested = "2.0"
+
+old_message = """
+
+The code review extension requires Mercurial """+hg_required+""" or newer.
+You are using Mercurial """+hgversion+""".
+
+To install a new Mercurial, use
+
+	sudo easy_install mercurial=="""+hg_suggested+"""
+
+or visit http://mercurial.selenic.com/downloads/.
+"""
+
+linux_message = """
+You may need to clear your current Mercurial installation by running:
+
+	sudo apt-get remove mercurial mercurial-common
+	sudo rm -rf /etc/mercurial
+"""
+
+if hgversion < hg_required:
+	msg = old_message
+	if os.access("/etc/mercurial", 0):
+		msg += linux_message
+	raise hg_util.Abort(msg)
+
+from mercurial.hg import clean as hg_clean
+from mercurial import cmdutil as hg_cmdutil
+from mercurial import error as hg_error
+from mercurial import match as hg_match
+from mercurial import node as hg_node
+
+class uiwrap(object):
+	def __init__(self, ui):
+		self.ui = ui
+		ui.pushbuffer()
+		self.oldQuiet = ui.quiet
+		ui.quiet = True
+		self.oldVerbose = ui.verbose
+		ui.verbose = False
+	def output(self):
+		ui = self.ui
+		ui.quiet = self.oldQuiet
+		ui.verbose = self.oldVerbose
+		return ui.popbuffer()
+
+def to_slash(path):
+	if sys.platform == "win32":
+		return path.replace('\\', '/')
+	return path
+
+def hg_matchPattern(ui, repo, *pats, **opts):
+	w = uiwrap(ui)
+	hg_commands.status(ui, repo, *pats, **opts)
+	text = w.output()
+	ret = []
+	prefix = to_slash(os.path.realpath(repo.root))+'/'
+	for line in text.split('\n'):
+		f = line.split()
+		if len(f) > 1:
+			if len(pats) > 0:
+				# Given patterns, Mercurial shows relative to cwd
+				p = to_slash(os.path.realpath(f[1]))