Dump9660 (and mk9660).  Until we either do something
intelligent with symlinks or put in a switch for things
like dump9660, this is of rather limited utility under Unix.
diff --git a/src/cmd/9660/dump9660.c b/src/cmd/9660/dump9660.c
new file mode 100644
index 0000000..320e56d
--- /dev/null
+++ b/src/cmd/9660/dump9660.c
@@ -0,0 +1,402 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <disk.h>
+#include <libsec.h>
+#include "iso9660.h"
+
+ulong now;
+int chatty;
+int doabort;
+int docolon;
+int mk9660;
+Conform *map;
+
+static void addprotofile(char *new, char *old, Dir *d, void *a);
+void usage(void);
+
+char *argv0;
+
+void
+usage(void)
+{
+	if(mk9660)
+		fprint(2, "usage: disk/mk9660 [-D:] [-9cjr] [-b bootfile] [-p proto] [-s src] cdimage\n");
+	else
+		fprint(2, "usage: disk/dump9660 [-D:] [-9cjr] [-m maxsize] [-n now] [-p proto] [-s src] cdimage\n");
+	exits("usage");
+}
+
+int
+main(int argc, char **argv)
+{
+	int fix;
+	char buf[256], *dumpname, *proto, *s, *src, *status;
+	ulong block, length, newnull, cblock, clength, maxsize;
+	Cdimg *cd;
+	Cdinfo info;
+	XDir dir;
+	Direc *iconform, idumproot, iroot, *jconform, jdumproot, jroot, *r;
+	Dump *dump;
+
+	fix = 0;
+	status = nil;
+	memset(&info, 0, sizeof info);
+	proto = "/sys/lib/sysconfig/proto/allproto";
+	src = "./";
+
+	info.volumename = atom("9CD");
+	info.volumeset = atom("9VolumeSet");
+	info.publisher = atom("9Publisher");
+	info.preparer = atom("dump9660");
+	info.application = atom("dump9660");
+	info.flags = CDdump;
+	maxsize = 0;
+	mk9660 = 0;
+	fmtinstall('H', encodefmt);
+
+	ARGBEGIN{
+	case 'D':
+		chatty++;
+		break;
+	case 'M':
+		mk9660 = 1;
+		argv0 = "disk/mk9660";
+		info.flags &= ~CDdump;
+		break;
+	case '9':
+		info.flags |= CDplan9;
+		break;
+	case ':':
+		docolon = 1;
+		break;
+	case 'a':
+		doabort = 1;
+		break;
+	case 'b':
+		if(!mk9660)
+			usage();
+		info.flags |= CDbootable;
+		info.bootimage = EARGF(usage());
+		break;
+	case 'c':
+		info.flags |= CDconform;
+		break;
+	case 'f':
+		fix = 1;
+		break;
+	case 'j':
+		info.flags |= CDjoliet;
+		break;
+	case 'n':
+		now = atoi(EARGF(usage()));
+		break;
+	case 'm':
+		maxsize = strtoul(EARGF(usage()), 0, 0);
+		break;
+	case 'p':
+		proto = EARGF(usage());
+		break;
+	case 'r':
+		info.flags |= CDrockridge;
+		break;
+	case 's':
+		src = EARGF(usage());
+		break;
+	case 'v':
+		info.volumename = atom(EARGF(usage()));
+		break;
+	default:
+		usage();
+	}ARGEND
+
+	if(mk9660 && (fix || now || maxsize))
+		usage();
+
+	if(argc != 1)
+		usage();
+
+	if(now == 0)
+		now = (ulong)time(0);
+	if(mk9660){
+		if((cd = createcd(argv[0], info)) == nil)
+			sysfatal("cannot create '%s': %r", argv[0]);
+	}else{
+		if((cd = opencd(argv[0], info)) == nil)
+			sysfatal("cannot open '%s': %r", argv[0]);
+		if(!(cd->flags & CDdump))
+			sysfatal("not a dump cd");
+	}
+
+	/* create ISO9660/Plan 9 tree in memory */
+	memset(&dir, 0, sizeof dir);
+	dir.name = atom("");
+	dir.uid = atom("sys");
+	dir.gid = atom("sys");
+	dir.uidno = 0;
+	dir.gidno = 0;
+	dir.mode = DMDIR | 0755;
+	dir.mtime = now;
+	dir.atime = now;
+	dir.ctime = now;
+
+	mkdirec(&iroot, &dir);
+	iroot.srcfile = src;
+
+	/*
+	 * Read new files into memory
+	 */
+	if(rdproto(proto, src, addprotofile, nil, &iroot) < 0)
+		sysfatal("rdproto: %r");
+
+	if(mk9660){
+		dump = emalloc(sizeof *dump);
+		dumpname = nil;
+	}else{
+		/*
+		 * Read current dump tree and _conform.map.
+		 */
+		idumproot = readdumpdirs(cd, &dir, isostring);
+		readdumpconform(cd);
+		if(cd->flags & CDjoliet)
+			jdumproot = readdumpdirs(cd, &dir, jolietstring);
+
+		if(fix){
+			dumpname = nil;
+			cd->nextblock = cd->nulldump+1;
+			cd->nulldump = 0;
+			Cwseek(cd, cd->nextblock*Blocksize);
+			goto Dofix;
+		}
+	
+		dumpname = adddumpdir(&idumproot, now, &dir);
+		/* note that we assume all names are conforming and thus sorted */
+		if(cd->flags & CDjoliet) {
+			s = adddumpdir(&jdumproot, now, &dir);
+			if(s != dumpname)
+				sysfatal("dumpnames don't match %s %s\n", dumpname, s);
+		}
+		dump = dumpcd(cd, &idumproot);
+		cd->nextblock = cd->nulldump+1;
+	}
+
+	/*
+	 * Write new files, starting where the dump tree was.
+ 	 * Must be done before creation of the Joliet tree so that
+ 	 * blocks and lengths are correct.
+	 */
+	Cwseek(cd, cd->nextblock*Blocksize);
+	writefiles(dump, cd, &iroot);
+
+	if(cd->bootimage){
+		findbootimage(cd, &iroot);
+		Cupdatebootcat(cd);
+	}
+		
+	/* create Joliet tree */
+	if(cd->flags & CDjoliet)
+		copydirec(&jroot, &iroot);
+
+	if(info.flags & CDconform) {
+		checknames(&iroot, isbadiso9660);
+		convertnames(&iroot, struprcpy);
+	} else
+		convertnames(&iroot, (void *) strcpy);
+
+//	isoabstract = findconform(&iroot, abstract);
+//	isobiblio = findconform(&iroot, biblio);
+//	isonotice = findconform(&iroot, notice);
+
+	dsort(&iroot, isocmp);
+
+	if(cd->flags & CDjoliet) {
+	//	jabstract = findconform(&jroot, abstract);
+	//	jbiblio = findconform(&jroot, biblio);
+	//	jnotice = findconform(&jroot, notice);
+
+		checknames(&jroot, isbadjoliet);
+		convertnames(&jroot, (void *) strcpy);
+		dsort(&jroot, jolietcmp);
+	}
+
+	/*
+	 * Write directories.
+	 */
+	writedirs(cd, &iroot, Cputisodir);
+	if(cd->flags & CDjoliet)
+		writedirs(cd, &jroot, Cputjolietdir);
+
+	if(mk9660){
+		cblock = 0;
+		clength = 0;
+		newnull = 0;
+	}else{
+		/*
+		 * Write incremental _conform.map block.
+		 */
+		wrconform(cd, cd->nconform, &cblock, &clength);
+	
+		/* jump here if we're just fixing up the cd */
+Dofix:
+		/*
+		 * Write null dump header block; everything after this will be 
+		 * overwritten at the next dump.  Because of this, it needs to be
+		 * reconstructable.  We reconstruct the _conform.map and dump trees
+		 * from the header blocks in dump.c, and we reconstruct the path 
+		 * tables by walking the cd.
+		 */
+		newnull = Cputdumpblock(cd);
+	}
+
+	/*
+	 * Write _conform.map.
+	 */
+	dir.mode = 0444;
+	if(cd->flags & (CDconform|CDjoliet)) {
+		if(!mk9660 && cd->nconform == 0){
+			block = cblock;	
+			length = clength;
+		}else
+			wrconform(cd, 0, &block, &length);
+
+		if(mk9660) 
+{
+			idumproot = iroot;
+			jdumproot = jroot;
+		}
+		if(length) {
+			/* The ISO9660 name will get turned into uppercase when written. */
+			if((iconform = walkdirec(&idumproot, "_conform.map")) == nil)
+				iconform = adddirec(&idumproot, "_conform.map", &dir);
+			jconform = nil;
+			if(cd->flags & CDjoliet) {
+				if((jconform = walkdirec(&jdumproot, "_conform.map")) == nil)
+					jconform = adddirec(&jdumproot, "_conform.map", &dir);
+			}
+			iconform->block = block;
+			iconform->length = length;
+			if(cd->flags & CDjoliet) {
+				jconform->block = block;
+				jconform->length = length;
+			}
+		}
+		if(mk9660) {
+			iroot = idumproot;
+			jroot = jdumproot;
+		}
+	}
+
+	if(mk9660){
+		/*
+		 * Patch in root directories.
+		 */
+		setroot(cd, cd->iso9660pvd, iroot.block, iroot.length);
+		setvolsize(cd, cd->iso9660pvd, cd->nextblock*Blocksize);
+		if(cd->flags & CDjoliet){
+			setroot(cd, cd->jolietsvd, jroot.block, jroot.length);
+			setvolsize(cd, cd->jolietsvd, cd->nextblock*Blocksize);
+		}
+	}else{
+		/*
+		 * Write dump tree at end.  We assume the name characters
+		 * are all conforming, so everything is already sorted properly.
+		 */
+		convertnames(&idumproot, (info.flags & CDconform) ? (void *) struprcpy : (void *) strcpy);
+		if(cd->nulldump) {
+			r = walkdirec(&idumproot, dumpname);
+			assert(r != nil);
+			copybutname(r, &iroot);
+		}
+		if(cd->flags & CDjoliet) {
+			convertnames(&jdumproot, (void *) strcpy);
+			if(cd->nulldump) {
+				r = walkdirec(&jdumproot, dumpname);
+				assert(r != nil);
+				copybutname(r, &jroot);
+			}
+		}
+	
+		writedumpdirs(cd, &idumproot, Cputisodir);
+		if(cd->flags & CDjoliet)
+			writedumpdirs(cd, &jdumproot, Cputjolietdir);
+	
+		/*
+		 * Patch in new root directory entry.
+		 */
+		setroot(cd, cd->iso9660pvd, idumproot.block, idumproot.length);
+		setvolsize(cd, cd->iso9660pvd, cd->nextblock*Blocksize);
+		if(cd->flags & CDjoliet){
+			setroot(cd, cd->jolietsvd, jdumproot.block, jdumproot.length);
+			setvolsize(cd, cd->jolietsvd, cd->nextblock*Blocksize);
+		}
+	}
+	writepathtables(cd);	
+
+	if(!mk9660){
+		/*
+		 * If we've gotten too big, truncate back to what we started with,
+		 * fix up the cd, and exit with a non-zero status.
+		 */
+		Cwflush(cd);
+		if(cd->nulldump && maxsize && Cwoffset(cd) > maxsize){
+			fprint(2, "too big; writing old tree back\n");
+			status = "cd too big; aborted";
+	
+			rmdumpdir(&idumproot, dumpname);
+			rmdumpdir(&jdumproot, dumpname);
+	
+			cd->nextblock = cd->nulldump+1;
+			cd->nulldump = 0;
+			Cwseek(cd, cd->nextblock*Blocksize);
+			goto Dofix;
+		}
+	
+		/*
+		 * Write old null header block; this commits all our changes.
+		 */
+		if(cd->nulldump){
+			Cwseek(cd, cd->nulldump*Blocksize);
+			sprint(buf, "plan 9 dump cd\n");
+			sprint(buf+strlen(buf), "%s %lud %lud %lud %lud %lud %lud",
+				dumpname, now, newnull, cblock, clength, iroot.block,
+				iroot.length);
+			if(cd->flags & CDjoliet)
+				sprint(buf+strlen(buf), " %lud %lud",
+					jroot.block, jroot.length);
+			strcat(buf, "\n");
+			Cwrite(cd, buf, strlen(buf));
+			Cpadblock(cd);
+			Cwflush(cd);
+		}
+	}
+	fdtruncate(cd->fd, cd->nextblock*Blocksize);
+	exits(status);
+	return 0;
+}
+
+static void
+addprotofile(char *new, char *old, Dir *d, void *a)
+{
+	char *name, *p;
+	Direc *direc;
+	XDir xd;
+
+	dirtoxdir(&xd, d);
+	name = nil;
+	if(docolon && strchr(new, ':')) {
+		name = emalloc(strlen(new)+1);
+		strcpy(name, new);
+		while((p=strchr(name, ':')))
+			*p=' ';
+		new = name;
+	}
+	if((direc = adddirec((Direc*)a, new, &xd))) {
+		direc->srcfile = atom(old);
+
+		// BUG: abstract, biblio, notice
+	}
+	if(name)
+		free(name);
+
+}
+