Browse Source

Plan 9 from Bell Labs 2013-12-03

David du Colombier 10 years ago
parent
commit
6b5fa6c314

+ 59 - 9
acme/mail/src/mesg.c

@@ -43,6 +43,7 @@ char *goodtypes[] = {
 	"message/rfc822",
 	"text/richtext",
 	"text/tab-separated-values",
+	"text/calendar",
 	"application/octet-stream",
 	nil,
 };
@@ -989,6 +990,48 @@ printheader(char *dir, Biobuf *b, char **okheaders)
 	free(s);
 }
 
+/*
+ * find the best alternative part.
+ *
+ * turkeys have started emitting empty text/plain parts,
+ * with the actual content in a text/html part, which complicates the choice.
+ *
+ * bigger turkeys emit a tiny base64-encoded text/plain part,
+ * a small base64-encoded text/html part, and the real content is in
+ * a text/calendar part.
+ *
+ * the magic lengths are empirically derived.
+ * as turkeys devolve and mutate, this will only get worse.
+ */
+static Message*
+bestalt(Message *m, char *dir)
+{
+	int len;
+	char *subdir;
+	Message *nm;
+	Message *realplain, *realhtml, *realcal;
+
+	realplain = realhtml = realcal = nil;
+	for(nm = m->head; nm != nil; nm = nm->next){
+		subdir = estrstrdup(dir, nm->name);
+		len = 0;
+		free(readbody(nm->type, subdir, &len));
+		free(subdir);
+		if(strcmp(nm->type, "text/plain") == 0 && len >= 8)
+			realplain = nm;
+		else if(strcmp(nm->type, "text/html") == 0 && len >= 600)
+			realhtml = nm;
+		else if(strcmp(nm->type, "text/calendar") == 0)
+			realcal = nm;
+	}
+	if(realplain == nil && realhtml == nil && realcal)
+		return realcal;			/* super-turkey */
+	else if(realplain == nil && realhtml)
+		return realhtml;		/* regular turkey */
+	else
+		return realplain;
+}
+
 void
 mesgload(Message *m, char *rootdir, char *file, Window *w)
 {
@@ -1029,12 +1072,15 @@ mesgload(Message *m, char *rootdir, char *file, Window *w)
 		/* multi-part message, either multipart/* or message/rfc822 */
 		thisone = nil;
 		if(strcmp(m->type, "multipart/alternative") == 0){
-			thisone = m->head;	/* in case we can't find a good one */
-			for(mp=m->head; mp!=nil; mp=mp->next)
-				if(isprintable(mp->type)){
-					thisone = mp;
-					break;
-				}
+			thisone = bestalt(m, dir);
+			if(thisone == nil){
+				thisone = m->head; /* in case we can't find a good one */
+				for(mp=m->head; mp!=nil; mp=mp->next)
+					if(isprintable(mp->type)){
+						thisone = mp;
+						break;
+					}
+			}
 		}
 		for(mp=m->head; mp!=nil; mp=mp->next){
 			if(thisone!=nil && mp!=thisone)
@@ -1043,8 +1089,11 @@ mesgload(Message *m, char *rootdir, char *file, Window *w)
 			name = estrstrdup(file, mp->name);
 			/* skip first element in name because it's already in window name */
 			if(mp != m->head)
-				Bprint(w->body, "\n===> %s (%s) [%s]\n", strchr(name, '/')+1, mp->type, mp->disposition);
-			if(strcmp(mp->type, "text")==0 || strncmp(mp->type, "text/", 5)==0){
+				Bprint(w->body, "\n===> %s (%s) [%s]\n",
+					strchr(name, '/')+1, mp->type,
+					mp->disposition);
+			if(strcmp(mp->type, "text")==0 ||
+			    strncmp(mp->type, "text/", 5)==0){
 				mimedisplay(mp, name, rootdir, w, 1);
 				printheader(subdir, w->body, okheaders);
 				printheader(subdir, w->body, extraheaders);
@@ -1053,7 +1102,8 @@ mesgload(Message *m, char *rootdir, char *file, Window *w)
 				winwritebody(w, s, n);
 				free(s);
 			}else{
-				if(strncmp(mp->type, "multipart/", 10)==0 || strcmp(mp->type, "message/rfc822")==0){
+				if(strncmp(mp->type, "multipart/", 10)==0 ||
+				    strcmp(mp->type, "message/rfc822")==0){
 					mp->w = w;
 					mesgload(mp, rootdir, name, w);
 					mp->w = nil;

+ 1 - 2
sys/lib/backup/backup

@@ -89,8 +89,7 @@ if not
 if (! ~ $debug yes && ~ $set set1 && ~ $print yes) {
 	9fs log
 	# don't hang in lp
-	tail -50 /n/$fs/sys/log/fs.archive |
-		pr -h 'recent fossil dump scores' | lp &
+	tail -50 /sys/log/fs.archive | pr -h 'recent fossil dump scores' | lp &
 	echo fossil dump scores just printed.
 }
 

+ 2 - 1
sys/lib/dist/mkfile

@@ -13,7 +13,8 @@ ncd-dist:V: $scr/plan9-new.iso.bz2
 
 $scr/usbdisk.bz2:D: $scr/plan9.iso.bz2
 	cd pc; mk $target
-usb-dist:V: $dist/web.protect/usbdisk.bz2
+usb-dist:V: $scr/usbdisk.bz2
+	mk $dist/web.protect/usbdisk.bz2
 
 #contrib-cd:V: $scr/contrib.iso.bz2
 #	mk $dist/web.protect/contrib.iso.bz2

+ 2 - 2
sys/lib/dist/pc/inst/xxx

@@ -1,9 +1,9 @@
 #!/bin/rc
 
 ip/ipconfig
-echo '	auth=204.178.31.3
+echo '	auth=p9auth.cs.bell-labs.com
 	authdom=cs.bell-labs.com' >>/net/ndb
 ndb/cs
 auth/factotum
 bind -a /bin/auth /
-cpu -e clear -h tcp!204.178.31.2
+cpu -e clear -h tcp!plan9.bell-labs.com

+ 4 - 3
sys/lib/dist/pc/mkfile

@@ -1,4 +1,6 @@
 # /sys/lib/dist/pc/mkfile
+<../defs
+
 out=outside			# outside web server
 s=/sys/lib/dist/pc
 x=`{bind -b /sys/lib/dist/bin/$cputype /bin}
@@ -80,7 +82,7 @@ cddisk:D: 9load /sys/src/9/pc/9pcflop.gz plan9.ini.cd /lib/vgadb
 		/sys/src/9/pc/^(9pcflop 9pccd)^.gz subst/plan9.ini /lib/vgadb
 	ls -l $target
 
-$scr/usbdisk:D: /n/sources/plan9
+$scr/usbdisk:D: $scr/plan9.iso.bz2 /n/sources/plan9
 	@ {
 	rfork n
 	if (~ $#scr 0 || ~ $scr '') {
@@ -96,8 +98,7 @@ $scr/usbdisk:D: /n/sources/plan9
 	}
 $scr/usbdisk.bz2:D: $scr/usbdisk
 	@ {
-	rm -f $target
-	bzip2 $prereq && rm -f $prereq
+	bzip2 <$prereq >$target && rm -f $prereq
 	ls -l $target
 	targsz = `{{ls -s $target; echo 0} | awk '{print $1; exit}'}
 	if (test $targsz -lt 80000) {

+ 151 - 93
sys/src/9/boot/boot.c

@@ -27,25 +27,13 @@ static void	swapproc(void);
 static Method	*rootserver(char*);
 static void	kbmap(void);
 
-void
-boot(int argc, char *argv[])
+/*
+ * we should inherit the standard fds all referring to /dev/cons,
+ * but we're being paranoid.
+ */
+static void
+opencons(void)
 {
-	int fd, afd;
-	Method *mp;
-	char *cmd, cmdbuf[64], *iargv[16];
-	char rootbuf[64];
-	int islocal, ishybrid;
-	char *rp, *rsp, *rdparts;
-	int iargc, n;
-	char buf[32];
-	AuthInfo *ai;
-
-	fmtinstall('r', errfmt);
-
-	/*
-	 * we should inherit the standard fds all referring to /dev/cons,
-	 * but we're being paranoid.
-	 */
 	close(0);
 	close(1);
 	close(2);
@@ -53,13 +41,25 @@ boot(int argc, char *argv[])
 	open("/dev/cons", OREAD);
 	open("/dev/cons", OWRITE);
 	open("/dev/cons", OWRITE);
-	/*
-	 * init will reinitialize its namespace.
-	 * #ec gets us plan9.ini settings (*var variables).
-	 */
+}
+
+/*
+ * init will reinitialize its namespace.
+ * #ec gets us plan9.ini settings (*var variables).
+ */
+static void
+bindenvsrv(void)
+{
 	bind("#ec", "/env", MREPL);
 	bind("#e", "/env", MBEFORE|MCREATE);
 	bind("#s", "/srv/", MREPL|MCREATE);
+}
+
+static void
+debuginit(int argc, char **argv)
+{
+	int fd;
+
 	if(getenv("debugboot"))
 		debugboot = 1;
 #ifdef DEBUG
@@ -68,77 +68,62 @@ boot(int argc, char *argv[])
 		print("%#p %s ", argv[fd], argv[fd]);
 	print("\n");
 #endif	/* DEBUG */
+	SET(fd, argc, argv); USED(fd, argc, argv);
+}
 
-	ARGBEGIN{
-	case 'k':
-		kflag = 1;
-		break;
-	case 'm':
-		mflag = 1;
-		break;
-	case 'f':
-		fflag = 1;
-		break;
-	}ARGEND
+/*
+ * read disk partition tables here so that readnvram via factotum
+ * can see them.  ideally we would have this information in
+ * environment variables before attaching #S, which would then
+ * parse them and create partitions.
+ */
+static void
+partinit(void)
+{
+	char *rdparts;
 
-	readfile("#e/cputype", cputype, sizeof(cputype));
+	rdparts = getenv("readparts");
+	if(rdparts)
+		readparts();
+	free(rdparts);
+}
 
-	/*
-	 *  set up usb keyboard & mouse, if any.
-	 *  starts usbd, which mounts itself on /dev.
-	 *  starts partfs on first disk, if any, to permit nvram on usb.
-	 */
-	usbinit(Dontpost);
+/*
+ *  pick a method and initialize it
+ */
+static Method *
+pickmethod(int argc, char **argv)
+{
+	Method *mp;
 
-	/*
-	 *  pick a method and initialize it
-	 */
 	if(method[0].name == nil)
 		fatal("no boot methods");
 	mp = rootserver(argc ? *argv : 0);
 	(*mp->config)(mp);
-	islocal = strcmp(mp->name, "local") == 0;
-	ishybrid = strcmp(mp->name, "hybrid") == 0;
-
-	/*
-	 *  load keymap if it's there.
-	 */
-	kbmap();
-
-	/* don't trigger aoe until the network has been configured */
-	bind("#æ", "/dev", MAFTER);	/* nvram could be here */
-	bind("#S", "/dev", MAFTER);	/* nvram could be here */
-
-	/*
-	 * read disk partition tables here so that readnvram via factotum
-	 * can see them.  ideally we would have this information in
-	 * environment variables before attaching #S, which would then
-	 * parse them and create partitions.
-	 */
-	rdparts = getenv("readparts");
-	if(rdparts)
-		readparts();
-	free(rdparts);
+	return mp;
+}
 
-	/*
- 	 *  authentication agent
-	 *  sets hostowner, creating an auth discontinuity
-	 */
+/*
+ *  authentication agent
+ *  sets hostowner, creating an auth discontinuity
+ */
+static void
+doauth(int cpuflag)
+{
 	if(debugboot)
 		fprint(2, "auth...");
 	authentication(cpuflag);
+}
 
-	/* leave existing subprocesses in their own namespace */
-	rfork(RFNAMEG);
-
-	/*
-	 * restart partfs under the new hostowner id
-	 */
-	usbinit(Post);
+/*
+ *  connect to the root file system
+ */
+static int
+connectroot(Method *mp, int islocal, int ishybrid)
+{
+	int fd, n;
+	char buf[32];
 
-	/*
-	 *  connect to the root file system
-	 */
 	fd = (*mp->connect)();
 	if(fd < 0)
 		fatal("can't connect to file server");
@@ -154,10 +139,20 @@ boot(int argc, char *argv[])
 	if(n < 0)
 		fatal("can't init 9P");
 	srvcreate("boot", fd);
+	return fd;
+}
+
+/*
+ *  create the name space, mount the root fs
+ */
+static int
+nsinit(int fd, char **rspp)
+{
+	int afd;
+	char *rp, *rsp;
+	AuthInfo *ai;
+	static char rootbuf[64];
 
-	/*
-	 *  create the name space, mount the root fs
-	 */
 	if(bind("/", "/", MREPL) < 0)
 		fatal("bind /");
 	rp = getenv("rootspec");
@@ -185,23 +180,28 @@ boot(int argc, char *argv[])
 		rp = rootbuf;
 		if(bind(rp, "/", MAFTER|MCREATE) < 0){
 			fprint(2, "boot: couldn't bind $rootdir=%s to root: %r\n", rp);
-			if(strcmp(rootbuf, "/root//plan9") == 0){
-				fprint(2, "**** warning: remove rootdir=/plan9 entry from plan9.ini\n");
-				rp = "/root";
-				if(bind(rp, "/", MAFTER|MCREATE) < 0)
-					fatal("second bind /");
-			}else
+			if(strcmp(rootbuf, "/root//plan9") != 0)
+				fatal("second bind /");
+			/* undo installer's work */
+			fprint(2, "**** warning: remove rootdir=/plan9 "
+				"entry from plan9.ini\n");
+			rp = "/root";
+			if(bind(rp, "/", MAFTER|MCREATE) < 0)
 				fatal("second bind /");
 		}
 	}
-	close(fd);
 	setenv("rootdir", rp);
+	*rspp = rsp;
+	return afd;
+}
 
-	settime(islocal, afd, rsp);
-	if(afd > 0)
-		close(afd);
-	swapproc();
+static void
+execinit(void)
+{
+	int iargc;
+	char *cmd, cmdbuf[64], *iargv[16];
 
+	/* exec init */
 	cmd = getenv("init");
 	if(cmd == nil){
 		sprint(cmdbuf, "/%s/init -%s%s", cputype,
@@ -224,6 +224,64 @@ boot(int argc, char *argv[])
 	fatal(cmd);
 }
 
+void
+boot(int argc, char *argv[])
+{
+	int fd, afd, islocal, ishybrid;
+	char *rsp;
+	Method *mp;
+
+	fmtinstall('r', errfmt);
+	opencons();
+	bindenvsrv();
+	debuginit(argc, argv);
+
+	ARGBEGIN{
+	case 'k':
+		kflag = 1;
+		break;
+	case 'm':
+		mflag = 1;
+		break;
+	case 'f':
+		fflag = 1;
+		break;
+	}ARGEND
+
+	readfile("#e/cputype", cputype, sizeof(cputype));
+
+	/*
+	 *  set up usb keyboard & mouse, if any.
+	 *  starts partfs on first disk, if any, to permit nvram on usb.
+	 */
+	usbinit(Dontpost);
+
+	mp = pickmethod(argc, argv);
+	islocal = strcmp(mp->name, "local") == 0;
+	ishybrid = strcmp(mp->name, "hybrid") == 0;
+
+	kbmap();			/*  load keymap if it's there. */
+
+	/* don't trigger aoe until the network has been configured */
+	bind("#æ", "/dev", MAFTER);	/* nvram could be here */
+	bind("#S", "/dev", MAFTER);	/* nvram could be here */
+	partinit();
+
+	doauth(cpuflag);	/* authentication usually changes hostowner */
+	rfork(RFNAMEG);		/* leave existing subprocs in own namespace */
+	usbinit(Post);		/* restart partfs under the new hostowner id */
+	fd = connectroot(mp, islocal, ishybrid);
+	afd = nsinit(fd, &rsp);
+	close(fd);
+
+	settime(islocal, afd, rsp);
+	if(afd > 0)
+		close(afd);
+	swapproc();
+	execinit();
+	exits("failed to exec init");
+}
+
 static Method*
 findmethod(char *a)
 {

+ 1 - 1
sys/src/9/ip/ipv6.c

@@ -259,7 +259,7 @@ ipiput6(Fs *f, Ipifc *ifc, Block *bp)
 	tentative = iptentative(f, v6dst);
 
 	if(tentative && h->proto != ICMPv6) {
-		print("tentative addr, drop\n");
+		print("ipv6 tentative addr %I, drop\n", v6dst);
 		freeblist(bp);
 		return;
 	}

+ 22 - 3
sys/src/9/pc/ether82563.c

@@ -1370,7 +1370,7 @@ i82575interrupt(Ureg*, void *)
 }
 
 static int
-i82563detach(Ctlr* ctlr)
+i82563detach0(Ctlr* ctlr)
 {
 	int r, timeo;
 
@@ -1442,6 +1442,18 @@ i82563detach(Ctlr* ctlr)
 	return 0;
 }
 
+static int
+i82563detach(Ctlr* ctlr)
+{
+	int r;
+	static Lock detlck;
+
+	ilock(&detlck);
+	r = i82563detach0(ctlr);
+	iunlock(&detlck);
+	return r;
+}
+
 static void
 i82563shutdown(Ether* ether)
 {
@@ -1451,9 +1463,13 @@ i82563shutdown(Ether* ether)
 static ushort
 eeread(Ctlr *ctlr, int adr)
 {
+	ulong n;
+
 	csr32w(ctlr, Eerd, EEstart | adr << 2);
-	while ((csr32r(ctlr, Eerd) & EEdone) == 0)
+	for (n = 1000000; (csr32r(ctlr, Eerd) & EEdone) == 0 && n-- > 0; )
 		;
+	if (n == 0)
+		panic("i82563: eeread stuck");
 	return csr32r(ctlr, Eerd) >> 16;
 }
 
@@ -1494,6 +1510,7 @@ static int
 fread(Ctlr *c, Flash *f, int ladr)
 {
 	ushort s;
+	ulong n;
 
 	delay(1);
 	if(fcycle(c, f) == -1)
@@ -1508,8 +1525,10 @@ fread(Ctlr *c, Flash *f, int ladr)
 	s &= ~(2*Flcycle);		/* read */
 	f->reg[Fctl] = s | Fgo;
 
-	while((f->reg[Fsts] & Fdone) == 0)
+	for (n = 1000000; (f->reg[Fsts] & Fdone) == 0 && n-- > 0; )
 		;
+	if (n == 0)
+		panic("i82563: fread stuck");
 	if(f->reg[Fsts] & (Fcerr|Ael))
 		return -1;
 	return f->reg32[Fdata] & 0xffff;

+ 2 - 1
sys/src/9/pc/ether82598.c

@@ -384,7 +384,8 @@ ifstat(Ether *e, void *a, long n, ulong offset)
 	readstats(c);
 	for(i = 0; i < nelem(stattab); i++)
 		if(c->stats[i] > 0)
-			p = seprint(p, q, "%.10s  %uld\n", stattab[i].name,					c->stats[i]);
+			p = seprint(p, q, "%.10s  %uld\n", stattab[i].name,
+				c->stats[i]);
 	t = c->speeds;
 	p = seprint(p, q, "speeds: 0:%d 1000:%d 10000:%d\n", t[0], t[1], t[2]);
 	p = seprint(p, q, "mtu: min:%d max:%d\n", e->minmtu, e->maxmtu);

+ 28 - 4
sys/src/9/port/proc.c

@@ -581,6 +581,24 @@ canpage(Proc *p)
 	return ok;
 }
 
+void
+noprocpanic(char *msg)
+{
+	/*
+	 * setting exiting will make hzclock() on each processor call exit(0).
+	 * clearing our bit in machs avoids calling exit(0) from hzclock()
+	 * on this processor.
+	 */
+	lock(&active);
+	active.machs &= ~(1<<m->machno);
+	active.exiting = 1;
+	unlock(&active);
+
+	procdump();
+	delay(1000);
+	panic(msg);
+}
+
 Proc*
 newproc(void)
 {
@@ -588,13 +606,19 @@ newproc(void)
 	Proc *p;
 
 	lock(&procalloc);
-	for(;;) {
-		if(p = procalloc.free)
-			break;
+	while((p = procalloc.free) == nil) {
+		unlock(&procalloc);
 
 		snprint(msg, sizeof msg, "no procs; %s forking",
 			up? up->text: "kernel");
-		unlock(&procalloc);
+		/*
+		 * the situation is unlikely to heal itself.
+		 * dump the proc table and restart by default.
+		 * *noprocspersist in plan9.ini will yield the old
+		 * behaviour of trying forever.
+		 */
+		if(getconf("*noprocspersist") == nil)
+			noprocpanic(msg);
 		resrcwait(msg);
 		lock(&procalloc);
 	}

+ 81 - 17
sys/src/cmd/upas/ned/nedmail.c

@@ -59,6 +59,7 @@ Ctype ctype[] = {
 	{ "text/tab-separated-values",	"tsv",	1,	0	},
 	{ "text/richtext",		"rtx",	1,	0	},
 	{ "text/rtf",			"rtf",	1,	0	},
+	{ "text/calendar",		"ics",	1,	0	},
 	{ "text",			"txt",	1,	0	},
 	{ "message/rfc822",		"msg",	0,	0	},
 	{ "image/bmp",			"bmp",	0,	"image"	},
@@ -1321,6 +1322,22 @@ ncmd(Cmd*, Message *m)
 	return m->next;
 }
 
+/* turn crlfs into newlines */
+int
+decrlf(char *buf, int n)
+{
+	char *nl;
+	int left;
+
+	for (nl = buf, left = n; left >= 2 &&
+	    (nl = memchr(nl, '\r', left)) != nil; left = n - (nl - buf))
+		if (nl[1] == '\n'){
+			memmove(nl, nl+1, left-1);	/* delete the cr */
+			--n;
+		}
+	return n;
+}
+
 int
 printpart(String *s, char *part)
 {
@@ -1339,6 +1356,7 @@ printpart(String *s, char *part)
 	while((n = read(fd, buf, sizeof(buf))) > 0){
 		if(interrupted)
 			break;
+		n = decrlf(buf, n);
 		if(Bwrite(&out, buf, n) <= 0)
 			break;
 		tot += n;
@@ -1388,6 +1406,66 @@ compress(char *p)
 	*np = 0;
 }
 
+/*
+ * find the best alternative part.
+ *
+ * turkeys have started emitting empty text/plain parts,
+ * with the actual content in a text/html part, which complicates the choice.
+ *
+ * bigger turkeys emit a tiny base64-encoded text/plain part,
+ * a small base64-encoded text/html part, and the real content is in
+ * a text/calendar part.
+ *
+ * the magic lengths are empirically derived.
+ * as turkeys devolve and mutate, this will only get worse.
+ */
+static Message*
+bestalt(Message *m)
+{
+	Message *nm;
+	Message *realplain, *realhtml, *realcal;
+	Ctype *cp;
+
+	realplain = realhtml = realcal = nil;
+	for(nm = m->child; nm != nil; nm = nm->next){
+		cp = findctype(nm);
+		if(cp->ext != nil)
+			if(strncmp(cp->ext, "txt", 3) == 0 && nm->len >= 88)
+				realplain = nm;
+			else if(strncmp(cp->ext, "html", 3) == 0 &&
+			    nm->len >= 670)
+				realhtml = nm;
+			else if(strncmp(cp->ext, "ics", 3) == 0)
+				realcal = nm;
+	}
+	if(realplain == nil && realhtml == nil && realcal)
+		return realcal;			/* super-turkey */
+	else if(realplain == nil && realhtml)
+		return realhtml;		/* regular turkey */
+	else
+		return realplain;
+}
+
+static void
+pralt(Message *m)
+{
+	Message *nm;
+	Ctype *cp;
+
+	nm = bestalt(m);
+	if(nm == nil)
+		/* no winner.  print the first displayable part. */
+		for(nm = m->child; nm != nil; nm = nm->next){
+			cp = findctype(nm);
+			if(cp->display)
+				break;
+		}
+	if(nm != nil)
+		pcmd(nil, nm);
+	else
+		hcmd(nil, m);
+}
+
 Message*
 pcmd(Cmd*, Message *m)
 {
@@ -1408,23 +1486,9 @@ pcmd(Cmd*, Message *m)
 			printhtml(m);
 		else
 			printpart(m->path, "body");
-	} else if(strcmp(m->type, "multipart/alternative") == 0){
-		for(nm = m->child; nm != nil; nm = nm->next){
-			cp = findctype(nm);
-			if(cp->ext != nil && strncmp(cp->ext, "txt", 3) == 0)
-				break;
-		}
-		if(nm == nil)
-			for(nm = m->child; nm != nil; nm = nm->next){
-				cp = findctype(nm);
-				if(cp->display)
-					break;
-			}
-		if(nm != nil)
-			pcmd(nil, nm);
-		else
-			hcmd(nil, m);
-	} else if(strncmp(m->type, "multipart/", 10) == 0){
+	} else if(strcmp(m->type, "multipart/alternative") == 0)
+		pralt(m);
+	else if(strncmp(m->type, "multipart/", 10) == 0){
 		nm = m->child;
 		if(nm != nil){
 			// always print first part

+ 26 - 9
sys/src/libc/9sys/dial.c

@@ -253,7 +253,7 @@ connectwait(Dest *dp, char *besterr)
 		if (conn - dp->conn != dp->winner && conn->dead &&
 		    conn->err[0]) {
 			strncpy(besterr, conn->err, ERRMAX-1);
-			conn->err[ERRMAX-1] = '\0';
+			besterr[ERRMAX-1] = '\0';
 			break;
 		}
 	return dp->winner;
@@ -265,8 +265,15 @@ parsecs(Dest *dp, char **clonep, char **destp)
 	char *dest, *p;
 
 	dest = strchr(dp->nextaddr, ' ');
-	if(dest == nil)
+	if(dest == nil) {
+		p = strchr(dp->nextaddr, '\n');
+		if(p)
+			*p = '\0';
+		werrstr("malformed clone cmd from cs `%s'", dp->nextaddr);
+		if(p)
+			*p = '\n';
 		return -1;
+	}
 	*dest++ = '\0';
 	p = strchr(dest, '\n');
 	if(p == nil)
@@ -303,7 +310,7 @@ dialmulti(DS *ds, Dest *dp)
 {
 	int rv, kid, kidme;
 	char *clone, *dest;
-	char err[ERRMAX], besterr[ERRMAX];
+	char besterr[ERRMAX];
 
 	dp->winner = -1;
 	dp->nkid = 0;
@@ -314,6 +321,8 @@ dialmulti(DS *ds, Dest *dp)
 		if (kid < 0)
 			--dp->nkid;
 		else if (kid == 0) {
+			char err[ERRMAX];
+
 			/* only in kid, to avoid atnotify callbacks in parent */
 			atnotify(catcher, 1);
 
@@ -324,11 +333,10 @@ dialmulti(DS *ds, Dest *dp)
 			_exits(besterr);	/* avoid atexit callbacks */
 		}
 	}
+	*besterr = '\0';
 	rv = connectwait(dp, besterr);
-	if(rv < 0 && *besterr)
-		werrstr("%s", besterr);
-	else
-		werrstr("%s", err);
+	if(rv < 0)
+		werrstr("%s", (*besterr? besterr: "unknown error"));
 	return rv;
 }
 
@@ -341,6 +349,7 @@ csdial(DS *ds)
 	char buf[Maxstring], clone[Maxpath], err[ERRMAX], besterr[ERRMAX];
 	Dest *dp;
 
+	werrstr("");
 	dp = mallocz(sizeof *dp, 1);
 	if(dp == nil)
 		return -1;
@@ -367,6 +376,7 @@ csdial(DS *ds)
 
 	/*
 	 *  ask connection server to translate
+	 *  e.g., net!cs.bell-labs.com!smtp
 	 */
 	snprint(buf, sizeof(buf), "%s!%s", ds->proto, ds->rem);
 	if(write(fd, buf, strlen(buf)) < 0){
@@ -376,7 +386,11 @@ csdial(DS *ds)
 	}
 
 	/*
-	 *  read all addresses from the connection server.
+	 *  read all addresses from the connection server:
+	 *  /net/tcp/clone 135.104.9.78!25
+	 *  /net/tcp/clone 2620:0:dc0:1805::29!25
+	 *
+	 *  assumes that we'll get one record per read.
 	 */
 	seek(fd, 0, 0);
 	addrs = 0;
@@ -389,6 +403,8 @@ csdial(DS *ds)
 		addrp += n;
 		bleft -= n;
 	}
+	*addrp = '\0';
+
 	/*
 	 * if we haven't read all of cs's output, assume the last line might
 	 * have been truncated and ignore it.  we really don't expect this
@@ -522,7 +538,8 @@ _dial_string_parse(char *str, DS *ds)
 			ds->netdir = 0;
 			ds->proto = ds->buf;
 		} else {
-			for(p2 = p; *p2 != '/'; p2--)
+			/* expecting /net.alt/tcp!foo or #I1/tcp!foo */
+			for(p2 = p; p2 > ds->buf && *p2 != '/'; p2--)
 				;
 			*p2++ = 0;
 			ds->netdir = ds->buf;

+ 5 - 1
sys/src/libmach/sym.c

@@ -1109,11 +1109,15 @@ fileelem(Sym **fp, uchar *cp, char *buf, int n)
 {
 	int i, j;
 	char *c, *bp, *end;
+	Sym *sym;
 
 	bp = buf;
 	end = buf+n-1;
 	for(i = 1; j = (cp[i]<<8)|cp[i+1]; i+=2){
-		c = fp[j]->name;
+		sym = fp[j];
+		if (sym == nil)
+			break;
+		c = sym->name;
 		if(bp != buf && bp[-1] != '/' && bp < end)
 			*bp++ = '/';
 		while(bp < end && *c)