Commit Diff


commit - e1dddc053287874e82e2b67f95ccee7d7bc63e22
commit + 7285a491c1ce1e630a0751b1011fd33e6b17234b
blob - /dev/null
blob + 9cae9582985a9610dd9fdbaa8ecb05be5d46a90b (mode 644)
--- /dev/null
+++ src/cmd/9660/boot.c
@@ -0,0 +1,189 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <libsec.h>
+
+#include "iso9660.h"
+
+/* FreeBSD 4.5 installation CD for reference
+g% cdsector 17 | xd -b
+1+0 records in
+1+0 records out
+0000000  00 	- volume descriptor type (0)
+         43 44 30 30 31     - "CD001"
+         01     - volume descriptor version (1)
+         45 4c 20 54 4f 52 49 54 4f
+0000010  20 53 50 45 43 49 46 49 43 41 54 49 4f 4e 00 00
+0000020  00 00 00 00 00 00 00 - 7-39 boot system identifier
+"EL TORITO SPECIFICATION"
+
+                              00 00 00 00 00 00 00 00 00
+0000030  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+0000040  00 00 00 00 00 00 00 - 39-71 boot identifier
+
+boot system use:
+
+absolute pointer to the boot catalog??
+
+                              4d 0c 00 00 00 00 00 00 00
+0000050  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+0000580  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+0000590  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00005a0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+g% cdsector 3149 | xd -b	# 0x0c4d
+1+0 records in
+1+0 records out
+0000000  01 	- header (1)
+            00  - platform id (0 = 0x86)
+               00 00 - reserved 0
+                     00 00 00 00 00 00 00 00 00 00 00 00
+0000010  00 00 00 00 00 00 00 00 00 00 00 00 - id string
+                                             aa 55 - checksum
+                                                    55 aa - magic
+
+0000020  88    - 88 = bootable
+            03   - 3 = 2.88MB diskette 
+               00 00 - load segment 0 means default 0x7C0
+                     00  - system type (byte 5 of boot image)
+                        00 - unused (0)
+                           01 00 - 512-byte sector count for initial load
+                                 4e 0c 00 00 - ptr to virtual disk
+                                             00 00 00 00
+0000030  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+0000040  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+0000050  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+0000060  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+g% cdsector `{h2d 0c4e} | xd -b
+1+0 records in
+1+0 records out
+0000000  eb 3c 00 00 00 00 00 00 00 00 00 00 02 00 00 00
+0000010  00 00 00 00 00 00 00 00 12 00 02 00 00 00 00 00
+0000020  00 00 00 00 00 16 1f 66 6a 00 51 50 06 53 
+                                          31 c0
+
+FREEBSD
+0000000  eb 3c 00 00 00 00 00 00 00 00 00 00 02 00 00 00
+0000010  00 00 00 00 00 00 00 00 12 00 02 00 00 00 00 00
+0000020  00 00 00 00 00 16 1f 66 6a 00 51 50 06 53 
+                                          31 c0
+
+DOS 5
+0000000  eb 3c 90 4d 53 44 4f 53 35 2e 30 00 02 01 01 00
+0000010  02 e0 00 40 0b f0 09 00 12 00 02 00 00 00 00 00
+0000020  00 00 00 00 00 00 29 6a 2c e0 16 4e 4f 20 4e 41
+0000030  4d 45 20 20 20 20 46 41 54 31 32 20 20 20 fa 33
+0000040  c0 8e d0 bc 00 7c 16 07 bb 78 00 36 c5 37 1e 56
+0000050  16 53 bf 3e 7c b9 0b 00 fc f3 a4 06 1f c6 45 fe
+0000060  0f 8b 0e 18 7c 88 4d f9 89 47 02 c7 07 3e 7c fb
+0000070  cd 13 72 79 33 c0 39 06 13 7c 74 08 8b 0e 13 7c
+0000080  89 0e 20 7c a0 10 7c f7 26 16 7c 03 06 1c 7c 13
+0000090  16 1e 7c 03 06 0e 7c 83 d2 00 a3 50 7c 89 16 52
+00000a0  7c a3 49 7c 89 16 4b 7c b8 20 00 f7 26 11 7c 8b
+
+NDISK
+0000000  eb 3c 90 50 6c 61 6e 39 2e 30 30 00 02 01 01 00
+0000010  02 e0 00 40 0b f0 09 00 12 00 02 00 00 00 00 00
+0000020  40 0b 00 00 00 00 29 13 00 00 00 43 59 4c 49 4e
+0000030  44 52 49 43 41 4c 46 41 54 31 32 20 20 20 fa 31
+0000040  c0 8e d0 8e d8 8e c0 bc ec 7b 89 e5 88 56 12 fb
+0000050  be ea 7d bf 90 7d ff d7 bf 82 7d ff d7 8b 06 27
+0000060  7c 8b 16 29 7c bb 00 7e bf 2c 7d ff d7 bb 10 00
+
+PBSDISK
+0000000  eb 3c 90 50 6c 61 6e 39 2e 30 30 00 02 01 01 00
+0000010  02 e0 00 40 0b f0 09 00 12 00 02 00 00 00 00 00
+0000020  40 0b 00 00 00 00 29 13 00 00 00 43 59 4c 49 4e
+0000030  44 52 49 43 41 4c 46 41 54 31 32 20 20 20 fa 31
+0000040  c0 8e d0 8e d8 8e c0 bc f8 7b 89 e5 88 56 00 fb
+0000050  be f8 7d bf 00 7d ff d7 bf df 7c ff d7 8b 06 27
+0000060  7c 8b 16 29 7c bb 00 7e bf 89 7c ff d7 bf fb 7c
+*/
+
+void
+Cputbootvol(Cdimg *cd)
+{
+	Cputc(cd, 0x00);
+	Cputs(cd, "CD001", 5);
+	Cputc(cd, 0x01);
+	Cputs(cd, "EL TORITO SPECIFICATION", 2+1+6+1+13);
+	Crepeat(cd, 0, 2+16+16+7);
+	cd->bootcatptr = Cwoffset(cd);
+	Cpadblock(cd);
+}
+
+void
+Cupdatebootvol(Cdimg *cd)
+{
+	ulong o;
+
+	o = Cwoffset(cd);
+	Cwseek(cd, cd->bootcatptr);
+	Cputnl(cd, cd->bootcatblock, 4);
+	Cwseek(cd, o);
+}
+
+void
+Cputbootcat(Cdimg *cd)
+{
+	cd->bootcatblock = Cwoffset(cd) / Blocksize;
+	Cputc(cd, 0x01);
+	Cputc(cd, 0x00);
+	Cputc(cd, 0x00);
+	Cputc(cd, 0x00);
+	Crepeat(cd, 0, 12+12);
+
+	/*
+	 * either the checksum doesn't include the header word
+	 * or it just doesn't matter.
+	 */
+	Cputc(cd, 0xAA);
+	Cputc(cd, 0x55);
+	Cputc(cd, 0x55);
+	Cputc(cd, 0xAA);
+
+	cd->bootimageptr = Cwoffset(cd);
+	Cpadblock(cd);
+}
+
+void
+Cupdatebootcat(Cdimg *cd)
+{
+	ulong o;
+
+	if(cd->bootdirec == nil)
+		return;
+
+	o = Cwoffset(cd);
+	Cwseek(cd, cd->bootimageptr);
+	Cputc(cd, 0x88);
+	switch(cd->bootdirec->length){
+	default:
+		fprint(2, "warning: boot image is not 1.44MB or 2.88MB; pretending 1.44MB\n");
+	case 1440*1024:
+		Cputc(cd, 0x02);	/* 1.44MB disk */
+		break;
+	case 2880*1024:
+		Cputc(cd, 0x03);	/* 2.88MB disk */
+		break;
+	}
+	Cputnl(cd, 0, 2);	/* load segment */
+	Cputc(cd, 0);	/* system type */
+	Cputc(cd, 0);	/* unused */
+	Cputnl(cd, 1, 2);	/* 512-byte sector count for load */
+	Cputnl(cd, cd->bootdirec->block, 4);	/* ptr to disk image */
+	Cwseek(cd, o);	
+}
+
+void
+findbootimage(Cdimg *cd, Direc *root)
+{
+	Direc *d;
+
+	d = walkdirec(root, cd->bootimage);
+	if(d == nil){
+		fprint(2, "warning: did not encounter boot image\n");
+		return;
+	}
+
+	cd->bootdirec = d;
+}
blob - /dev/null
blob + 492e028949d386d50a693c47c4949628345a5a5a (mode 644)
--- /dev/null
+++ src/cmd/9660/cdrdwr.c
@@ -0,0 +1,632 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <libsec.h>
+
+#include "iso9660.h"
+
+static int readisodesc(Cdimg*, Voldesc*);
+static int readjolietdesc(Cdimg*, Voldesc*);
+
+/*
+ * It's not strictly conforming; instead it's enough to
+ * get us up and running; presumably the real CD writing
+ * will take care of being conforming.
+ *
+ * Things not conforming include:
+ *	- no path table
+ *	- root directories are of length zero
+ */
+Cdimg*
+createcd(char *file, Cdinfo info)
+{
+	int fd, xfd;
+	Cdimg *cd;
+
+	if(access(file, AEXIST) == 0){
+		werrstr("file already exists");
+		return nil;
+	}
+
+	if((fd = create(file, ORDWR, 0666)) < 0)
+		return nil;
+
+	cd = emalloc(sizeof *cd);
+	cd->file = atom(file);
+
+	Binit(&cd->brd, fd, OREAD);
+
+	if((xfd = open(file, ORDWR)) < 0)
+		sysfatal("can't open file again: %r");
+	Binit(&cd->bwr, xfd, OWRITE);
+
+	Crepeat(cd, 0, 16*Blocksize);
+	Cputisopvd(cd, info);
+	if(info.flags & CDbootable){
+		cd->bootimage = info.bootimage;
+		cd->flags |= CDbootable;
+		Cputbootvol(cd);
+	}
+
+	if(readisodesc(cd, &cd->iso) < 0)
+		assert(0);
+	if(info.flags & CDplan9)
+		cd->flags |= CDplan9;
+	else if(info.flags & CDrockridge)
+		cd->flags |= CDrockridge;
+	if(info.flags & CDjoliet) {
+		Cputjolietsvd(cd, info);
+		if(readjolietdesc(cd, &cd->joliet) < 0)
+			assert(0);
+		cd->flags |= CDjoliet;
+	}
+	Cputendvd(cd);
+
+	if(info.flags & CDdump){
+		cd->nulldump = Cputdumpblock(cd);
+		cd->flags |= CDdump;
+	}
+	if(cd->flags & CDbootable){
+		Cputbootcat(cd);
+		Cupdatebootvol(cd);
+	}
+
+	if(info.flags & CDconform)
+		cd->flags |= CDconform;
+
+	cd->flags |= CDnew;
+	cd->nextblock = Cwoffset(cd) / Blocksize;
+	assert(cd->nextblock != 0);
+
+	return cd;
+}
+
+Cdimg*
+opencd(char *file, Cdinfo info)
+{
+	int fd, xfd;
+	Cdimg *cd;
+	Dir *d;
+
+	if((fd = open(file, ORDWR)) < 0) {
+		if(access(file, AEXIST) == 0)
+			return nil;
+		return createcd(file, info);
+	}
+
+	if((d = dirfstat(fd)) == nil) {
+		close(fd);
+		return nil;
+	}
+	if(d->length == 0 || d->length % Blocksize) {
+		werrstr("bad length %lld", d->length);
+		close(fd);
+		free(d);
+		return nil;
+	}
+
+	cd = emalloc(sizeof *cd);
+	cd->file = atom(file);
+	cd->nextblock = d->length / Blocksize;
+	assert(cd->nextblock != 0);
+	free(d);
+
+	Binit(&cd->brd, fd, OREAD);
+
+	if((xfd = open(file, ORDWR)) < 0)
+		sysfatal("can't open file again: %r");
+	Binit(&cd->bwr, xfd, OWRITE);
+
+	if(readisodesc(cd, &cd->iso) < 0) {
+		free(cd);
+		close(fd);
+		close(xfd);
+		return nil;
+	}
+
+	/* lowercase because of isostring */
+	if(strstr(cd->iso.systemid, "iso9660") == nil 
+	&& strstr(cd->iso.systemid, "utf8") == nil) {
+		werrstr("unknown systemid %s", cd->iso.systemid);
+		free(cd);
+		close(fd);
+		close(xfd);
+		return nil;
+	}
+	
+	if(strstr(cd->iso.systemid, "plan 9"))
+		cd->flags |= CDplan9;
+	if(strstr(cd->iso.systemid, "iso9660"))
+		cd->flags |= CDconform;
+	if(strstr(cd->iso.systemid, "rrip"))
+		cd->flags |= CDrockridge;
+	if(strstr(cd->iso.systemid, "boot"))
+		cd->flags |= CDbootable;
+	if(readjolietdesc(cd, &cd->joliet) == 0)
+		cd->flags |= CDjoliet;
+	if(hasdump(cd))
+		cd->flags |= CDdump;
+
+	return cd;
+}
+
+ulong
+big(void *a, int n)
+{
+	uchar *p;
+	ulong v;
+	int i;
+
+	p = a;
+	v = 0;
+	for(i=0; i<n; i++)
+		v = (v<<8) | *p++;
+	return v;
+}
+
+ulong
+little(void *a, int n)
+{
+	uchar *p;
+	ulong v;
+	int i;
+
+	p = a;
+	v = 0;
+	for(i=0; i<n; i++)
+		v |= (*p++<<(i*8));
+	return v;
+}
+
+void
+Creadblock(Cdimg *cd, void *buf, ulong block, ulong len)
+{
+	assert(block != 0);	/* nothing useful there */
+
+	Bflush(&cd->bwr);
+	if(Bseek(&cd->brd, block*Blocksize, 0) != block*Blocksize)
+		sysfatal("error seeking to block %lud", block);
+	if(Bread(&cd->brd, buf, len) != len)
+		sysfatal("error reading %lud bytes at block %lud: %r %lld", len, block, Bseek(&cd->brd, 0, 2));
+}
+
+int
+parsedir(Cdimg *cd, Direc *d, uchar *buf, int len, char *(*cvtname)(uchar*, int))
+{
+	enum { NAMELEN = 28 };
+	char name[NAMELEN];
+	uchar *p;
+	Cdir *c;
+
+	memset(d, 0, sizeof *d);
+
+	c = (Cdir*)buf;
+
+	if(c->len > len) {
+		werrstr("buffer too small");
+		return -1;
+	}
+
+	if(c->namelen == 1 && c->name[0] == '\0')
+		d->name = atom(".");
+	else if(c->namelen == 1 && c->name[0] == '\001')
+		d->name = atom("..");
+	else if(cvtname)
+		d->name = cvtname(c->name, c->namelen);
+
+	d->block = little(c->dloc, 4);
+	d->length = little(c->dlen, 4);
+
+	if(c->flags & 2)
+		d->mode |= DMDIR;
+
+/*BUG: do we really need to parse the plan 9 fields? */
+	/* plan 9 use fields */
+	if((cd->flags & CDplan9) && cvtname == isostring
+	&& (c->namelen != 1 || c->name[0] > 1)) {
+		p = buf+33+c->namelen;
+		if((p-buf)&1)
+			p++;
+		assert(p < buf+c->len);
+		assert(*p < NAMELEN);
+		if(*p != 0) {
+			memmove(name, p+1, *p);
+			name[*p] = '\0';
+			d->confname = d->name;
+			d->name = atom(name);
+		}
+		p += *p+1;
+		assert(*p < NAMELEN);
+		memmove(name, p+1, *p);
+		name[*p] = '\0';
+		d->uid = atom(name);
+		p += *p+1;
+		assert(*p < NAMELEN);
+		memmove(name, p+1, *p);
+		name[*p] = '\0';
+		d->gid = atom(name);
+		p += *p+1;
+		if((p-buf)&1)
+			p++;
+		d->mode = little(p, 4);
+	}
+
+	// BUG: rock ridge extensions
+	return 0;
+}
+
+void
+setroot(Cdimg *cd, ulong block, ulong dloc, ulong dlen)
+{
+	assert(block != 0);
+
+	Cwseek(cd, block*Blocksize+offsetof(Cvoldesc, rootdir[0])+offsetof(Cdir, dloc[0]));
+	Cputn(cd, dloc, 4);
+	Cputn(cd, dlen, 4);
+}
+
+void
+setvolsize(Cdimg *cd, ulong block, ulong size)
+{
+	assert(block != 0);
+
+	Cwseek(cd, block*Blocksize+offsetof(Cvoldesc, volsize[0]));
+	Cputn(cd, size, 4);
+}
+
+void
+setpathtable(Cdimg *cd, ulong block, ulong sz, ulong lloc, ulong bloc)
+{
+	assert(block != 0);
+
+	Cwseek(cd, block*Blocksize+offsetof(Cvoldesc, pathsize[0]));
+	Cputn(cd, sz, 4);
+	Cputnl(cd, lloc, 4);
+	Cputnl(cd, 0, 4);
+	Cputnm(cd, bloc, 4);
+	Cputnm(cd, 0, 4);
+	assert(Cwoffset(cd) == block*Blocksize+offsetof(Cvoldesc, rootdir[0]));
+}
+
+
+static void
+parsedesc(Voldesc *v, Cvoldesc *cv, char *(*string)(uchar*, int))
+{
+	v->systemid = string(cv->systemid, sizeof cv->systemid);
+
+	v->pathsize = little(cv->pathsize, 4);
+	v->lpathloc = little(cv->lpathloc, 4);
+	v->mpathloc = little(cv->mpathloc, 4);
+
+	v->volumeset = string(cv->volumeset, sizeof cv->volumeset);
+	v->publisher = string(cv->publisher, sizeof cv->publisher);
+	v->preparer = string(cv->preparer, sizeof cv->preparer);
+	v->application = string(cv->application, sizeof cv->application);
+
+	v->abstract = string(cv->abstract, sizeof cv->abstract);
+	v->biblio = string(cv->biblio, sizeof cv->biblio);
+	v->notice = string(cv->notice, sizeof cv->notice);
+}
+	
+static int
+readisodesc(Cdimg *cd, Voldesc *v)
+{
+	static uchar magic[] = { 0x01, 'C', 'D', '0', '0', '1', 0x01, 0x00 };
+	Cvoldesc cv;
+
+	memset(v, 0, sizeof *v);
+
+	Creadblock(cd, &cv, 16, sizeof cv);
+	if(memcmp(cv.magic, magic, sizeof magic) != 0) {
+		werrstr("bad pvd magic");
+		return -1;
+	}
+
+	if(little(cv.blocksize, 2) != Blocksize) {
+		werrstr("block size not %d", Blocksize);
+		return -1;
+	}
+
+	cd->iso9660pvd = 16;
+	parsedesc(v, &cv, isostring);
+
+	return parsedir(cd, &v->root, cv.rootdir, sizeof cv.rootdir, isostring);
+}
+
+static int
+readjolietdesc(Cdimg *cd, Voldesc *v)
+{
+	int i;
+	static uchar magic[] = { 0x02, 'C', 'D', '0', '0', '1', 0x01, 0x00 };
+	Cvoldesc cv;
+
+	memset(v, 0, sizeof *v);
+
+	for(i=16; i<24; i++) {
+		Creadblock(cd, &cv, i, sizeof cv);
+		if(memcmp(cv.magic, magic, sizeof magic) != 0)
+			continue;
+		if(cv.charset[0] != 0x25 || cv.charset[1] != 0x2F
+		|| (cv.charset[2] != 0x40 && cv.charset[2] != 0x43 && cv.charset[2] != 0x45))
+			continue;
+		break;
+	}
+
+	if(i==24) {
+		werrstr("could not find Joliet SVD");
+		return -1;
+	}
+
+	if(little(cv.blocksize, 2) != Blocksize) {
+		werrstr("block size not %d", Blocksize);
+		return -1;
+	}
+
+	cd->jolietsvd = i;
+	parsedesc(v, &cv, jolietstring);
+
+	return parsedir(cd, &v->root, cv.rootdir, sizeof cv.rootdir, jolietstring);
+}
+
+/*
+ * CD image buffering routines.
+ */
+void
+Cputc(Cdimg *cd, int c)
+{
+	assert(Boffset(&cd->bwr) >= 16*Blocksize || c == 0);
+
+if(Boffset(&cd->bwr) == 0x9962)
+if(c >= 256) abort();
+	if(Bputc(&cd->bwr, c) < 0)
+		sysfatal("Bputc: %r");
+	Bflush(&cd->brd);
+}
+
+void
+Cputnl(Cdimg *cd, ulong val, int size)
+{
+	switch(size) {
+	default:
+		sysfatal("bad size %d in bputnl", size);
+	case 2:
+		Cputc(cd, val);
+		Cputc(cd, val>>8);
+		break;
+	case 4:
+		Cputc(cd, val);
+		Cputc(cd, val>>8);
+		Cputc(cd, val>>16);
+		Cputc(cd, val>>24);
+		break;
+	}
+}
+
+void
+Cputnm(Cdimg *cd, ulong val, int size)
+{
+	switch(size) {
+	default:
+		sysfatal("bad size %d in bputnl", size);
+	case 2:
+		Cputc(cd, val>>8);
+		Cputc(cd, val);
+		break;
+	case 4:
+		Cputc(cd, val>>24);
+		Cputc(cd, val>>16);
+		Cputc(cd, val>>8);
+		Cputc(cd, val);
+		break;
+	}
+}
+
+void
+Cputn(Cdimg *cd, long val, int size)
+{
+	Cputnl(cd, val, size);
+	Cputnm(cd, val, size);
+}
+
+/*
+ * ASCII/UTF string writing
+ */
+void
+Crepeat(Cdimg *cd, int c, int n)
+{
+	while(n-- > 0)
+		Cputc(cd, c);
+}
+
+void
+Cputs(Cdimg *cd, char *s, int size)
+{
+	int n;
+
+	if(s == nil) {
+		Crepeat(cd, ' ', size);
+		return;
+	}
+
+	for(n=0; n<size && *s; n++)
+		Cputc(cd, *s++);
+	if(n<size)
+		Crepeat(cd, ' ', size-n);
+}
+
+void
+Cwrite(Cdimg *cd, void *buf, int n)
+{
+	assert(Boffset(&cd->bwr) >= 16*Blocksize);
+
+	if(Bwrite(&cd->bwr, buf, n) != n)
+		sysfatal("Bwrite: %r");
+	Bflush(&cd->brd);
+}
+
+void
+Cputr(Cdimg *cd, Rune r)
+{
+	Cputc(cd, r>>8);
+	Cputc(cd, r);
+}
+
+void
+Crepeatr(Cdimg *cd, Rune r, int n)
+{
+	int i;
+
+	for(i=0; i<n; i++)
+		Cputr(cd, r);
+}
+
+void
+Cputrs(Cdimg *cd, Rune *s, int osize)
+{
+	int n, size;
+
+	size = osize/2;
+	if(s == nil)
+		Crepeatr(cd, (Rune)' ', size);
+	else {
+		for(n=0; *s && n<size; n++)
+			Cputr(cd, *s++);
+		if(n<size)
+			Crepeatr(cd, ' ', size-n);
+	}
+	if(osize&1)
+		Cputc(cd, 0);	/* what else can we do? */
+}
+
+void
+Cputrscvt(Cdimg *cd, char *s, int size)
+{
+	Rune r[256];
+
+	strtorune(r, s);
+	Cputrs(cd, strtorune(r, s), size);
+}
+
+void
+Cpadblock(Cdimg *cd)
+{
+	int n;
+	ulong nb;
+
+	n = Blocksize - (Boffset(&cd->bwr) % Blocksize);
+	if(n != Blocksize)
+		Crepeat(cd, 0, n);
+
+	nb = Boffset(&cd->bwr)/Blocksize;
+	assert(nb != 0);
+	if(nb > cd->nextblock)
+		cd->nextblock = nb;
+}
+
+void
+Cputdate(Cdimg *cd, ulong ust)
+{
+	Tm *tm;
+
+	if(ust == 0) {
+		Crepeat(cd, 0, 7);
+		return;
+	}
+	tm = gmtime(ust);
+	Cputc(cd, tm->year);
+	Cputc(cd, tm->mon+1);
+	Cputc(cd, tm->mday);
+	Cputc(cd, tm->hour);
+	Cputc(cd, tm->min);
+	Cputc(cd, tm->sec);
+	Cputc(cd, 0);
+}
+
+void
+Cputdate1(Cdimg *cd, ulong ust)
+{
+	Tm *tm;
+	char str[20];
+
+	if(ust == 0) {
+		Crepeat(cd, '0', 16);
+		Cputc(cd, 0);
+		return;
+	}
+	tm = gmtime(ust);
+	sprint(str, "%.4d%.2d%.2d%.2d%.2d%.4d",
+		tm->year+1900,
+		tm->mon+1,
+		tm->mday,
+		tm->hour,
+		tm->min,
+		tm->sec*100);
+	Cputs(cd, str, 16);
+	Cputc(cd, 0);
+}
+
+void
+Cwseek(Cdimg *cd, ulong offset)
+{
+	Bseek(&cd->bwr, offset, 0);
+}
+
+ulong
+Cwoffset(Cdimg *cd)
+{
+	return Boffset(&cd->bwr);
+}
+
+void
+Cwflush(Cdimg *cd)
+{
+	Bflush(&cd->bwr);
+}
+
+ulong
+Croffset(Cdimg *cd)
+{
+	return Boffset(&cd->brd);
+}
+
+void
+Crseek(Cdimg *cd, ulong offset)
+{
+	Bseek(&cd->brd, offset, 0);
+}
+
+int
+Cgetc(Cdimg *cd)
+{
+	int c;
+
+	Cwflush(cd);
+	if((c = Bgetc(&cd->brd)) == Beof) {
+		fprint(2, "getc at %lud\n", Croffset(cd));
+		assert(0);
+		//sysfatal("Bgetc: %r");
+	}
+	return c;
+}
+
+void
+Cread(Cdimg *cd, void *buf, int n)
+{
+	Cwflush(cd);
+	if(Bread(&cd->brd, buf, n) != n)
+		sysfatal("Bread: %r");
+}
+
+char*
+Crdline(Cdimg *cd, int c)
+{
+	Cwflush(cd);
+	return Brdline(&cd->brd, c);
+}
+
+int
+Clinelen(Cdimg *cd)
+{
+	return Blinelen(&cd->brd);
+}
+
blob - /dev/null
blob + 530b4d561bd6b7f5517925239758131d55da40f3 (mode 644)
--- /dev/null
+++ src/cmd/9660/conform.c
@@ -0,0 +1,141 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <libsec.h>
+#include <ctype.h>
+#include "iso9660.h"
+
+/*
+ * We keep an array sorted by bad atom pointer.
+ * The theory is that since we don't free memory very often,
+ * the array will be mostly sorted already and insertions will
+ * usually be near the end, so we won't spend much time
+ * keeping it sorted.
+ */
+
+/*
+ * Binary search a Tx list.
+ * If no entry is found, return a pointer to
+ * where a new such entry would go.
+ */
+static Tx*
+txsearch(char *atom, Tx *t, int n)
+{
+	while(n > 0) {
+		if(atom < t[n/2].bad)
+			n = n/2;
+		else if(atom > t[n/2].bad) {
+			t += n/2+1;
+			n -= (n/2+1);
+		} else
+			return &t[n/2];
+	}
+	return t;
+}
+
+void
+addtx(char *b, char *g)
+{
+	Tx *t;
+	Conform *c;
+
+	if(map == nil)
+		map = emalloc(sizeof(*map));
+	c = map;
+
+	if(c->nt%32 == 0)
+		c->t = erealloc(c->t, (c->nt+32)*sizeof(c->t[0]));
+	t = txsearch(b, c->t, c->nt);
+	if(t < c->t+c->nt && t->bad == b) {
+		fprint(2, "warning: duplicate entry for %s in _conform.map\n", b);
+		return;
+	}
+
+	if(t != c->t+c->nt)
+		memmove(t+1, t, (c->t+c->nt - t)*sizeof(Tx));
+	t->bad = b;
+	t->good = g;
+	c->nt++;
+}
+
+char*
+conform(char *s, int isdir)
+{
+	Tx *t;
+	char buf[10], *g;
+	Conform *c;
+
+	c = map;
+	s = atom(s);
+	if(c){
+		t = txsearch(s, c->t, c->nt);
+		if(t < c->t+c->nt && t->bad == s)
+			return t->good;
+	}
+
+	sprint(buf, "%c%.6d", isdir ? 'D' : 'F', c ? c->nt : 0);
+	g = atom(buf);
+	addtx(s, g);
+	return g;
+}
+
+#ifdef NOTUSED
+static int
+isalldigit(char *s)
+{
+	while(*s)
+		if(!isdigit(*s++))
+			return 0;
+	return 1;
+}
+#endif
+
+static int
+goodcmp(const void *va, const void *vb)
+{
+	Tx *a, *b;
+
+	a = (Tx*)va;
+	b = (Tx*)vb;
+	return strcmp(a->good, b->good);
+}
+
+static int
+badatomcmp(const void *va, const void *vb)
+{
+	Tx *a, *b;
+
+	a = (Tx*)va;
+	b = (Tx*)vb;
+	if(a->good < b->good)
+		return -1;
+	if(a->good > b->good)
+		return 1;
+	return 0;
+}
+
+void
+wrconform(Cdimg *cd, int n, ulong *pblock, ulong *plength)
+{
+	char buf[1024];
+	int i;
+	Conform *c;
+
+	c = map;
+	*pblock = cd->nextblock;
+	if(c==nil || n==c->nt){
+		*plength = 0;
+		return;
+	}
+
+	Cwseek(cd, cd->nextblock*Blocksize);
+	qsort(c->t, c->nt, sizeof(c->t[0]), goodcmp);
+	for(i=n; i<c->nt; i++) {
+		snprint(buf, sizeof buf, "%s %s\n", c->t[i].good, c->t[i].bad);
+		Cwrite(cd, buf, strlen(buf));
+	}
+	qsort(c->t, c->nt, sizeof(c->t[0]), badatomcmp);
+	*plength = Cwoffset(cd) - *pblock*Blocksize;
+	chat("write _conform.map at %lud+%lud\n", *pblock, *plength);
+	Cpadblock(cd);
+}
blob - /dev/null
blob + 8185f680611259cea5531245bb7f16dfb9e5e21e (mode 644)
--- /dev/null
+++ src/cmd/9660/direc.c
@@ -0,0 +1,222 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <libsec.h>
+
+#include "iso9660.h"
+
+void
+mkdirec(Direc *direc, XDir *d)
+{
+	memset(direc, 0, sizeof(Direc));
+	direc->name = atom(d->name);
+	direc->uid = atom(d->uid);
+	direc->gid = atom(d->gid);
+	direc->uidno = d->uidno;
+	direc->gidno = d->gidno;
+	direc->mode = d->mode;
+	direc->length = d->length;
+	direc->mtime = d->mtime;
+	direc->atime = d->atime;
+	direc->ctime = d->ctime;
+	direc->symlink = d->symlink;
+}
+
+static int
+strecmp(char *a, char *ea, char *b)
+{
+	int r;
+
+	if((r = strncmp(a, b, ea-a)) != 0)
+		return r;
+
+	if(b[ea-a] == '\0')
+		return 0;
+	return 1;
+}
+
+/*
+ * Binary search a list of directories for the
+ * entry with name name.
+ * If no entry is found, return a pointer to
+ * where a new such entry would go.
+ */
+static Direc*
+dbsearch(char *name, int nname, Direc *d, int n)
+{
+	int i;
+
+	while(n > 0) {
+		i = strecmp(name, name+nname, d[n/2].name);
+		if(i < 0)
+			n = n/2;
+		else if(i > 0) {
+			d += n/2+1;
+			n -= (n/2+1);
+		} else
+			return &d[n/2];
+	}
+	return d;
+}
+
+/*
+ * Walk to name, starting at d.
+ */
+Direc*
+walkdirec(Direc *d, char *name)
+{
+	char *p, *nextp, *slashp;
+	Direc *nd;
+
+	for(p=name; p && *p; p=nextp) {
+		if((slashp = strchr(p, '/')) != nil)
+			nextp = slashp+1;
+		else
+			nextp = slashp = p+strlen(p);
+
+		nd = dbsearch(p, slashp-p, d->child, d->nchild);
+		if(nd >= d->child+d->nchild || strecmp(p, slashp, nd->name) != 0)
+			return nil;
+		d = nd;
+	}
+	return d;
+}
+
+/*
+ * Add the file ``name'' with attributes d to the
+ * directory ``root''.  Name may contain multiple
+ * elements; all but the last must exist already.
+ * 
+ * The child lists are kept sorted by utfname.
+ */	
+Direc*
+adddirec(Direc *root, char *name, XDir *d)
+{
+	char *p;
+	Direc *nd;
+	int off;
+
+	if(name[0] == '/')
+		name++;
+	if((p = strrchr(name, '/')) != nil) {
+		*p = '\0';
+		root = walkdirec(root, name);
+		if(root == nil) {
+			sysfatal("error in proto file: no entry for /%s but /%s/%s\n", name, name, p+1);
+			return nil;
+		}
+		*p = '/';
+		p++;
+	} else
+		p = name;
+
+	nd = dbsearch(p, strlen(p), root->child, root->nchild);
+	off = nd - root->child;
+	if(off < root->nchild && strcmp(nd->name, p) == 0) {
+		if ((d->mode & DMDIR) == 0)
+			fprint(2, "warning: proto lists %s twice\n", name);
+		return nil;
+	}
+
+	if(root->nchild%Ndirblock == 0) {
+		root->child = erealloc(root->child, (root->nchild+Ndirblock)*sizeof(Direc));
+		nd = root->child + off;
+	}
+
+	memmove(nd+1, nd, (root->nchild - off)*sizeof(Direc));
+	mkdirec(nd, d);
+	nd->name = atom(p);
+	root->nchild++;
+	return nd;
+}
+
+/* 
+ * Copy the tree src into dst.
+ */
+void
+copydirec(Direc *dst, Direc *src)
+{
+	int i, n;
+
+	*dst = *src;
+
+	if((src->mode & DMDIR) == 0)
+		return;
+
+	n = (src->nchild + Ndirblock - 1);
+	n -= n%Ndirblock;
+	dst->child = emalloc(n*sizeof(Direc));
+
+	n = dst->nchild;
+	for(i=0; i<n; i++)
+		copydirec(&dst->child[i], &src->child[i]);
+}
+
+/*
+ * Turn the Dbadname flag on for any entries
+ * that have non-conforming names.
+ */
+static void
+_checknames(Direc *d, int (*isbadname)(char*), int isroot)
+{
+	int i;
+
+	if(!isroot && isbadname(d->name))
+		d->flags |= Dbadname;
+
+	if(strcmp(d->name, "_conform.map") == 0)
+		d->flags |= Dbadname;
+
+	for(i=0; i<d->nchild; i++)
+		_checknames(&d->child[i], isbadname, 0);
+}
+
+void
+checknames(Direc *d, int (*isbadname)(char*))
+{
+	_checknames(d, isbadname, 1);
+}
+
+/*
+ * Set the names to conform to 8.3
+ * by changing them to numbers.
+ * Plan 9 gets the right names from its
+ * own directory entry.
+ *
+ * We used to write a _conform.map file to translate
+ * names.  Joliet should take care of most of the
+ * interoperability with other systems now.
+ */
+void
+convertnames(Direc *d, char* (*cvt)(char*, char*))
+{
+	int i;
+	char new[1024];
+
+	if(d->flags & Dbadname)
+		cvt(new, conform(d->name, d->mode & DMDIR));
+	else
+		cvt(new, d->name);
+	d->confname = atom(new);
+
+	for(i=0; i<d->nchild; i++)
+		convertnames(&d->child[i], cvt);
+}
+
+/*
+ * Sort a directory with a given comparison function.
+ * After this is called on a tree, adddirec should not be,
+ * since the entries may no longer be sorted as adddirec expects.
+ */
+void
+dsort(Direc *d, int (*cmp)(const void*, const void*))
+{
+	int i, n;
+
+	n = d->nchild;
+	qsort(d->child, n, sizeof(d[0]), cmp);
+
+	for(i=0; i<n; i++)
+		dsort(&d->child[i], cmp);
+}
+
blob - /dev/null
blob + b77e9eb91f6f878ad1b0599ef001b2cb3409427d (mode 644)
--- /dev/null
+++ src/cmd/9660/dump.c
@@ -0,0 +1,511 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <libsec.h>
+#include <ctype.h>
+#include "iso9660.h"
+
+static void
+md5cd(Cdimg *cd, ulong block, ulong length, uchar *digest)
+{
+	int n;
+	uchar buf[Blocksize];
+	DigestState *s;
+
+	s = md5(nil, 0, nil, nil);
+	while(length > 0) {
+		n = length;
+		if(n > Blocksize)
+			n = Blocksize;
+
+		Creadblock(cd, buf, block, n);
+
+		md5(buf, n, nil, s);
+
+		block++;
+		length -= n;
+	}
+	md5(nil, 0, digest, s);
+}
+
+static Dumpdir*
+mkdumpdir(char *name, uchar *md5, ulong block, ulong length)
+{
+	Dumpdir *d;
+
+	assert(block != 0);
+
+	d = emalloc(sizeof *d);
+	d->name = name;
+	memmove(d->md5, md5, sizeof d->md5);
+	d->block = block;
+	d->length = length;
+
+	return d;
+}
+
+static Dumpdir**
+ltreewalkmd5(Dumpdir **l, uchar *md5)
+{
+	int i;
+
+	while(*l) {
+		i = memcmp(md5, (*l)->md5, MD5dlen);
+		if(i < 0)
+			l = &(*l)->md5left;
+		else if(i == 0)
+			return l;
+		else
+			l = &(*l)->md5right;
+	}
+	return l;
+}
+
+static Dumpdir**
+ltreewalkblock(Dumpdir **l, ulong block)
+{
+	while(*l) {
+		if(block < (*l)->block)
+			l = &(*l)->blockleft;
+		else if(block == (*l)->block)
+			return l;
+		else
+			l = &(*l)->blockright;
+	}
+	return l;
+}
+
+/*
+ * Add a particular file to our binary tree.
+ */
+static void
+addfile(Cdimg *cd, Dump *d, char *name, Direc *dir)
+{
+	uchar md5[MD5dlen];
+	Dumpdir **lblock;
+
+	assert((dir->mode & DMDIR) == 0);
+
+	if(dir->length == 0)
+		return;
+
+	lblock = ltreewalkblock(&d->blockroot, dir->block);
+	if(*lblock != nil) {
+		if((*lblock)->length == dir->length)
+			return;
+		fprint(2, "block %lud length %lud %s %lud %s\n", dir->block, (*lblock)->length, (*lblock)->name,
+			dir->length, dir->name);
+		assert(0);
+	}
+
+	md5cd(cd, dir->block, dir->length, md5);
+	if(chatty > 1)
+		fprint(2, "note file %.16H %lud (%s)\n", md5, dir->length, dir->name);
+	insertmd5(d, name, md5, dir->block, dir->length);
+}
+
+void
+insertmd5(Dump *d, char *name, uchar *md5, ulong block, ulong length)
+{
+	Dumpdir **lmd5;
+	Dumpdir **lblock;
+
+	lblock = ltreewalkblock(&d->blockroot, block);
+	if(*lblock != nil) {
+		if((*lblock)->length == length)
+			return;
+		fprint(2, "block %lud length %lud %lud\n", block, (*lblock)->length, length);
+		assert(0);
+	}
+
+	assert(length != 0);
+	*lblock = mkdumpdir(name, md5, block, length);
+
+	lmd5 = ltreewalkmd5(&d->md5root, md5);
+	if(*lmd5 != nil)
+		fprint(2, "warning: data duplicated on CD\n");
+	else
+		*lmd5 = *lblock;
+}
+
+/*
+ * Fill all the children entries for a particular directory;
+ * all we care about is block, length, and whether it is a directory.
+ */
+void
+readkids(Cdimg *cd, Direc *dir, char *(*cvt)(uchar*, int))
+{
+	char *dot, *dotdot;
+	int m, n;
+	uchar buf[Blocksize], *ebuf, *p;
+	ulong b, nb;
+	Cdir *c;
+	Direc dx;
+
+	assert(dir->mode & DMDIR);
+
+	dot = atom(".");
+	dotdot = atom("..");
+	ebuf = buf+Blocksize;
+	nb = (dir->length+Blocksize-1) / Blocksize;
+
+	n = 0;
+	for(b=0; b<nb; b++) {
+		Creadblock(cd, buf, dir->block + b, Blocksize);
+		p = buf;
+		while(p < ebuf) {
+			c = (Cdir*)p;
+			if(c->len == 0)
+				break;
+			if(p+c->len > ebuf)
+				break;
+			if(parsedir(cd, &dx, p, ebuf-p, cvt) == 0 && dx.name != dot && dx.name != dotdot)
+				n++;
+			p += c->len;
+		}
+	}
+
+	m = (n+Ndirblock-1)/Ndirblock * Ndirblock;
+	dir->child = emalloc(m*sizeof dir->child[0]);
+	dir->nchild = n;
+
+	n = 0;
+	for(b=0; b<nb; b++) {
+		assert(n <= dir->nchild);
+		Creadblock(cd, buf, dir->block + b, Blocksize);
+		p = buf;
+		while(p < ebuf) {
+			c = (Cdir*)p;
+			if(c->len == 0)
+				break;
+			if(p+c->len > ebuf)
+				break;
+			if(parsedir(cd, &dx, p, ebuf-p, cvt) == 0 && dx.name != dot && dx.name != dotdot) {
+				assert(n < dir->nchild);
+				dir->child[n++] = dx;
+			}
+			p += c->len;
+		}
+	}
+}
+
+/*
+ * Free the children.  Make sure their children are free too.
+ */
+void
+freekids(Direc *dir)
+{
+	int i;
+
+	for(i=0; i<dir->nchild; i++)
+		assert(dir->child[i].nchild == 0);
+
+	free(dir->child);
+	dir->child = nil;
+	dir->nchild = 0;
+}
+
+/*
+ * Add a whole directory and all its children to our binary tree.
+ */
+static void
+adddir(Cdimg *cd, Dump *d, Direc *dir)
+{
+	int i;
+
+	readkids(cd, dir, isostring);
+	for(i=0; i<dir->nchild; i++) {
+		if(dir->child[i].mode & DMDIR)
+			adddir(cd, d, &dir->child[i]);
+		else
+			addfile(cd, d, atom(dir->name), &dir->child[i]);
+	}
+	freekids(dir);
+}
+
+Dumpdir*
+lookupmd5(Dump *d, uchar *md5)
+{
+	return *ltreewalkmd5(&d->md5root, md5);
+}
+
+void
+adddirx(Cdimg *cd, Dump *d, Direc *dir, int lev)
+{
+	int i;
+	Direc dd;
+
+	if(lev == 2){
+		dd = *dir;
+		adddir(cd, d, &dd);
+		return;
+	}
+	for(i=0; i<dir->nchild; i++)
+		adddirx(cd, d, &dir->child[i], lev+1);
+}
+
+Dump*
+dumpcd(Cdimg *cd, Direc *dir)
+{
+	Dump *d;
+
+	d = emalloc(sizeof *d);
+	d->cd = cd;
+	adddirx(cd, d, dir, 0);
+	return d;
+}
+
+/*
+static ulong
+minblock(Direc *root, int lev)
+{
+	int i;
+	ulong m, n;
+
+	m = root->block;
+	for(i=0; i<root->nchild; i++) {
+		n = minblock(&root->child[i], lev-1);
+		if(m > n)
+			m = n;
+	}
+	return m;
+}
+*/
+
+void
+copybutname(Direc *d, Direc *s)
+{
+	Direc x;
+
+	x = *d;
+	*d = *s;
+	d->name = x.name;
+	d->confname = x.confname;
+}
+
+Direc*
+createdumpdir(Direc *root, XDir *dir, char *utfname)
+{
+	char *p;
+	Direc *d;
+
+	if(utfname[0]=='/')
+		sysfatal("bad dump name '%s'", utfname);
+	p = strchr(utfname, '/');
+	if(p == nil || strchr(p+1, '/'))
+		sysfatal("bad dump name '%s'", utfname);
+	*p++ = '\0';
+	if((d = walkdirec(root, utfname)) == nil)
+		d = adddirec(root, utfname, dir);
+	if(walkdirec(d, p))
+		sysfatal("duplicate dump name '%s/%s'", utfname, p);
+	d = adddirec(d, p, dir);
+	return d;
+}
+
+static void
+rmdirec(Direc *d, Direc *kid)
+{
+	Direc *ekid;
+
+	ekid = d->child+d->nchild;
+	assert(d->child <= kid && kid < ekid);
+	if(ekid != kid+1)
+		memmove(kid, kid+1, (ekid-(kid+1))*sizeof(*kid));
+	d->nchild--;
+}
+
+void
+rmdumpdir(Direc *root, char *utfname)
+{
+	char *p;
+	Direc *d, *dd;
+
+	if(utfname[0]=='/')
+		sysfatal("bad dump name '%s'", utfname);
+	p = strchr(utfname, '/');
+	if(p == nil || strchr(p+1, '/'))
+		sysfatal("bad dump name '%s'", utfname);
+	*p++ = '\0';
+	if((d = walkdirec(root, utfname)) == nil)
+		sysfatal("cannot remove %s/%s: %s does not exist", utfname, p, utfname);
+	p[-1] = '/';
+
+	if((dd = walkdirec(d, p)) == nil)
+		sysfatal("cannot remove %s: does not exist", utfname);
+
+	rmdirec(d, dd);
+	if(d->nchild == 0)
+		rmdirec(root, d);
+}
+
+char*
+adddumpdir(Direc *root, ulong now, XDir *dir)
+{
+	char buf[40], *p;
+	int n;
+	Direc *dday, *dyear;
+	Tm tm;
+
+	tm = *localtime(now);
+	
+	sprint(buf, "%d", tm.year+1900);
+	if((dyear = walkdirec(root, buf)) == nil) {
+		dyear = adddirec(root, buf, dir);
+		assert(dyear != nil);
+	}
+
+	n = 0;
+	sprint(buf, "%.2d%.2d", tm.mon+1, tm.mday);
+	p = buf+strlen(buf);
+	while(walkdirec(dyear, buf))
+		sprint(p, "%d", ++n);
+
+	dday = adddirec(dyear, buf, dir);
+	assert(dday != nil);
+
+	sprint(buf, "%s/%s", dyear->name, dday->name);
+assert(walkdirec(root, buf)==dday);
+	return atom(buf);
+}
+
+/*
+ * The dump directory tree is inferred from a linked list of special blocks.
+ * One block is written at the end of each dump.
+ * The blocks have the form
+ *
+ * plan 9 dump cd
+ * <dump-name> <dump-time> <next-block> <conform-block> <conform-length> \
+ *	<iroot-block> <iroot-length> <jroot-block> <jroot-length>
+ *
+ * If only the first line is present, this is the end of the chain.
+ */
+static char magic[] = "plan 9 dump cd\n";
+ulong
+Cputdumpblock(Cdimg *cd)
+{
+	ulong x;
+
+	Cwseek(cd, cd->nextblock*Blocksize);
+	x = Cwoffset(cd);
+	Cwrite(cd, magic, sizeof(magic)-1);
+	Cpadblock(cd);
+	return x/Blocksize;
+}
+
+int
+hasdump(Cdimg *cd)
+{
+	int i;
+	char buf[128];
+
+	for(i=16; i<24; i++) {
+		Creadblock(cd, buf, i, sizeof buf);
+		if(memcmp(buf, magic, sizeof(magic)-1) == 0)
+			return i;
+	}
+	return 0;
+}
+	
+Direc
+readdumpdirs(Cdimg *cd, XDir *dir, char *(*cvt)(uchar*, int))
+{
+	char buf[Blocksize];
+	char *p, *q, *f[16];
+	int i, nf;
+	ulong db, t;
+	Direc *nr, root;
+	XDir xd;
+
+	mkdirec(&root, dir);
+	db = hasdump(cd);
+	xd = *dir;
+	for(;;){
+		if(db == 0)
+			sysfatal("error in dump blocks");
+
+		Creadblock(cd, buf, db, sizeof buf);
+		if(memcmp(buf, magic, sizeof(magic)-1) != 0)
+			break;
+		p = buf+sizeof(magic)-1;
+		if(p[0] == '\0')
+			break;
+		if((q = strchr(p, '\n')) != nil)
+			*q = '\0';
+
+		nf = tokenize(p, f, nelem(f));
+		i = 5;
+		if(nf < i || (cvt==jolietstring && nf < i+2))
+			sysfatal("error in dump block %lud: nf=%d; p='%s'", db, nf, p);
+		nr = createdumpdir(&root, &xd, f[0]);
+		t = strtoul(f[1], 0, 0);
+		xd.mtime = xd.ctime = xd.atime = t;
+		db = strtoul(f[2], 0, 0);
+		if(cvt == jolietstring)
+			i += 2;
+		nr->block = strtoul(f[i], 0, 0);
+		nr->length = strtoul(f[i+1], 0, 0);
+	}
+	cd->nulldump = db;
+	return root;
+}
+
+extern void addtx(char*, char*);
+
+static int
+isalldigit(char *s)
+{
+	while(*s)
+		if(!isdigit(*s++))
+			return 0;
+	return 1;
+}
+
+void
+readdumpconform(Cdimg *cd)
+{
+	char buf[Blocksize];
+	char *p, *q, *f[10];
+	ulong cb, nc, m, db;
+	int nf;
+
+	db = hasdump(cd);
+	assert(map==nil || map->nt == 0);
+
+	for(;;){
+		if(db == 0)
+			sysfatal("error0 in dump blocks");
+
+		Creadblock(cd, buf, db, sizeof buf);
+		if(memcmp(buf, magic, sizeof(magic)-1) != 0)
+			break;
+		p = buf+sizeof(magic)-1;
+		if(p[0] == '\0')
+			break;
+		if((q = strchr(p, '\n')) != nil)
+			*q = '\0';
+
+		nf = tokenize(p, f, nelem(f));
+		if(nf < 5)
+			sysfatal("error0 in dump block %lud", db);
+
+		db = strtoul(f[2], 0, 0);
+		cb = strtoul(f[3], 0, 0);
+		nc = strtoul(f[4], 0, 0);
+
+		Crseek(cd, cb*Blocksize);
+		m = cb*Blocksize+nc;
+		while(Croffset(cd) < m && (p = Crdline(cd, '\n')) != nil){
+			p[Clinelen(cd)-1] = '\0';
+			if(tokenize(p, f, 2) != 2 || (f[0][0] != 'D' && f[0][0] != 'F')
+			|| strlen(f[0]) != 7 || !isalldigit(f[0]+1))
+				break;
+	
+			addtx(atom(f[1]), atom(f[0]));
+		}
+	}
+	if(map)
+		cd->nconform = map->nt;
+	else
+		cd->nconform = 0;
+}
blob - /dev/null
blob + 320e56d3ec88f4b471148be48f2c2b851c55c628 (mode 644)
--- /dev/null
+++ 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);
+
+}
+
blob - /dev/null
blob + 35b0c0564162f769fc5393fb6f94a400677b6e8c (mode 644)
--- /dev/null
+++ src/cmd/9660/ichar.c
@@ -0,0 +1,206 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <libsec.h>
+#include <ctype.h>
+
+#include "iso9660.h"
+
+/*
+ * ISO 9660 file names must be uppercase, digits, or underscore.
+ * We use lowercase, digits, and underscore, translating lower to upper
+ * in mkisostring, and upper to lower in isostring.
+ * Files with uppercase letters in their names are thus nonconforming.
+ * Conforming files also must have a basename
+ * at most 8 letters and at most one suffix of at most 3 letters.
+ */
+char*
+isostring(uchar *buf, int len)
+{
+	char *p, *q;
+
+	p = emalloc(len+1);
+	memmove(p, buf, len);
+	p[len] = '\0';
+	while(len > 0 && p[len-1] == ' ')
+		p[--len] = '\0';
+	for(q=p; *q; q++)
+		*q = tolower(*q);
+
+	q = atom(p);
+	free(p);
+	return q;
+}
+
+int 
+isisofrog(char c)
+{
+	if(c >= '0' && c <= '9')
+		return 0;
+	if(c >= 'a' && c <= 'z')
+		return 0;
+	if(c == '_')
+		return 0;
+
+	return 1;
+}
+
+int
+isbadiso9660(char *s)
+{
+	char *p, *q;
+	int i;
+
+	if((p = strchr(s, '.')) != nil) {
+		if(p-s > 8)
+			return 1;
+		for(q=s; q<p; q++)
+			if(isisofrog(*q))
+				return 1;
+		if(strlen(p+1) > 3)
+			return 1;
+		for(q=p+1; *q; q++)
+			if(isisofrog(*q))
+				return 1;
+	} else {
+		if(strlen(s) > 8)
+			return 1;
+		for(q=s; *q; q++)
+			if(isisofrog(*q))
+				return 1;
+
+		/*
+		 * we rename files of the form [FD]dddddd
+		 * so they don't interfere with us.
+		 */
+		if(strlen(s) == 7 && (s[0] == 'D' || s[0] == 'F')) {
+			for(i=1; i<7; i++)
+				if(s[i] < '0' || s[i] > '9')
+					break;
+			if(i == 7)
+				return 1;
+		}
+	}
+	return 0;
+}
+
+/*
+ * ISO9660 name comparison
+ * 
+ * The standard algorithm is as follows:
+ *   Take the filenames without extensions, pad the shorter with 0x20s (spaces),
+ *   and do strcmp.  If they are equal, go on.
+ *   Take the extensions, pad the shorter with 0x20s (spaces),
+ *   and do strcmp.  If they are equal, go on.
+ *   Compare the version numbers.
+ *
+ * Since Plan 9 names are not allowed to contain characters 0x00-0x1F,
+ * the padded comparisons are equivalent to using strcmp directly.
+ * We still need to handle the base and extension differently,
+ * so that .foo sorts before !foo.foo.
+ */
+int
+isocmp(const void *va, const void *vb)
+{
+	int i;
+	char s1[32], s2[32], *b1, *b2, *e1, *e2;
+	const Direc *a, *b;
+
+	a = va;
+	b = vb;
+
+	strecpy(s1, s1+sizeof s1, a->confname);
+	b1 = s1;
+	strecpy(s2, s2+sizeof s2, b->confname);
+	b2 = s2;
+	if((e1 = strchr(b1, '.')) != nil)
+		*e1++ = '\0';
+	else
+		e1 = "";
+	if((e2 = strchr(b2, '.')) != nil)
+		*e2++ = '\0';
+	else
+		e2 = "";
+
+	if((i = strcmp(b1, b2)) != 0)
+		return i;
+
+	return strcmp(e1, e2);
+}
+
+static char*
+mkisostring(char *isobuf, int n, char *s)
+{
+	char *p, *q, *eq;
+
+	eq = isobuf+n;
+	for(p=s, q=isobuf; *p && q < eq; p++)
+		if('a' <= *p && *p <= 'z')
+			*q++ = *p+'A'-'a';
+		else
+			*q++ = *p;
+
+	while(q < eq)
+		*q++ = ' ';
+
+	return isobuf;
+}
+
+void
+Cputisopvd(Cdimg *cd, Cdinfo info)
+{
+	char buf[130];
+
+	Cputc(cd, 1);				/* primary volume descriptor */
+	Cputs(cd, "CD001", 5);			/* standard identifier */
+	Cputc(cd, 1);				/* volume descriptor version */
+	Cputc(cd, 0);				/* unused */
+
+	assert(~info.flags & (CDplan9|CDrockridge));
+
+	/* system identifier */
+	strcpy(buf, "");
+	if(info.flags & CDplan9)
+		strcat(buf, "plan 9 ");
+	if(info.flags & CDrockridge)
+		strcat(buf, "rrip ");
+	if(info.flags & CDbootable)
+		strcat(buf, "boot ");
+	if(info.flags & CDconform)
+		strcat(buf, "iso9660");
+	else
+		strcat(buf, "utf8");
+	
+	struprcpy(buf, buf);
+	Cputs(cd, buf, 32);
+
+	Cputs(cd, mkisostring(buf, 32, info.volumename), 32);			/* volume identifier */
+
+	Crepeat(cd, 0, 8);				/* unused */
+	Cputn(cd, 0, 4);				/* volume space size */
+	Crepeat(cd, 0, 32);				/* unused */
+	Cputn(cd, 1, 2);				/* volume set size */
+	Cputn(cd, 1, 2);				/* volume sequence number */
+	Cputn(cd, Blocksize, 2);			/* logical block size */
+	Cputn(cd, 0, 4);				/* path table size */
+	Cputnl(cd, 0, 4);				/* location of Lpath */
+	Cputnl(cd, 0, 4);				/* location of optional Lpath */
+	Cputnm(cd, 0, 4);				/* location of Mpath */
+	Cputnm(cd, 0, 4);				/* location of optional Mpath */
+	Cputisodir(cd, nil, DTroot, 1, Cwoffset(cd));			/* root directory */
+
+	Cputs(cd, mkisostring(buf, 128, info.volumeset), 128);		/* volume set identifier */
+	Cputs(cd, mkisostring(buf, 128, info.publisher), 128);			/* publisher identifier */
+	Cputs(cd, mkisostring(buf, 128, info.preparer), 128);			/* data preparer identifier */
+	Cputs(cd, mkisostring(buf, 128, info.application), 128);		/* application identifier */
+
+	Cputs(cd, "", 37);			/* copyright notice */
+	Cputs(cd, "", 37);			/* abstract */
+	Cputs(cd, "", 37);			/* bibliographic file */
+	Cputdate1(cd, now);				/* volume creation date */
+	Cputdate1(cd, now);				/* volume modification date */
+	Cputdate1(cd, 0);				/* volume expiration date */
+	Cputdate1(cd, 0);				/* volume effective date */
+	Cputc(cd, 1);				/* file structure version */
+	Cpadblock(cd);
+}
blob - /dev/null
blob + 5eefae8830a370d15caf7f80000b8c7d2a112680 (mode 644)
--- /dev/null
+++ src/cmd/9660/iso9660.h
@@ -0,0 +1,424 @@
+/*
+ * iso9660.h
+ *
+ * Routines and data structures to support reading and writing
+ * ISO 9660 CD images. See the ISO 9660 or ECMA 119 standards.
+ *
+ * Also supports Rock Ridge extensions for long file names and Unix stuff.
+ * Also supports Microsoft's Joliet extensions for Unicode and long file names.
+ * Also supports El Torito bootable CD spec.
+ */
+
+typedef struct Cdimg Cdimg;
+typedef struct Cdinfo Cdinfo;
+typedef struct Conform Conform;
+typedef struct Direc Direc;
+typedef struct Dumproot Dumproot;
+typedef struct Voldesc Voldesc;
+typedef struct XDir XDir;
+
+#ifndef CHLINK
+#define CHLINK 0
+#endif
+
+struct XDir {
+	char	*name;
+	char	*uid;
+	char	*gid;
+	char	*symlink;
+	ulong   uidno;   /* Numeric uid */
+	ulong   gidno;   /* Numeric gid */
+
+	ulong	mode;
+	ulong	atime;
+	ulong	mtime;
+	ulong   ctime;
+
+        vlong   length;
+};
+
+/*
+ * A directory entry in a ISO9660 tree.
+ * The extra data (uid, etc.) here is put into the system use areas.
+ */
+struct Direc {
+	char *name;	/* real name */
+	char *confname;	/* conformant name */
+	char *srcfile;	/* file to copy onto the image */
+
+	ulong block;
+	ulong length;
+	int flags;
+
+	char *uid;
+	char *gid;
+	char *symlink;
+	ulong mode;
+	long atime;
+	long ctime;
+	long mtime;
+
+	ulong uidno;
+	ulong gidno;
+
+	Direc *child;
+	int nchild;
+};
+enum {  /* Direc flags */
+	Dbadname = 1<<0,  /* Non-conformant name     */
+};
+
+/*
+ * Data found in a volume descriptor.
+ */
+struct Voldesc {
+	char *systemid;
+	char *volumeset;
+	char *publisher;
+	char *preparer;
+	char *application;
+
+	/* file names for various parameters */
+	char *abstract;
+	char *biblio;
+	char *notice;
+
+	/* path table */
+	ulong pathsize;
+	ulong lpathloc;
+	ulong mpathloc;
+
+	/* root of file tree */
+	Direc root;	
+};
+
+/*
+ * An ISO9660 CD image.  Various parameters are kept in memory but the
+ * real image file is opened for reading and writing on fd.
+ *
+ * The bio buffers brd and bwr moderate reading and writing to the image.
+ * The routines we use are careful to flush one before or after using the other,
+ * as necessary.
+ */
+struct Cdimg {
+	char *file;
+	int fd;
+	ulong dumpblock;
+	ulong nextblock;
+	ulong iso9660pvd;
+	ulong jolietsvd;
+	ulong pathblock;
+	ulong rrcontin; /* rock ridge continuation offset */
+	ulong nulldump;	/* next dump block */
+	ulong nconform;	/* number of conform entries written already */
+	ulong bootcatptr;
+	ulong bootcatblock;
+	ulong bootimageptr;
+	Direc *bootdirec;
+	char *bootimage;
+	
+	Biobuf brd;
+	Biobuf bwr;
+
+	int flags;
+
+	Voldesc iso;
+	Voldesc joliet;
+};
+enum {	/* Cdimg->flags, Cdinfo->flags */
+	CDjoliet = 1<<0,
+	CDplan9 = 1<<1,
+	CDconform = 1<<2,
+	CDrockridge = 1<<3,
+	CDnew = 1<<4,
+	CDdump = 1<<5,
+	CDbootable = 1<<6,
+};
+
+typedef struct Tx Tx;
+struct Tx {
+	char *bad;	/* atoms */
+	char *good;
+};
+
+struct Conform {
+	Tx *t;
+	int nt;	/* delta = 32 */
+};
+
+struct Cdinfo {
+	int flags;
+
+	char *volumename;
+
+	char *volumeset;
+	char *publisher;
+	char *preparer;
+	char *application;
+	char *bootimage;
+};
+
+enum {
+	Blocklen = 2048,
+};
+
+/*
+ * This is a doubly binary tree.
+ * We have a tree keyed on the MD5 values
+ * as well as a tree keyed on the block numbers.
+ */
+typedef struct Dump Dump;
+typedef struct Dumpdir Dumpdir;
+
+struct Dump {
+	Cdimg *cd;
+	Dumpdir *md5root;
+	Dumpdir *blockroot;
+};
+
+struct Dumpdir {
+	char *name;
+	uchar md5[MD5dlen];
+	ulong block;
+	ulong length;
+	Dumpdir *md5left;
+	Dumpdir *md5right;
+	Dumpdir *blockleft;
+	Dumpdir *blockright;
+};
+
+struct Dumproot {
+	char *name;
+	int nkid;
+	Dumproot *kid;
+	Direc root;
+	Direc jroot;
+};
+
+/*
+ * ISO9660 on-CD structures.
+ */
+typedef struct Cdir Cdir;
+typedef struct Cpath Cpath;
+typedef struct Cvoldesc Cvoldesc;
+
+/* a volume descriptor block */
+struct Cvoldesc {
+	uchar	magic[8];	/* 0x01, "CD001", 0x01, 0x00 */
+	uchar	systemid[32];	/* system identifier */
+	uchar	volumeid[32];	/* volume identifier */
+	uchar	unused[8];	/* character set in secondary desc */
+	uchar	volsize[8];	/* volume size */
+	uchar	charset[32];
+	uchar	volsetsize[4];	/* volume set size = 1 */
+	uchar	volseqnum[4];	/* volume sequence number = 1 */
+	uchar	blocksize[4];	/* logical block size */
+	uchar	pathsize[8];	/* path table size */
+	uchar	lpathloc[4];	/* Lpath */
+	uchar	olpathloc[4];	/* optional Lpath */
+	uchar	mpathloc[4];	/* Mpath */
+	uchar	ompathloc[4];	/* optional Mpath */
+	uchar	rootdir[34];	/* directory entry for root */
+	uchar	volumeset[128];	/* volume set identifier */
+	uchar	publisher[128];
+	uchar	preparer[128];	/* data preparer identifier */
+	uchar	application[128];	/* application identifier */
+	uchar	notice[37];	/* copyright notice file */
+	uchar	abstract[37];	/* abstract file */
+	uchar	biblio[37];	/* bibliographic file */
+	uchar	cdate[17];	/* creation date */
+	uchar	mdate[17];	/* modification date */
+	uchar	xdate[17];	/* expiration date */
+	uchar	edate[17];	/* effective date */
+	uchar	fsvers;		/* file system version = 1 */
+};
+
+/* a directory entry */
+struct Cdir {
+	uchar	len;
+	uchar	xlen;
+	uchar	dloc[8];
+	uchar	dlen[8];
+	uchar	date[7];
+	uchar	flags;
+	uchar	unitsize;
+	uchar	gapsize;
+	uchar	volseqnum[4];
+	uchar	namelen;
+	uchar	name[1];	/* chumminess */
+};
+
+/* a path table entry */
+struct Cpath {
+	uchar   namelen;
+	uchar   xlen;
+	uchar   dloc[4];
+	uchar   parent[2];
+	uchar   name[1];        /* chumminess */
+};
+
+enum { /* Rockridge flags */
+	RR_PX = 1<<0,
+	RR_PN = 1<<1,
+	RR_SL = 1<<2,
+	RR_NM = 1<<3,
+	RR_CL = 1<<4,
+	RR_PL = 1<<5,
+	RR_RE = 1<<6,
+	RR_TF = 1<<7,
+};
+
+enum { /* CputrripTF type argument */
+	TFcreation = 1<<0,
+	TFmodify = 1<<1,
+	TFaccess = 1<<2,
+	TFattributes = 1<<3,
+	TFbackup = 1<<4,
+	TFexpiration = 1<<5,
+	TFeffective = 1<<6,
+	TFlongform = 1<<7,
+};
+
+enum { /* CputrripNM flag types */
+	NMcontinue = 1<<0,
+	NMcurrent = 1<<1,
+	NMparent = 1<<2,
+	NMroot = 1<<3,
+	NMvolroot = 1<<4,
+	NMhost = 1<<5,
+};
+
+/* boot.c */
+void Cputbootvol(Cdimg*);
+void Cputbootcat(Cdimg*);
+void Cupdatebootvol(Cdimg*);
+void Cupdatebootcat(Cdimg*);
+void findbootimage(Cdimg*, Direc*);
+
+/* cdrdwr.c */
+Cdimg *createcd(char*, Cdinfo);
+Cdimg *opencd(char*, Cdinfo);
+void Creadblock(Cdimg*, void*, ulong, ulong);
+ulong big(void*, int);
+ulong little(void*, int);
+int parsedir(Cdimg*, Direc*, uchar*, int, char *(*)(uchar*, int));
+void setroot(Cdimg*, ulong, ulong, ulong);
+void setvolsize(Cdimg*, ulong, ulong);
+void setpathtable(Cdimg*, ulong, ulong, ulong, ulong);
+void Cputc(Cdimg*, int);
+void Cputnl(Cdimg*, ulong, int);
+void Cputnm(Cdimg*, ulong, int);
+void Cputn(Cdimg*, long, int);
+void Crepeat(Cdimg*, int, int);
+void Cputs(Cdimg*, char*, int);
+void Cwrite(Cdimg*, void*, int);
+void Cputr(Cdimg*, Rune);
+void Crepeatr(Cdimg*, Rune, int);
+void Cputrs(Cdimg*, Rune*, int);
+void Cputrscvt(Cdimg*, char*, int);
+void Cpadblock(Cdimg*);
+void Cputdate(Cdimg*, ulong);
+void Cputdate1(Cdimg*, ulong);
+void Cread(Cdimg*, void*, int);
+void Cwflush(Cdimg*);
+void Cwseek(Cdimg*, ulong);
+ulong Cwoffset(Cdimg*);
+ulong Croffset(Cdimg*);
+int Cgetc(Cdimg*);
+void Crseek(Cdimg*, ulong);
+char *Crdline(Cdimg*, int);
+int Clinelen(Cdimg*);
+
+/* conform.c */
+void rdconform(Cdimg*);
+char *conform(char*, int);
+void wrconform(Cdimg*, int, ulong*, ulong*);
+
+/* direc.c */
+void mkdirec(Direc*, XDir*);
+Direc *walkdirec(Direc*, char*);
+Direc *adddirec(Direc*, char*, XDir*);
+void copydirec(Direc*, Direc*);
+void checknames(Direc*, int (*)(char*));
+void convertnames(Direc*, char* (*)(char*, char*));
+void dsort(Direc*, int (*)(const void*, const void*));
+void setparents(Direc*);
+
+/* dump.c */
+ulong Cputdumpblock(Cdimg*);
+int hasdump(Cdimg*);
+Dump *dumpcd(Cdimg*, Direc*);
+Dumpdir *lookupmd5(Dump*, uchar*);
+void insertmd5(Dump*, char*, uchar*, ulong, ulong);
+
+Direc readdumpdirs(Cdimg*, XDir*, char*(*)(uchar*,int));
+char *adddumpdir(Direc*, ulong, XDir*);
+void copybutname(Direc*, Direc*);
+
+void readkids(Cdimg*, Direc*, char*(*)(uchar*,int));
+void freekids(Direc*);
+void readdumpconform(Cdimg*);
+void rmdumpdir(Direc*, char*);
+
+/* ichar.c */
+char *isostring(uchar*, int);
+int isbadiso9660(char*);
+int isocmp(const void*, const void*);
+int isisofrog(char);
+void Cputisopvd(Cdimg*, Cdinfo);
+
+/* jchar.c */
+char *jolietstring(uchar*, int);
+int isbadjoliet(char*);
+int jolietcmp(const void*, const void*);
+int isjolietfrog(Rune);
+void Cputjolietsvd(Cdimg*, Cdinfo);
+
+/* path.c */
+void writepathtables(Cdimg*);
+
+/* util.c */
+void *emalloc(ulong);
+void *erealloc(void*, ulong);
+char *atom(char*);
+char *struprcpy(char*, char*);
+int chat(char*, ...);
+
+/* unix.c, plan9.c */
+void dirtoxdir(XDir*, Dir*);
+void fdtruncate(int, ulong);
+long uidno(char*);
+long gidno(char*);
+
+/* rune.c */
+Rune *strtorune(Rune*, char*);
+Rune *runechr(Rune*, Rune);
+int runecmp(Rune*, Rune*);
+
+/* sysuse.c */
+int Cputsysuse(Cdimg*, Direc*, int, int, int);
+
+/* write.c */
+void writefiles(Dump*, Cdimg*, Direc*);
+void writedirs(Cdimg*, Direc*, int(*)(Cdimg*, Direc*, int, int, int));
+void writedumpdirs(Cdimg*, Direc*, int(*)(Cdimg*, Direc*, int, int, int));
+int Cputisodir(Cdimg*, Direc*, int, int, int);
+int Cputjolietdir(Cdimg*, Direc*, int, int, int);
+void Cputendvd(Cdimg*);
+
+enum { 
+	Blocksize = 2048,
+	Ndirblock = 16,		/* directory blocks allocated at once */
+
+	DTdot = 0,
+	DTdotdot,
+	DTiden,
+	DTroot,
+	DTrootdot,
+};
+
+extern ulong now;
+extern Conform *map;
+extern int chatty;
+extern int docolon;
+extern int mk9660;
blob - /dev/null
blob + c49da6351ce9598bad20a1b90561d07f280f5dd7 (mode 644)
--- /dev/null
+++ src/cmd/9660/jchar.c
@@ -0,0 +1,138 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <libsec.h>
+
+#include "iso9660.h"
+
+char*
+jolietstring(uchar *buf, int len)
+{
+	char *p, *q;
+	int i;
+	Rune *rp;
+
+	rp = emalloc(sizeof(Rune)*(len/2+1));
+	p = emalloc(UTFmax*(len/2+1));
+
+	for(i=0; i<len/2; i++)
+		rp[i] = (buf[2*i]<<8) | buf[2*i+1];
+	rp[i] = (Rune)'\0';
+
+	snprint(p, UTFmax*(len/2+1), "%S", rp);
+	q = atom(p);
+	free(p);
+	return q;
+}
+
+/*
+ * Joliet name validity check 
+ * 
+ * Joliet names have length at most 128 bytes (64 runes),
+ * and cannot contain '*', '/', ':', ';', '?', or '\'.
+ */
+int
+isjolietfrog(Rune r)
+{
+	return r==L'*' || r==L'/' || r==L':' 
+		|| r==';' || r=='?' || r=='\\';
+}
+
+int
+isbadjoliet(char *s)
+{
+	Rune r[256], *p;
+
+	if(utflen(s) > 64)
+		return 1;
+	strtorune(r, s);
+	for(p=r; *p; p++)
+		if(isjolietfrog(*p))
+			return 1;
+	return 0;
+}
+
+/*
+ * Joliet name comparison
+ *
+ * The standard algorithm is the ISO9660 algorithm but
+ * on the encoded Runes.  Runes are encoded in big endian
+ * format, so we can just use runecmp.
+ * 
+ * Padding is with zeros, but that still doesn't affect us.
+ */
+
+static Rune emptystring[] = { (Rune)0 };
+int
+jolietcmp(const void *va, const void *vb)
+{
+	int i;
+	Rune s1[256], s2[256], *b1, *b2, *e1, *e2;	/*BUG*/
+	const Direc *a, *b;
+
+	a = va;
+	b = vb;
+
+	b1 = strtorune(s1, a->confname);
+	b2 = strtorune(s2, b->confname);
+	if((e1 = runechr(b1, (Rune)'.')) != nil)
+		*e1++ = '\0';
+	else
+		e1 = emptystring;
+
+	if((e2 = runechr(b2, (Rune)'.')) != nil)
+		*e2++ = '\0';
+	else
+		e2 = emptystring;
+
+	if((i = runecmp(b1, b2)) != 0)
+		return i;
+
+	return runecmp(e1, e2);
+}
+
+/*
+ * Write a Joliet secondary volume descriptor.
+ */
+void
+Cputjolietsvd(Cdimg *cd, Cdinfo info)
+{
+	Cputc(cd, 2);				/* secondary volume descriptor */
+	Cputs(cd, "CD001", 5);			/* standard identifier */
+	Cputc(cd, 1);				/* volume descriptor version */
+	Cputc(cd, 0);				/* unused */
+
+	Cputrscvt(cd, "Joliet Plan 9", 32);			/* system identifier */
+	Cputrscvt(cd, info.volumename, 32);			/* volume identifier */
+
+	Crepeat(cd, 0, 8);				/* unused */
+	Cputn(cd, 0, 4);				/* volume space size */
+	Cputc(cd, 0x25);				/* escape sequences: UCS-2 Level 2 */
+	Cputc(cd, 0x2F);
+	Cputc(cd, 0x43);
+
+	Crepeat(cd, 0, 29);
+	Cputn(cd, 1, 2);				/* volume set size */
+	Cputn(cd, 1, 2);				/* volume sequence number */
+	Cputn(cd, Blocksize, 2);			/* logical block size */
+	Cputn(cd, 0, 4);				/* path table size */
+	Cputnl(cd, 0, 4);				/* location of Lpath */
+	Cputnl(cd, 0, 4);				/* location of optional Lpath */
+	Cputnm(cd, 0, 4);				/* location of Mpath */
+	Cputnm(cd, 0, 4);				/* location of optional Mpath */
+	Cputjolietdir(cd, nil, DTroot, 1, Cwoffset(cd));			/* root directory */
+	Cputrscvt(cd, info.volumeset, 128);		/* volume set identifier */
+	Cputrscvt(cd, info.publisher, 128);			/* publisher identifier */
+	Cputrscvt(cd, info.preparer, 128);			/* data preparer identifier */
+	Cputrscvt(cd, info.application, 128);		/* application identifier */
+	Cputrscvt(cd, "", 37);			/* copyright notice */
+	Cputrscvt(cd, "", 37);			/* abstract */
+	Cputrscvt(cd, "", 37);			/* bibliographic file */
+	Cputdate1(cd, now);				/* volume creation date */
+	Cputdate1(cd, now);				/* volume modification date */
+	Cputdate1(cd, 0);				/* volume expiration date */
+	Cputdate1(cd, 0);				/* volume effective date */
+	Cputc(cd, 1);				/* file structure version */
+	Cpadblock(cd);
+}
+
blob - /dev/null
blob + 1824f8e745b0015235f34ce105b17ad385c6d22a (mode 755)
--- /dev/null
+++ src/cmd/9660/mk9660.rc
@@ -0,0 +1,5 @@
+#!/bin/rc
+
+# the master copy of this file is /sys/src/cmd/disk/9660/mk9660.rc
+# do NOT edit the copies in /*/bin/disk.
+exec disk/dump9660 -M $*
blob - /dev/null
blob + a127b8295976b12f0d00b0b5bd9201f59c687ddc (mode 755)
--- /dev/null
+++ src/cmd/9660/mk9660.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+# the master copy of this file is /sys/src/cmd/disk/9660/mk9660.rc
+# do NOT edit the copies in /*/bin/disk.
+exec disk/dump9660 -M $*
blob - /dev/null
blob + 06d996f02ce2e28f4b1aea7a121b65fe1ebd7f32 (mode 644)
--- /dev/null
+++ src/cmd/9660/mkfile
@@ -0,0 +1,34 @@
+<$PLAN9/src/mkhdr
+
+TARG=dump9660 mk9660
+
+OFILES=
+
+DFILES=\
+	boot.$O\
+	cdrdwr.$O\
+	conform.$O\
+	direc.$O\
+	dump.$O\
+	dump9660.$O\
+	ichar.$O\
+	jchar.$O\
+	path.$O\
+	unix.$O\
+	rune.$O\
+	sysuse.$O\
+	util.$O\
+	write.$O\
+
+HFILES=iso9660.h
+
+SHORTLIB=sec disk bio 9
+<$PLAN9/src/mkmany
+
+$O.dump9660: $DFILES
+
+mk9660.$O:V:
+	# nothing
+
+$O.mk9660: mk9660.sh
+	cp mk9660.sh $target
blob - /dev/null
blob + f2757dba93f6ec5f6f325d0491b8cb64ce546434 (mode 644)
--- /dev/null
+++ src/cmd/9660/path.c
@@ -0,0 +1,155 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <libsec.h>
+
+#include "iso9660.h"
+
+/*
+ * Add the requisite path tables to the CD image.
+ * They get put on the end once everything else is done.
+ * We use the path table itself as a queue in the breadth-first
+ * traversal of the tree.  
+ *
+ * The only problem with this is that the path table does not
+ * store the lengths of the directories.  So we keep an explicit
+ * map in an array in memory.
+ */
+
+enum {
+	Big,
+	Little
+};
+
+static void
+Crdpath(Cdimg *cd, Cpath *p)
+{
+	p->namelen = Cgetc(cd);
+	if(p->namelen == 0) {
+		Crseek(cd, (Croffset(cd)+Blocksize-1)/Blocksize * Blocksize);
+		p->namelen = Cgetc(cd);
+		assert(p->namelen != 0);
+	}
+
+	p->xlen = Cgetc(cd);
+	assert(p->xlen == 0);	/* sanity, might not be true if we start using the extended fields */
+
+	Cread(cd, p->dloc, 4);
+	Cread(cd, p->parent, 2);
+	p->name[0] = '\0';
+	Crseek(cd, Croffset(cd)+p->namelen+p->xlen+(p->namelen&1));	/* skip name, ext data */
+}
+
+static void
+writepath(Cdimg *cd, Cdir *c, int parent, int size)
+{
+/*
+	DO NOT UNCOMMENT THIS CODE.
+	This commented-out code is here only so that no one comes
+	along and adds it later.
+
+	The ISO 9660 spec is silent about whether path table entries
+	need to be padded so that they never cross block boundaries.
+	It would be reasonable to assume that they are like every other
+	data structure in the bloody spec; this code pads them out.
+
+	Empirically, though, they're NOT padded.  Windows NT and
+	derivatives are the only known current operating systems
+	that actually read these things.
+
+	int l;
+
+	l = 1+1+4+2+c->namelen;
+	if(Cwoffset(cd)/Blocksize != (Cwoffset(cd)+l)/Blocksize)
+		Cpadblock(cd);
+*/
+	Cputc(cd, c->namelen);
+	Cputc(cd, 0);
+	Cwrite(cd, c->dloc + (size==Little ? 0 : 4), 4);
+	(size==Little ? Cputnl : Cputnm)(cd, parent, 2);
+	Cwrite(cd, c->name, c->namelen);
+	if(c->namelen & 1)
+		Cputc(cd, 0);
+}
+
+static ulong*
+addlength(ulong *a, ulong x, int n)
+{
+	if(n%128==0)
+		a = erealloc(a, (n+128)*sizeof a[0]);
+	a[n] = x;
+	return a;
+}
+
+static ulong
+writepathtable(Cdimg *cd, ulong vdblock, int size)
+{
+	int rp, wp;
+	uchar buf[Blocksize];
+	ulong bk, end, i, *len, n, rdoff, start;
+	Cdir *c;
+	Cpath p;
+
+	Creadblock(cd, buf, vdblock, Blocksize);
+	c = (Cdir*)(buf+offsetof(Cvoldesc, rootdir[0]));
+
+	rp = 0;
+	wp = 0;
+	len = nil;
+	start = cd->nextblock*Blocksize;
+	Cwseek(cd, start);
+	Crseek(cd, start);
+	writepath(cd, c, 1, size);
+	len = addlength(len, little(c->dlen, 4), wp);
+	wp++;
+
+	while(rp < wp) {
+		Crdpath(cd, &p);
+		n = (len[rp]+Blocksize-1)/Blocksize;
+		rp++;
+		bk = (size==Big ? big : little)(p.dloc, 4);
+		rdoff = Croffset(cd);
+		for(i=0; i<n; i++) {
+			Creadblock(cd, buf, bk+i, Blocksize);
+			c = (Cdir*)buf;
+			if(i != 0 && c->namelen == 1 && c->name[0] == '\0')	/* hit another directory; stop */
+				break;
+			while(c->len && c->namelen && (uchar*)c+c->len < buf+Blocksize) {
+				if((c->flags & 0x02) && (c->namelen > 1 || c->name[0] > '\001')) {	/* directory */
+					writepath(cd, c, rp, size);
+					len = addlength(len, little(c->dlen, 4), wp);
+					wp++;
+				}
+				c = (Cdir*)((uchar*)c+c->len);
+			}
+		}
+		Crseek(cd, rdoff);
+	}
+	end = Cwoffset(cd);
+	Cpadblock(cd);
+	return end-start;
+}
+
+
+static void
+writepathtablepair(Cdimg *cd, ulong vdblock)
+{
+	ulong bloc, lloc, sz, sz2;
+
+	lloc = cd->nextblock;
+	sz = writepathtable(cd, vdblock, Little);
+	bloc = cd->nextblock;
+	sz2 = writepathtable(cd, vdblock, Big);
+	assert(sz == sz2);
+	setpathtable(cd, vdblock, sz, lloc, bloc);
+}
+
+void
+writepathtables(Cdimg *cd)
+{
+	cd->pathblock = cd->nextblock;
+
+	writepathtablepair(cd, cd->iso9660pvd);
+	if(cd->flags & CDjoliet)
+		writepathtablepair(cd, cd->jolietsvd);
+}
blob - /dev/null
blob + 25f04e9216d01415a24225b8e713937c1228ba13 (mode 644)
--- /dev/null
+++ src/cmd/9660/plan9.c
@@ -0,0 +1,27 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <libsec.h>
+#include <disk.h>
+#include "iso9660.h"
+
+void
+dirtoxdir(XDir *xd, Dir *d)
+{
+	xd->name = atom(d->name);
+	xd->uid = atom(d->uid);
+	xd->gid = atom(d->gid);
+	xd->uidno = 0;
+	xd->gidno = 0;
+	xd->mode = d->mode;
+	xd->atime = d->atime;
+	xd->mtime = d->mtime;
+	xd->ctime = 0;
+	xd->length = d->length;
+};
+
+void
+fdtruncate(int fd, ulong size)
+{
+	USED(fd, size);
+}
blob - /dev/null
blob + 3a436f4a320ad05a22b0a584832ab7d95f79d4f5 (mode 644)
--- /dev/null
+++ src/cmd/9660/rune.c
@@ -0,0 +1,39 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <libsec.h>
+
+#include "iso9660.h"
+
+Rune*
+strtorune(Rune *r, char *s)
+{
+	Rune *or;
+
+	if(s == nil)
+		return nil;
+
+	or = r;
+	while(*s)
+		s += chartorune(r++, s);
+	*r = L'\0';
+	return or;
+}
+
+Rune*
+runechr(Rune *s, Rune c)
+{
+	for(; *s; s++)
+		if(*s == c)
+			return s;
+	return nil;
+}
+
+int
+runecmp(Rune *s, Rune *t)
+{
+	while(*s && *t && *s == *t)
+		s++, t++;
+	return *s - *t;
+}
+
blob - /dev/null
blob + dc326c6a850d04f6348236269b24bab242c6fdeb (mode 644)
--- /dev/null
+++ src/cmd/9660/sysuse.c
@@ -0,0 +1,613 @@
+/*
+ * To understand this code, see Rock Ridge Interchange Protocol
+ * standard 1.12 and System Use Sharing Protocol version 1.12
+ * (search for rrip112.ps and susp112.ps on the web).
+ *
+ * Even better, go read something else.
+ */
+
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <libsec.h>
+#include "iso9660.h"
+
+static long mode(Direc*, int);
+static long nlink(Direc*);
+static ulong suspdirflags(Direc*, int);
+static ulong CputsuspCE(Cdimg *cd, ulong offset);
+static int CputsuspER(Cdimg*, int);
+static int CputsuspRR(Cdimg*, int, int);
+static int CputsuspSP(Cdimg*, int);
+//static int CputsuspST(Cdimg*, int);
+static int Cputrripname(Cdimg*, char*, int, char*, int);
+static int CputrripSL(Cdimg*, int, int, char*, int);
+static int CputrripPX(Cdimg*, Direc*, int, int);
+static int CputrripTF(Cdimg*, Direc*, int, int);
+
+/*
+ * Patch the length field in a CE record.
+ */
+static void
+setcelen(Cdimg *cd, ulong woffset, ulong len)
+{
+	ulong o;
+
+	o = Cwoffset(cd);
+	Cwseek(cd, woffset);
+	Cputn(cd, len, 4);
+	Cwseek(cd, o);
+}
+
+/*
+ * Rock Ridge data is put into little blockettes, which can be
+ * at most 256 bytes including a one-byte length.  Some number
+ * of blockettes get packed together into a normal 2048-byte block.
+ * Blockettes cannot cross block boundaries. 
+ *
+ * A Cbuf is a blockette buffer.  Len contains 
+ * the length of the buffer written so far, and we can
+ * write up to 254-28.  
+ *
+ * We only have one active Cbuf at a time; cdimg.rrcontin is the byte
+ * offset of the beginning of that Cbuf.
+ *
+ * The blockette can be at most 255 bytes.  The last 28
+ * will be (in the worst case) a CE record pointing at
+ * a new blockette.  If we do write 255 bytes though,
+ * we'll try to pad it out to be even, and overflow.
+ * So the maximum is 254-28.
+ *
+ * Ceoffset contains the offset to be used with setcelen
+ * to patch the CE pointing at the Cbuf once we know how
+ * long the Cbuf is.
+ */
+typedef struct Cbuf Cbuf;
+struct Cbuf {
+	int len;	/* written so far, of 254-28 */
+	ulong ceoffset;
+};
+
+static int
+freespace(Cbuf *cp)
+{
+	return (254-28) - cp->len;
+}
+
+static Cbuf*
+ensurespace(Cdimg *cd, int n, Cbuf *co, Cbuf *cn, int dowrite)
+{
+	ulong end;
+
+	if(co->len+n <= 254-28) {
+		co->len += n;
+		return co;
+	}
+
+	co->len += 28;
+	assert(co->len <= 254);
+
+	if(dowrite == 0) {
+		cn->len = n;
+		return cn;
+	}
+
+	/*
+	 * the current blockette is full; update cd->rrcontin and then
+ 	 * write a CE record to finish it.  Unfortunately we need to 
+	 * figure out which block will be next before we write the CE.
+	 */
+	end = Cwoffset(cd)+28;
+
+	/*
+	 * if we're in a continuation blockette, update rrcontin.
+	 * also, write our length into the field of the CE record
+	 * that points at us.
+	 */
+	if(cd->rrcontin+co->len == end) {
+		assert(cd->rrcontin != 0);
+		assert(co == cn);
+		cd->rrcontin += co->len;
+		setcelen(cd, co->ceoffset, co->len);
+	} else
+		assert(co != cn);
+
+	/*
+	 * if the current continuation block can't fit another
+	 * blockette, then start a new continuation block.
+	 * rrcontin = 0 (mod Blocksize) means we just finished
+	 * one, not that we've just started one.
+	 */
+	if(cd->rrcontin%Blocksize == 0
+	|| cd->rrcontin/Blocksize != (cd->rrcontin+256)/Blocksize) {
+		cd->rrcontin = cd->nextblock*Blocksize;
+		cd->nextblock++;
+	}
+
+	cn->ceoffset = CputsuspCE(cd, cd->rrcontin);
+
+	assert(Cwoffset(cd) == end);
+
+	cn->len = n;
+	Cwseek(cd, cd->rrcontin);
+	assert(cd->rrcontin != 0);
+
+	return cn;
+}
+	
+/*
+ * Put down the name, but we might need to break it
+ * into chunks so that each chunk fits in 254-28-5 bytes.
+ * What a crock.
+ *
+ * The new Plan 9 format uses strings of this form too, 
+ * since they're already there.
+ */
+Cbuf*
+Cputstring(Cdimg *cd, Cbuf *cp, Cbuf *cn, char *nm, char *p, int flags, int dowrite)
+{
+	char buf[256], *q;
+	int free;
+
+	for(; p[0] != '\0'; p = q) {
+		cp = ensurespace(cd, 5+1, cp, cn, dowrite);
+		cp->len -= 5+1;
+		free = freespace(cp);
+		assert(5+1 <= free && free < 256);
+
+		strncpy(buf, p, free-5);
+		buf[free-5] = '\0';
+		q = p+strlen(buf);
+		p = buf;
+
+		ensurespace(cd, 5+strlen(p), cp, nil, dowrite);	/* nil: better not use this. */
+		Cputrripname(cd, nm, flags | (q[0] ? NMcontinue : 0), p, dowrite);
+	}
+	return cp;
+}
+
+/*
+ * Write a Rock Ridge SUSP set of records for a directory entry.
+ */
+int
+Cputsysuse(Cdimg *cd, Direc *d, int dot, int dowrite, int initlen)
+{
+	char buf[256], buf0[256], *nextpath, *p, *path, *q;
+	int flags, free, m, what;
+	ulong o;
+	Cbuf cn, co, *cp;
+
+	assert(cd != nil);
+	assert((initlen&1) == 0);
+
+	if(dot == DTroot)
+		return 0;
+
+	co.len = initlen;
+
+	o = Cwoffset(cd);
+
+	assert(dowrite==0 || Cwoffset(cd) == o+co.len-initlen);
+	cp = &co;
+
+	if (dot == DTrootdot) {
+		m = CputsuspSP(cd, 0);
+		cp = ensurespace(cd, m, cp, &cn, dowrite);
+		CputsuspSP(cd, dowrite);
+
+		m = CputsuspER(cd, 0);
+		cp = ensurespace(cd, m, cp, &cn, dowrite);
+		CputsuspER(cd, dowrite);
+	}
+
+	/*
+	 * In a perfect world, we'd be able to omit the NM
+	 * entries when our name was all lowercase and conformant,
+	 * but OpenBSD insists on uppercasing (really, not lowercasing)
+	 * the ISO9660 names.
+	 */
+	what = RR_PX | RR_TF | RR_NM;
+	if(d != nil && (d->mode & CHLINK))
+		what |= RR_SL;
+
+	m = CputsuspRR(cd, what, 0);
+	cp = ensurespace(cd, m, cp, &cn, dowrite);	
+	CputsuspRR(cd, what, dowrite);
+
+	if(what & RR_PX) {
+		m = CputrripPX(cd, d, dot, 0);
+		cp = ensurespace(cd, m, cp, &cn, dowrite);
+		CputrripPX(cd, d, dot, dowrite);
+	}
+
+	if(what & RR_NM) {
+		if(dot == DTiden)
+			p = d->name;
+		else if(dot == DTdotdot)
+			p = "..";
+		else
+			p = ".";
+
+		flags = suspdirflags(d, dot);
+		assert(dowrite==0 || cp != &co || Cwoffset(cd) == o+co.len-initlen);
+		cp = Cputstring(cd, cp, &cn, "NM", p, flags, dowrite);
+	}
+
+	/*
+	 * Put down the symbolic link.  This is even more of a crock.
+	 * Not only are the individual elements potentially split, 
+	 * but the whole path itself can be split across SL blocks.
+	 * To keep the code simple as possible (really), we write
+	 * only one element per SL block, wasting 6 bytes per element.
+	 */
+	if(what & RR_SL) {
+		for(path=d->symlink; path[0] != '\0'; path=nextpath) {
+			/* break off one component */
+			if((nextpath = strchr(path, '/')) == nil)
+				nextpath = path+strlen(path);
+			strncpy(buf0, path, nextpath-path);
+			buf0[nextpath-path] = '\0';
+			if(nextpath[0] == '/')
+				nextpath++;
+			p = buf0;
+
+			/* write the name, perhaps broken into pieces */
+			if(strcmp(p, "") == 0)
+				flags = NMroot;
+			else if(strcmp(p, ".") == 0)
+				flags = NMcurrent;
+			else if(strcmp(p, "..") == 0)
+				flags = NMparent;
+			else
+				flags = 0;
+
+			/* the do-while handles the empty string properly */
+			do {
+				/* must have room for at least 1 byte of name */
+				cp = ensurespace(cd, 7+1, cp, &cn, dowrite);
+				cp->len -= 7+1;
+				free = freespace(cp);
+				assert(7+1 <= free && free < 256);
+
+				strncpy(buf, p, free-7);
+				buf[free-7] = '\0';
+				q = p+strlen(buf);
+				p = buf;
+
+				/* nil: better not need to expand */
+				assert(7+strlen(p) <= free);
+				ensurespace(cd, 7+strlen(p), cp, nil, dowrite);
+				CputrripSL(cd, nextpath[0], flags | (q[0] ? NMcontinue : 0), p, dowrite);
+				p = q;
+			} while(p[0] != '\0');
+		}
+	}
+
+	assert(dowrite==0 || cp != &co || Cwoffset(cd) == o+co.len-initlen);
+
+	if(what & RR_TF) {
+		m = CputrripTF(cd, d, TFcreation|TFmodify|TFaccess|TFattributes, 0);
+		cp = ensurespace(cd, m, cp, &cn, dowrite);
+		CputrripTF(cd, d, TFcreation|TFmodify|TFaccess|TFattributes, dowrite);
+	}
+	assert(dowrite==0 || cp != &co || Cwoffset(cd) == o+co.len-initlen);
+
+	if(cp == &cn && dowrite) {
+		/* seek out of continuation, but mark our place */
+		cd->rrcontin = Cwoffset(cd);
+		setcelen(cd, cn.ceoffset, cn.len);
+		Cwseek(cd, o+co.len-initlen);
+	}
+
+	if(co.len & 1) {
+		co.len++;
+		if(dowrite)
+			Cputc(cd, 0);
+	}
+
+	if(dowrite) {
+		if(Cwoffset(cd) != o+co.len-initlen)
+			fprint(2, "offset %lud o+co.len-initlen %lud\n", Cwoffset(cd), o+co.len-initlen);
+		assert(Cwoffset(cd) == o+co.len-initlen);
+	} else
+		assert(Cwoffset(cd) == o);
+
+	assert(co.len <= 255);
+	return co.len - initlen;
+}
+
+static char SUSPrrip[10] = "RRIP_1991A";
+static char SUSPdesc[84] = "RRIP <more garbage here>";
+static char SUSPsrc[135] = "RRIP <more garbage here>";
+
+static ulong
+CputsuspCE(Cdimg *cd, ulong offset)
+{
+	ulong o, x;
+
+	chat("writing SUSP CE record pointing to %ld, %ld\n", offset/Blocksize, offset%Blocksize);
+	o = Cwoffset(cd);
+	Cputc(cd, 'C');
+	Cputc(cd, 'E');
+	Cputc(cd, 28);
+	Cputc(cd, 1);
+	Cputn(cd, offset/Blocksize, 4);
+	Cputn(cd, offset%Blocksize, 4);
+	x = Cwoffset(cd);
+	Cputn(cd, 0, 4);
+	assert(Cwoffset(cd) == o+28);
+
+	return x;
+}
+
+static int
+CputsuspER(Cdimg *cd, int dowrite)
+{
+	assert(cd != nil);
+
+	if(dowrite) {
+		chat("writing SUSP ER record\n");
+		Cputc(cd, 'E');           /* ER field marker */
+		Cputc(cd, 'R');
+		Cputc(cd, 26);            /* Length          */
+		Cputc(cd, 1);             /* Version         */
+		Cputc(cd, 10);            /* LEN_ID          */
+		Cputc(cd, 4);             /* LEN_DESC        */
+		Cputc(cd, 4);             /* LEN_SRC         */
+		Cputc(cd, 1);             /* EXT_VER         */
+		Cputs(cd, SUSPrrip, 10);  /* EXT_ID          */
+		Cputs(cd, SUSPdesc, 4);   /* EXT_DESC        */
+		Cputs(cd, SUSPsrc, 4);    /* EXT_SRC         */
+	}
+	return 8+10+4+4;
+}
+
+static int
+CputsuspRR(Cdimg *cd, int what, int dowrite)
+{
+	assert(cd != nil);
+
+	if(dowrite) {
+		Cputc(cd, 'R');           /* RR field marker */
+		Cputc(cd, 'R');
+		Cputc(cd, 5);             /* Length          */
+		Cputc(cd, 1);		  /* Version number  */
+		Cputc(cd, what);          /* Flags           */
+	}
+	return 5;
+}
+
+static int
+CputsuspSP(Cdimg *cd, int dowrite)
+{
+	assert(cd!=0);
+
+	if(dowrite) { 
+chat("writing SUSP SP record\n");
+		Cputc(cd, 'S');           /* SP field marker */
+		Cputc(cd, 'P');
+		Cputc(cd, 7);             /* Length          */
+		Cputc(cd, 1);             /* Version         */
+		Cputc(cd, 0xBE);          /* Magic           */
+		Cputc(cd, 0xEF);
+		Cputc(cd, 0);
+	}
+
+	return 7;
+}
+
+#ifdef NOTUSED
+static int
+CputsuspST(Cdimg *cd, int dowrite)
+{
+	assert(cd!=0);
+
+	if(dowrite) {
+		Cputc(cd, 'S');           /* ST field marker */
+		Cputc(cd, 'T');
+		Cputc(cd, 4);             /* Length          */
+		Cputc(cd, 1);             /* Version         */	
+	}
+	return 4;
+}
+#endif
+
+static ulong
+suspdirflags(Direc *d, int dot)
+{
+	uchar flags;
+
+	USED(d);
+	flags = 0;
+	switch(dot) {
+	default:
+		assert(0);
+	case DTdot:
+	case DTrootdot:
+		flags |= NMcurrent;
+		break;
+	case DTdotdot:
+		flags |= NMparent;
+		break;
+	case DTroot:
+		flags |= NMvolroot;
+		break;
+	case DTiden:
+		break;
+	}
+	return flags;
+}
+
+static int
+Cputrripname(Cdimg *cd, char *nm, int flags, char *name, int dowrite)
+{
+	int l;
+
+	l = strlen(name);
+	if(dowrite) {
+		Cputc(cd, nm[0]);                   /* NM field marker */
+		Cputc(cd, nm[1]);
+		Cputc(cd, l+5);        /* Length          */
+		Cputc(cd, 1);                     /* Version         */
+		Cputc(cd, flags);                 /* Flags           */
+		Cputs(cd, name, l);    /* Alternate name  */
+	}
+	return 5+l;
+}
+
+static int
+CputrripSL(Cdimg *cd, int contin, int flags, char *name, int dowrite)
+{
+	int l;
+
+	l = strlen(name);
+	if(dowrite) {
+		Cputc(cd, 'S');
+		Cputc(cd, 'L');
+		Cputc(cd, l+7);
+		Cputc(cd, 1);
+		Cputc(cd, contin ? 1 : 0);
+		Cputc(cd, flags);
+		Cputc(cd, l);
+		Cputs(cd, name, l);
+	}
+	return 7+l;
+}
+
+static int
+CputrripPX(Cdimg *cd, Direc *d, int dot, int dowrite)
+{
+	assert(cd!=0);
+
+	if(dowrite) {
+		Cputc(cd, 'P');             /* PX field marker */
+		Cputc(cd, 'X');
+		Cputc(cd, 36);              /* Length          */
+		Cputc(cd, 1);               /* Version         */
+	
+		Cputn(cd, mode(d, dot), 4); /* POSIX File mode */
+		Cputn(cd, nlink(d), 4);     /* POSIX st_nlink  */
+		Cputn(cd, d?d->uidno:0, 4);  /* POSIX st_uid    */
+		Cputn(cd, d?d->gidno:0, 4);  /* POSIX st_gid    */
+	}
+
+	return 36;
+}
+
+static int
+CputrripTF(Cdimg *cd, Direc *d, int type, int dowrite)
+{
+	int i, length;
+
+	assert(cd!=0);
+	assert(!(type & TFlongform));
+
+	length = 0;
+	for(i=0; i<7; i++)
+		if (type & (1<<i))
+			length++;
+	assert(length == 4);
+
+	if(dowrite) {
+		Cputc(cd, 'T');				/* TF field marker */
+		Cputc(cd, 'F');
+		Cputc(cd, 5+7*length);		/* Length		 */
+		Cputc(cd, 1);				/* Version		 */
+		Cputc(cd, type);					/* Flags (types)	 */
+	
+		if (type & TFcreation)
+			Cputdate(cd, d?d->ctime:0);
+		if (type & TFmodify)
+			Cputdate(cd, d?d->mtime:0);
+		if (type & TFaccess)
+			Cputdate(cd, d?d->atime:0);
+		if (type & TFattributes)
+			Cputdate(cd, d?d->ctime:0);
+	
+	//	if (type & TFbackup)
+	//		Cputdate(cd, 0);
+	//	if (type & TFexpiration)
+	//		Cputdate(cd, 0);
+	//	if (type & TFeffective)
+	//		Cputdate(cd, 0);
+	}
+	return 5+7*length;
+}
+
+
+#define NONPXMODES  (DMDIR & DMAPPEND & DMEXCL & DMMOUNT)
+#define POSIXMODEMASK (0177777)
+#ifndef S_IFMT
+#define S_IFMT  (0170000)
+#endif
+#ifndef S_IFDIR
+#define S_IFDIR (0040000)
+#endif
+#ifndef S_IFREG
+#define S_IFREG (0100000)
+#endif
+#ifndef S_IFLNK
+#define S_IFLNK (0120000)
+#endif
+#undef  ISTYPE
+#define ISTYPE(mode, mask)  (((mode) & S_IFMT) == (mask))
+#ifndef S_ISDIR
+#define S_ISDIR(mode) ISTYPE(mode, S_IFDIR)
+#endif
+#ifndef S_ISREG
+#define S_ISREG(mode) ISTYPE(mode, S_IREG)
+#endif
+#ifndef S_ISLNK
+#define S_ISLNK(mode) ISTYPE(mode, S_ILNK)
+#endif
+
+
+static long
+mode(Direc *d, int dot)
+{
+	long mode;
+	
+	if (!d)
+		return 0;
+
+	if ((dot != DTroot) && (dot != DTrootdot)) {
+		mode = (d->mode & ~(NONPXMODES));
+		if (d->mode & DMDIR)
+			mode |= S_IFDIR;
+		else if (d->mode & CHLINK)
+			mode |= S_IFLNK;
+		else
+			mode |= S_IFREG;
+	} else
+		mode = S_IFDIR | (0755);
+
+	mode &= POSIXMODEMASK;
+		
+	/* Botch: not all POSIX types supported yet */
+	assert(mode & (S_IFDIR|S_IFREG));
+
+chat("writing PX record mode field %ulo with dot %d and name \"%s\"\n", mode, dot, d->name); 
+
+	return mode;		
+}
+
+static long
+nlink(Direc *d)   /* Trump up the nlink field for POSIX compliance */
+{
+	int i;
+	long n;
+
+	if (!d)
+		return 0;
+
+	n = 1;
+	if (d->mode & DMDIR)   /* One for "." and one more for ".." */
+		n++;
+
+	for(i=0; i<d->nchild; i++)
+		if (d->child[i].mode & DMDIR)
+			n++;
+
+	return n;
+}
+
blob - /dev/null
blob + a63484249dafe70702b1641d10dff2a421e48f60 (mode 644)
--- /dev/null
+++ src/cmd/9660/uid.c
@@ -0,0 +1,41 @@
+#include <u.h>
+#include <libc.h>
+
+/*
+ * /adm/users is
+ *	id:user/group:head member:other members
+ *
+ * /etc/{passwd,group}
+ *	name:x:nn:other stuff
+ */
+
+static int isnumber(char *s);
+
+sniff(Biobuf *b)
+{
+	read first line of file into p;
+
+	nf = getfields(p, f, nelem(f), ":");
+	if(nf < 4)
+		return nil;
+
+	if(isnumber(f[0]) && !isnumber(f[2]))
+		return _plan9;
+
+	if(!isnumber(f[0]) && isnumber(f[2]))
+		return _unix;
+
+	return nil;
+}
+
+
+int
+isnumber(char *s)
+{
+	char *q;
+
+	strtol(s, &q, 10);
+	return *q == '\0';
+}
+
+/* EOF */
blob - /dev/null
blob + 99332af8857fdb926d4c7e435d6a12d697a6675a (mode 644)
--- /dev/null
+++ src/cmd/9660/unix.c
@@ -0,0 +1,84 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <libsec.h>
+#include <disk.h>
+#include <ctype.h>
+
+#include "iso9660.h"
+
+#include <grp.h>
+#include <pwd.h>
+
+typedef struct Xarg Xarg;
+struct Xarg {
+	void (*enm)(char*,char*,XDir*,void*);
+	void (*warn)(char*,void*);
+	void *arg;
+};
+
+static long numericuid(char *user);
+static long numericgid(char *gp);
+
+void
+dirtoxdir(XDir *xd, Dir *d)
+{
+	//	char buf[NAMELEN+1];
+	memset(xd, 0, sizeof *xd);
+
+	xd->name = atom(d->name);
+	xd->uid = atom(d->uid);
+	xd->gid = atom(d->gid);
+	xd->uidno = numericuid(d->uid);
+	xd->gidno = numericgid(d->gid);
+	xd->mode = d->mode;
+	xd->atime = d->atime;
+	xd->mtime = d->mtime;
+	xd->ctime = 0;
+	xd->length = d->length;
+	if(xd->mode & CHLINK) {
+		xd->mode |= 0777;
+		//xd->symlink = atom(d->symlink);
+		xd->symlink = atom("symlink");		// XXX: rsc
+	}
+};
+
+void
+fdtruncate(int fd, ulong size)
+{
+	ftruncate(fd, size);
+
+	return;
+}
+
+static long
+numericuid(char *user)
+{
+	struct passwd *pass;
+	static int warned = 0;
+
+	if (! (pass = getpwnam(user))) {
+		if (!warned)
+			fprint(2, "Warning: getpwnam(3) failed for \"%s\"\n", user);
+		warned = 1;
+		return 0;
+	}
+
+	return pass->pw_uid;
+}
+
+static long
+numericgid(char *gp)
+{
+	struct group *gr;
+	static int warned = 0;
+
+	if (! (gr = getgrnam(gp))) {
+		if (!warned)
+			fprint(2, "Warning: getgrnam(3) failed for \"%s\"\n", gp);
+		warned = 1;
+		return 0;
+	}
+
+	return gr->gr_gid;
+}
blob - /dev/null
blob + 16fb21f3d1a2a14d8add8754d8e33a4608314ece (mode 644)
--- /dev/null
+++ src/cmd/9660/util.c
@@ -0,0 +1,98 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <libsec.h>
+#include <ctype.h>
+
+#include "iso9660.h"
+
+typedef struct Stringtab	Stringtab;
+struct Stringtab {
+	Stringtab *link;
+	char *str;
+};
+
+static Stringtab *stab[1024];
+
+static uint
+hash(char *s)
+{
+	uint h;
+	uchar *p;
+
+	h = 0;
+	for(p=(uchar*)s; *p; p++)
+		h = h*37 + *p;
+	return h;
+}
+
+static char*
+estrdup(char *s)
+{
+	if((s = strdup(s)) == nil)
+		sysfatal("strdup(%.10s): out of memory", s);
+	return s;
+}
+
+char*
+atom(char *str)
+{
+	uint h;
+	Stringtab *tab;
+	
+	h = hash(str) % nelem(stab);
+	for(tab=stab[h]; tab; tab=tab->link)
+		if(strcmp(str, tab->str) == 0)
+			return tab->str;
+
+	tab = emalloc(sizeof *tab);
+	tab->str = estrdup(str);
+	tab->link = stab[h];
+	stab[h] = tab;
+	return tab->str;
+}
+
+void*
+emalloc(ulong n)
+{
+	void *p;
+
+	if((p = malloc(n)) == nil)
+		sysfatal("malloc(%lud): out of memory", n);
+	memset(p, 0, n);
+	return p;
+}
+
+void*
+erealloc(void *v, ulong n)
+{
+	if((v = realloc(v, n)) == nil)
+		sysfatal("realloc(%p, %lud): out of memory", v, n);
+	return v;
+}
+
+char*
+struprcpy(char *p, char *s)
+{
+	char *op;
+
+	op = p;
+	for(; *s; s++)
+		*p++ = toupper(*s);
+	*p = '\0';
+
+	return op;
+}
+
+int
+chat(char *fmt, ...)
+{
+	va_list arg;
+
+	if(!chatty)
+		return 0;
+	va_start(arg, fmt);
+	vfprint(2, fmt, arg);
+	va_end(arg);
+	return 1;
+}
blob - /dev/null
blob + 94405317625ff4d8cbad8d229e65a4ad469218d8 (mode 644)
--- /dev/null
+++ src/cmd/9660/write.c
@@ -0,0 +1,409 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <libsec.h>
+
+#include "iso9660.h"
+
+static void
+writelittlebig4(uchar *buf, ulong x)
+{
+	buf[0] = buf[7] = x;
+	buf[1] = buf[6] = x>>8;
+	buf[2] = buf[5] = x>>16;
+	buf[3] = buf[4] = x>>24;
+}
+
+void
+rewritedot(Cdimg *cd, Direc *d)
+{
+	uchar buf[Blocksize];
+	Cdir *c;
+
+	Creadblock(cd, buf, d->block, Blocksize);
+	c = (Cdir*)buf;
+	assert(c->len != 0);
+	assert(c->namelen == 1 && c->name[0] == '\0');	/* dot */
+	writelittlebig4(c->dloc, d->block);
+	writelittlebig4(c->dlen, d->length);
+
+	Cwseek(cd, d->block*Blocksize);
+	Cwrite(cd, buf, Blocksize);
+}
+
+void
+rewritedotdot(Cdimg *cd, Direc *d, Direc *dparent)
+{
+	uchar buf[Blocksize];
+	Cdir *c;
+
+	Creadblock(cd, buf, d->block, Blocksize);
+	c = (Cdir*)buf;
+	assert(c->len != 0);
+	assert(c->namelen == 1 && c->name[0] == '\0');	/* dot */
+
+	c = (Cdir*)(buf+c->len);
+	assert(c->len != 0);
+	assert(c->namelen == 1 && c->name[0] == '\001');	/* dotdot*/
+
+	writelittlebig4(c->dloc, dparent->block);
+	writelittlebig4(c->dlen, dparent->length);
+
+	Cwseek(cd, d->block*Blocksize);
+	Cwrite(cd, buf, Blocksize);
+}
+
+/*
+ * Write each non-directory file.  We copy the file to
+ * the cd image, and then if it turns out that we've
+ * seen this stream of bits before, we push the next block
+ * pointer back.  This ensures consistency between the MD5s
+ * and the data on the CD image.  MD5 summing on one pass
+ * and copying on another would not ensure this.
+ */
+void
+writefiles(Dump *d, Cdimg *cd, Direc *direc)
+{
+	int i;
+	uchar buf[8192], digest[MD5dlen];
+	ulong length, n, start;
+	Biobuf *b;
+	DigestState *s;
+	Dumpdir *dd;
+
+	if(direc->mode & DMDIR) {
+		for(i=0; i<direc->nchild; i++)
+			writefiles(d, cd, &direc->child[i]);
+		return;
+	}
+
+	assert(direc->block == 0);
+
+	if((b = Bopen(direc->srcfile, OREAD)) == nil){
+		fprint(2, "warning: cannot open '%s': %r\n", direc->srcfile);
+		direc->block = 0;
+		direc->length = 0;
+		return;
+	}
+
+	start = cd->nextblock;
+	assert(start != 0);
+
+	Cwseek(cd, start*Blocksize);
+	
+	s = md5(nil, 0, nil, nil);
+	length = 0;
+	while((n = Bread(b, buf, sizeof buf)) > 0) {
+		md5(buf, n, nil, s);
+		Cwrite(cd, buf, n);
+		length += n;
+	}
+	md5(nil, 0, digest, s);
+	Bterm(b);
+	Cpadblock(cd);
+
+	if(length != direc->length) {
+		fprint(2, "warning: %s changed size underfoot\n", direc->srcfile);
+		direc->length = length;
+	}
+
+	if(length == 0)
+		direc->block = 0;
+	else if((dd = lookupmd5(d, digest))) {
+		assert(dd->length == length);
+		assert(dd->block != 0);
+		direc->block = dd->block;
+		cd->nextblock = start;
+	} else {
+		direc->block = start;
+		if(chatty > 1)
+			fprint(2, "lookup %.16H %lud (%s) failed\n", digest, length, direc->name);
+		insertmd5(d, atom(direc->name), digest, start, length);
+	}
+}
+
+/*
+ * Write a directory tree.  We work from the leaves, 
+ * and patch the dotdot pointers afterward.
+ */
+static void
+_writedirs(Cdimg *cd, Direc *d, int (*put)(Cdimg*, Direc*, int, int, int), int level)
+{
+	int i, l, ll;
+	ulong start, next;
+
+	if((d->mode & DMDIR) == 0)
+		return;
+
+	if(chatty)
+		fprint(2, "%*s%s\n", 4*level, "", d->name);
+
+	for(i=0; i<d->nchild; i++)
+		_writedirs(cd, &d->child[i], put, level+1);
+
+	l = 0;
+	l += put(cd, d, (level == 0) ? DTrootdot : DTdot, 0, l);
+	l += put(cd, nil, DTdotdot, 0, l);
+	for(i=0; i<d->nchild; i++)
+		l += put(cd, &d->child[i], DTiden, 0, l);
+
+	start = cd->nextblock;
+	cd->nextblock += (l+Blocksize-1)/Blocksize;
+	next = cd->nextblock;
+
+	Cwseek(cd, start*Blocksize);
+	ll = 0;
+	ll += put(cd, d, (level == 0) ? DTrootdot : DTdot, 1, ll);
+	ll += put(cd, nil, DTdotdot, 1, ll);
+	for(i=0; i<d->nchild; i++)
+		ll += put(cd, &d->child[i], DTiden, 1, ll);
+	assert(ll == l);
+	Cpadblock(cd);
+	assert(Cwoffset(cd) == next*Blocksize);
+
+	d->block = start;
+	d->length = (next - start) * Blocksize;
+	rewritedot(cd, d);
+	rewritedotdot(cd, d, d);
+
+	for(i=0; i<d->nchild; i++)
+		if(d->child[i].mode & DMDIR)
+			rewritedotdot(cd, &d->child[i], d);
+}
+
+void
+writedirs(Cdimg *cd, Direc *d, int (*put)(Cdimg*, Direc*, int, int, int))
+{
+	/*
+	 * If we're writing a mk9660 image, then the root really
+	 * is the root, so start at level 0.  If we're writing a dump image,
+	 * then the "root" is really going to be two levels down once
+	 * we patch in the dump hierarchy above it, so start at level non-zero.
+	 */
+	if(chatty)
+		fprint(2, ">>> writedirs\n");
+	_writedirs(cd, d, put, mk9660 ? 0 : 1);
+}
+
+
+/*
+ * Write the dump tree.  This is like writedirs but once we get to
+ * the roots of the individual days we just patch the parent dotdot blocks.
+ */
+static void
+_writedumpdirs(Cdimg *cd, Direc *d, int (*put)(Cdimg*, Direc*, int, int, int), int level)
+{
+	int i;
+	ulong start;
+
+	switch(level) {
+	case 0:
+		/* write root, list of years, also conform.map */
+		for(i=0; i<d->nchild; i++)
+			if(d->child[i].mode & DMDIR)
+				_writedumpdirs(cd, &d->child[i], put, level+1);
+		chat("write dump root dir at %lud\n", cd->nextblock);
+		goto Writedir;
+
+	case 1:	/* write year, list of days */
+		for(i=0; i<d->nchild; i++)
+			_writedumpdirs(cd, &d->child[i], put, level+1);
+		chat("write dump %s dir at %lud\n", d->name, cd->nextblock);
+		goto Writedir;
+
+	Writedir:
+		start = cd->nextblock;
+		Cwseek(cd, start*Blocksize);
+
+		put(cd, d, (level == 0) ? DTrootdot : DTdot, 1, Cwoffset(cd));
+		put(cd, nil, DTdotdot, 1, Cwoffset(cd));
+		for(i=0; i<d->nchild; i++)
+			put(cd, &d->child[i], DTiden, 1, Cwoffset(cd));
+		Cpadblock(cd);
+
+		d->block = start;
+		d->length = (cd->nextblock - start) * Blocksize;
+
+		rewritedot(cd, d);
+		rewritedotdot(cd, d, d);
+
+		for(i=0; i<d->nchild; i++)
+			if(d->child[i].mode & DMDIR)
+				rewritedotdot(cd, &d->child[i], d);
+		break;
+
+	case 2:	/* write day: already written, do nothing */
+		break;
+
+	default:
+		assert(0);
+	}
+}
+
+void
+writedumpdirs(Cdimg *cd, Direc *d, int (*put)(Cdimg*, Direc*, int, int, int))
+{
+	_writedumpdirs(cd, d, put, 0);
+}
+
+static int
+Cputplan9(Cdimg *cd, Direc *d, int dot, int dowrite)
+{
+	int l, n;
+
+	if(dot != DTiden)
+		return 0;
+
+	l = 0;
+	if(d->flags & Dbadname) {
+		n = strlen(d->name);
+		l += 1+n;
+		if(dowrite) {
+			Cputc(cd, n);
+			Cputs(cd, d->name, n);
+		}
+	} else {
+		l++;
+		if(dowrite)
+			Cputc(cd, 0);
+	}
+
+	n = strlen(d->uid);
+	l += 1+n;
+	if(dowrite) {
+		Cputc(cd, n);
+		Cputs(cd, d->uid, n);
+	}
+
+	n = strlen(d->gid);
+	l += 1+n;
+	if(dowrite) {
+		Cputc(cd, n);
+		Cputs(cd, d->gid, n);
+	}
+
+	if(l & 1) {
+		l++;
+		if(dowrite)
+			Cputc(cd, 0);
+	}
+	l += 8;
+	if(dowrite)
+		Cputn(cd, d->mode, 4);
+
+	return l;
+}
+
+/*
+ * Write a directory entry.
+ */
+static int
+genputdir(Cdimg *cd, Direc *d, int dot, int joliet, int dowrite, int offset)
+{
+	int f, n, l, lp;
+	long o;
+
+	f = 0;
+	if(dot != DTiden || (d->mode & DMDIR))
+		f |= 2;
+
+	n = 1;
+	if(dot == DTiden) {
+		if(joliet)
+			n = 2*utflen(d->confname);
+		else
+			n = strlen(d->confname);
+	}
+
+	l = 33+n;
+	if(l & 1)
+		l++;
+	assert(l <= 255);
+
+	if(joliet == 0) {
+		if(cd->flags & CDplan9)
+			l += Cputplan9(cd, d, dot, 0);
+		else if(cd->flags & CDrockridge)
+			l += Cputsysuse(cd, d, dot, 0, l);
+		assert(l <= 255);
+	}
+
+	if(dowrite == 0) {
+		if(Blocksize - offset%Blocksize < l)
+			l += Blocksize - offset%Blocksize;
+		return l;
+	}
+
+	assert(offset%Blocksize == Cwoffset(cd)%Blocksize);
+
+	o = Cwoffset(cd);
+	lp = 0;
+	if(Blocksize - Cwoffset(cd)%Blocksize < l) {
+		lp = Blocksize - Cwoffset(cd)%Blocksize;
+		Cpadblock(cd);
+	}
+
+	Cputc(cd, l);			/* length of directory record */
+	Cputc(cd, 0);			/* extended attribute record length */
+	if(d) {
+		if((d->mode & DMDIR) == 0)
+			assert(d->length == 0 || d->block >= 18);
+
+		Cputn(cd, d->block, 4);		/* location of extent */
+		Cputn(cd, d->length, 4);		/* data length */
+	} else {
+		Cputn(cd, 0, 4);
+		Cputn(cd, 0, 4);
+	}
+	Cputdate(cd, d ? d->mtime : now);		/* recorded date */
+	Cputc(cd, f);			/* file flags */
+	Cputc(cd, 0);			/* file unit size */
+	Cputc(cd, 0);			/* interleave gap size */
+	Cputn(cd, 1, 2);	       	/* volume sequence number */
+	Cputc(cd, n);			/* length of file identifier */
+
+	if(dot == DTiden) {		/* identifier */
+		if(joliet)
+			Cputrscvt(cd, d->confname, n);
+		else
+			Cputs(cd, d->confname, n);
+	}else
+	if(dot == DTdotdot)
+		Cputc(cd, 1);
+	else
+		Cputc(cd, 0);
+
+	if(Cwoffset(cd) & 1)			/* pad */
+		Cputc(cd, 0);
+
+	if(joliet == 0) {
+		if(cd->flags & CDplan9)
+			Cputplan9(cd, d, dot, 1);
+		else if(cd->flags & CDrockridge)
+			Cputsysuse(cd, d, dot, 1, Cwoffset(cd)-(o+lp));
+	}
+
+	assert(o+lp+l == Cwoffset(cd));
+	return lp+l;
+}
+
+int
+Cputisodir(Cdimg *cd, Direc *d, int dot, int dowrite, int offset)
+{
+	return genputdir(cd, d, dot, 0, dowrite, offset);
+}
+
+int
+Cputjolietdir(Cdimg *cd, Direc *d, int dot, int dowrite, int offset)
+{
+	return genputdir(cd, d, dot, 1, dowrite, offset);
+}
+
+void
+Cputendvd(Cdimg *cd)
+{
+	Cputc(cd, 255);				/* volume descriptor set terminator */
+	Cputs(cd, "CD001", 5);			/* standard identifier */
+	Cputc(cd, 1);				/* volume descriptor version */
+	Cpadblock(cd);
+}