aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/9660
diff options
context:
space:
mode:
authorwkj <devnull@localhost>2004-06-17 01:47:21 +0000
committerwkj <devnull@localhost>2004-06-17 01:47:21 +0000
commit7285a491c1ce1e630a0751b1011fd33e6b17234b (patch)
treeb2b2e24e333fa4660325a35f6c0f1d333e50e797 /src/cmd/9660
parente1dddc053287874e82e2b67f95ccee7d7bc63e22 (diff)
downloadplan9port-7285a491c1ce1e630a0751b1011fd33e6b17234b.tar.gz
plan9port-7285a491c1ce1e630a0751b1011fd33e6b17234b.tar.bz2
plan9port-7285a491c1ce1e630a0751b1011fd33e6b17234b.zip
Dump9660 (and mk9660). Until we either do something
intelligent with symlinks or put in a switch for things like dump9660, this is of rather limited utility under Unix.
Diffstat (limited to 'src/cmd/9660')
-rw-r--r--src/cmd/9660/boot.c189
-rw-r--r--src/cmd/9660/cdrdwr.c632
-rw-r--r--src/cmd/9660/conform.c141
-rw-r--r--src/cmd/9660/direc.c222
-rw-r--r--src/cmd/9660/dump.c511
-rw-r--r--src/cmd/9660/dump9660.c402
-rw-r--r--src/cmd/9660/ichar.c206
-rw-r--r--src/cmd/9660/iso9660.h424
-rw-r--r--src/cmd/9660/jchar.c138
-rwxr-xr-xsrc/cmd/9660/mk9660.rc5
-rwxr-xr-xsrc/cmd/9660/mk9660.sh5
-rw-r--r--src/cmd/9660/mkfile34
-rw-r--r--src/cmd/9660/path.c155
-rw-r--r--src/cmd/9660/plan9.c27
-rw-r--r--src/cmd/9660/rune.c39
-rw-r--r--src/cmd/9660/sysuse.c613
-rw-r--r--src/cmd/9660/uid.c41
-rw-r--r--src/cmd/9660/unix.c84
-rw-r--r--src/cmd/9660/util.c98
-rw-r--r--src/cmd/9660/write.c409
20 files changed, 4375 insertions, 0 deletions
diff --git a/src/cmd/9660/boot.c b/src/cmd/9660/boot.c
new file mode 100644
index 00000000..9cae9582
--- /dev/null
+++ b/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;
+}
diff --git a/src/cmd/9660/cdrdwr.c b/src/cmd/9660/cdrdwr.c
new file mode 100644
index 00000000..492e0289
--- /dev/null
+++ b/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);
+}
+
diff --git a/src/cmd/9660/conform.c b/src/cmd/9660/conform.c
new file mode 100644
index 00000000..530b4d56
--- /dev/null
+++ b/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);
+}
diff --git a/src/cmd/9660/direc.c b/src/cmd/9660/direc.c
new file mode 100644
index 00000000..8185f680
--- /dev/null
+++ b/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);
+}
+
diff --git a/src/cmd/9660/dump.c b/src/cmd/9660/dump.c
new file mode 100644
index 00000000..b77e9eb9
--- /dev/null
+++ b/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;
+}
diff --git a/src/cmd/9660/dump9660.c b/src/cmd/9660/dump9660.c
new file mode 100644
index 00000000..320e56d3
--- /dev/null
+++ b/src/cmd/9660/dump9660.c
@@ -0,0 +1,402 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <disk.h>
+#include <libsec.h>
+#include "iso9660.h"
+
+ulong now;
+int chatty;
+int doabort;
+int docolon;
+int mk9660;
+Conform *map;
+
+static void addprotofile(char *new, char *old, Dir *d, void *a);
+void usage(void);
+
+char *argv0;
+
+void
+usage(void)
+{
+ if(mk9660)
+ fprint(2, "usage: disk/mk9660 [-D:] [-9cjr] [-b bootfile] [-p proto] [-s src] cdimage\n");
+ else
+ fprint(2, "usage: disk/dump9660 [-D:] [-9cjr] [-m maxsize] [-n now] [-p proto] [-s src] cdimage\n");
+ exits("usage");
+}
+
+int
+main(int argc, char **argv)
+{
+ int fix;
+ char buf[256], *dumpname, *proto, *s, *src, *status;
+ ulong block, length, newnull, cblock, clength, maxsize;
+ Cdimg *cd;
+ Cdinfo info;
+ XDir dir;
+ Direc *iconform, idumproot, iroot, *jconform, jdumproot, jroot, *r;
+ Dump *dump;
+
+ fix = 0;
+ status = nil;
+ memset(&info, 0, sizeof info);
+ proto = "/sys/lib/sysconfig/proto/allproto";
+ src = "./";
+
+ info.volumename = atom("9CD");
+ info.volumeset = atom("9VolumeSet");
+ info.publisher = atom("9Publisher");
+ info.preparer = atom("dump9660");
+ info.application = atom("dump9660");
+ info.flags = CDdump;
+ maxsize = 0;
+ mk9660 = 0;
+ fmtinstall('H', encodefmt);
+
+ ARGBEGIN{
+ case 'D':
+ chatty++;
+ break;
+ case 'M':
+ mk9660 = 1;
+ argv0 = "disk/mk9660";
+ info.flags &= ~CDdump;
+ break;
+ case '9':
+ info.flags |= CDplan9;
+ break;
+ case ':':
+ docolon = 1;
+ break;
+ case 'a':
+ doabort = 1;
+ break;
+ case 'b':
+ if(!mk9660)
+ usage();
+ info.flags |= CDbootable;
+ info.bootimage = EARGF(usage());
+ break;
+ case 'c':
+ info.flags |= CDconform;
+ break;
+ case 'f':
+ fix = 1;
+ break;
+ case 'j':
+ info.flags |= CDjoliet;
+ break;
+ case 'n':
+ now = atoi(EARGF(usage()));
+ break;
+ case 'm':
+ maxsize = strtoul(EARGF(usage()), 0, 0);
+ break;
+ case 'p':
+ proto = EARGF(usage());
+ break;
+ case 'r':
+ info.flags |= CDrockridge;
+ break;
+ case 's':
+ src = EARGF(usage());
+ break;
+ case 'v':
+ info.volumename = atom(EARGF(usage()));
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ if(mk9660 && (fix || now || maxsize))
+ usage();
+
+ if(argc != 1)
+ usage();
+
+ if(now == 0)
+ now = (ulong)time(0);
+ if(mk9660){
+ if((cd = createcd(argv[0], info)) == nil)
+ sysfatal("cannot create '%s': %r", argv[0]);
+ }else{
+ if((cd = opencd(argv[0], info)) == nil)
+ sysfatal("cannot open '%s': %r", argv[0]);
+ if(!(cd->flags & CDdump))
+ sysfatal("not a dump cd");
+ }
+
+ /* create ISO9660/Plan 9 tree in memory */
+ memset(&dir, 0, sizeof dir);
+ dir.name = atom("");
+ dir.uid = atom("sys");
+ dir.gid = atom("sys");
+ dir.uidno = 0;
+ dir.gidno = 0;
+ dir.mode = DMDIR | 0755;
+ dir.mtime = now;
+ dir.atime = now;
+ dir.ctime = now;
+
+ mkdirec(&iroot, &dir);
+ iroot.srcfile = src;
+
+ /*
+ * Read new files into memory
+ */
+ if(rdproto(proto, src, addprotofile, nil, &iroot) < 0)
+ sysfatal("rdproto: %r");
+
+ if(mk9660){
+ dump = emalloc(sizeof *dump);
+ dumpname = nil;
+ }else{
+ /*
+ * Read current dump tree and _conform.map.
+ */
+ idumproot = readdumpdirs(cd, &dir, isostring);
+ readdumpconform(cd);
+ if(cd->flags & CDjoliet)
+ jdumproot = readdumpdirs(cd, &dir, jolietstring);
+
+ if(fix){
+ dumpname = nil;
+ cd->nextblock = cd->nulldump+1;
+ cd->nulldump = 0;
+ Cwseek(cd, cd->nextblock*Blocksize);
+ goto Dofix;
+ }
+
+ dumpname = adddumpdir(&idumproot, now, &dir);
+ /* note that we assume all names are conforming and thus sorted */
+ if(cd->flags & CDjoliet) {
+ s = adddumpdir(&jdumproot, now, &dir);
+ if(s != dumpname)
+ sysfatal("dumpnames don't match %s %s\n", dumpname, s);
+ }
+ dump = dumpcd(cd, &idumproot);
+ cd->nextblock = cd->nulldump+1;
+ }
+
+ /*
+ * Write new files, starting where the dump tree was.
+ * Must be done before creation of the Joliet tree so that
+ * blocks and lengths are correct.
+ */
+ Cwseek(cd, cd->nextblock*Blocksize);
+ writefiles(dump, cd, &iroot);
+
+ if(cd->bootimage){
+ findbootimage(cd, &iroot);
+ Cupdatebootcat(cd);
+ }
+
+ /* create Joliet tree */
+ if(cd->flags & CDjoliet)
+ copydirec(&jroot, &iroot);
+
+ if(info.flags & CDconform) {
+ checknames(&iroot, isbadiso9660);
+ convertnames(&iroot, struprcpy);
+ } else
+ convertnames(&iroot, (void *) strcpy);
+
+// isoabstract = findconform(&iroot, abstract);
+// isobiblio = findconform(&iroot, biblio);
+// isonotice = findconform(&iroot, notice);
+
+ dsort(&iroot, isocmp);
+
+ if(cd->flags & CDjoliet) {
+ // jabstract = findconform(&jroot, abstract);
+ // jbiblio = findconform(&jroot, biblio);
+ // jnotice = findconform(&jroot, notice);
+
+ checknames(&jroot, isbadjoliet);
+ convertnames(&jroot, (void *) strcpy);
+ dsort(&jroot, jolietcmp);
+ }
+
+ /*
+ * Write directories.
+ */
+ writedirs(cd, &iroot, Cputisodir);
+ if(cd->flags & CDjoliet)
+ writedirs(cd, &jroot, Cputjolietdir);
+
+ if(mk9660){
+ cblock = 0;
+ clength = 0;
+ newnull = 0;
+ }else{
+ /*
+ * Write incremental _conform.map block.
+ */
+ wrconform(cd, cd->nconform, &cblock, &clength);
+
+ /* jump here if we're just fixing up the cd */
+Dofix:
+ /*
+ * Write null dump header block; everything after this will be
+ * overwritten at the next dump. Because of this, it needs to be
+ * reconstructable. We reconstruct the _conform.map and dump trees
+ * from the header blocks in dump.c, and we reconstruct the path
+ * tables by walking the cd.
+ */
+ newnull = Cputdumpblock(cd);
+ }
+
+ /*
+ * Write _conform.map.
+ */
+ dir.mode = 0444;
+ if(cd->flags & (CDconform|CDjoliet)) {
+ if(!mk9660 && cd->nconform == 0){
+ block = cblock;
+ length = clength;
+ }else
+ wrconform(cd, 0, &block, &length);
+
+ if(mk9660)
+{
+ idumproot = iroot;
+ jdumproot = jroot;
+ }
+ if(length) {
+ /* The ISO9660 name will get turned into uppercase when written. */
+ if((iconform = walkdirec(&idumproot, "_conform.map")) == nil)
+ iconform = adddirec(&idumproot, "_conform.map", &dir);
+ jconform = nil;
+ if(cd->flags & CDjoliet) {
+ if((jconform = walkdirec(&jdumproot, "_conform.map")) == nil)
+ jconform = adddirec(&jdumproot, "_conform.map", &dir);
+ }
+ iconform->block = block;
+ iconform->length = length;
+ if(cd->flags & CDjoliet) {
+ jconform->block = block;
+ jconform->length = length;
+ }
+ }
+ if(mk9660) {
+ iroot = idumproot;
+ jroot = jdumproot;
+ }
+ }
+
+ if(mk9660){
+ /*
+ * Patch in root directories.
+ */
+ setroot(cd, cd->iso9660pvd, iroot.block, iroot.length);
+ setvolsize(cd, cd->iso9660pvd, cd->nextblock*Blocksize);
+ if(cd->flags & CDjoliet){
+ setroot(cd, cd->jolietsvd, jroot.block, jroot.length);
+ setvolsize(cd, cd->jolietsvd, cd->nextblock*Blocksize);
+ }
+ }else{
+ /*
+ * Write dump tree at end. We assume the name characters
+ * are all conforming, so everything is already sorted properly.
+ */
+ convertnames(&idumproot, (info.flags & CDconform) ? (void *) struprcpy : (void *) strcpy);
+ if(cd->nulldump) {
+ r = walkdirec(&idumproot, dumpname);
+ assert(r != nil);
+ copybutname(r, &iroot);
+ }
+ if(cd->flags & CDjoliet) {
+ convertnames(&jdumproot, (void *) strcpy);
+ if(cd->nulldump) {
+ r = walkdirec(&jdumproot, dumpname);
+ assert(r != nil);
+ copybutname(r, &jroot);
+ }
+ }
+
+ writedumpdirs(cd, &idumproot, Cputisodir);
+ if(cd->flags & CDjoliet)
+ writedumpdirs(cd, &jdumproot, Cputjolietdir);
+
+ /*
+ * Patch in new root directory entry.
+ */
+ setroot(cd, cd->iso9660pvd, idumproot.block, idumproot.length);
+ setvolsize(cd, cd->iso9660pvd, cd->nextblock*Blocksize);
+ if(cd->flags & CDjoliet){
+ setroot(cd, cd->jolietsvd, jdumproot.block, jdumproot.length);
+ setvolsize(cd, cd->jolietsvd, cd->nextblock*Blocksize);
+ }
+ }
+ writepathtables(cd);
+
+ if(!mk9660){
+ /*
+ * If we've gotten too big, truncate back to what we started with,
+ * fix up the cd, and exit with a non-zero status.
+ */
+ Cwflush(cd);
+ if(cd->nulldump && maxsize && Cwoffset(cd) > maxsize){
+ fprint(2, "too big; writing old tree back\n");
+ status = "cd too big; aborted";
+
+ rmdumpdir(&idumproot, dumpname);
+ rmdumpdir(&jdumproot, dumpname);
+
+ cd->nextblock = cd->nulldump+1;
+ cd->nulldump = 0;
+ Cwseek(cd, cd->nextblock*Blocksize);
+ goto Dofix;
+ }
+
+ /*
+ * Write old null header block; this commits all our changes.
+ */
+ if(cd->nulldump){
+ Cwseek(cd, cd->nulldump*Blocksize);
+ sprint(buf, "plan 9 dump cd\n");
+ sprint(buf+strlen(buf), "%s %lud %lud %lud %lud %lud %lud",
+ dumpname, now, newnull, cblock, clength, iroot.block,
+ iroot.length);
+ if(cd->flags & CDjoliet)
+ sprint(buf+strlen(buf), " %lud %lud",
+ jroot.block, jroot.length);
+ strcat(buf, "\n");
+ Cwrite(cd, buf, strlen(buf));
+ Cpadblock(cd);
+ Cwflush(cd);
+ }
+ }
+ fdtruncate(cd->fd, cd->nextblock*Blocksize);
+ exits(status);
+ return 0;
+}
+
+static void
+addprotofile(char *new, char *old, Dir *d, void *a)
+{
+ char *name, *p;
+ Direc *direc;
+ XDir xd;
+
+ dirtoxdir(&xd, d);
+ name = nil;
+ if(docolon && strchr(new, ':')) {
+ name = emalloc(strlen(new)+1);
+ strcpy(name, new);
+ while((p=strchr(name, ':')))
+ *p=' ';
+ new = name;
+ }
+ if((direc = adddirec((Direc*)a, new, &xd))) {
+ direc->srcfile = atom(old);
+
+ // BUG: abstract, biblio, notice
+ }
+ if(name)
+ free(name);
+
+}
+
diff --git a/src/cmd/9660/ichar.c b/src/cmd/9660/ichar.c
new file mode 100644
index 00000000..35b0c056
--- /dev/null
+++ b/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);
+}
diff --git a/src/cmd/9660/iso9660.h b/src/cmd/9660/iso9660.h
new file mode 100644
index 00000000..5eefae88
--- /dev/null
+++ b/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;
diff --git a/src/cmd/9660/jchar.c b/src/cmd/9660/jchar.c
new file mode 100644
index 00000000..c49da635
--- /dev/null
+++ b/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);
+}
+
diff --git a/src/cmd/9660/mk9660.rc b/src/cmd/9660/mk9660.rc
new file mode 100755
index 00000000..1824f8e7
--- /dev/null
+++ b/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 $*
diff --git a/src/cmd/9660/mk9660.sh b/src/cmd/9660/mk9660.sh
new file mode 100755
index 00000000..a127b829
--- /dev/null
+++ b/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 $*
diff --git a/src/cmd/9660/mkfile b/src/cmd/9660/mkfile
new file mode 100644
index 00000000..06d996f0
--- /dev/null
+++ b/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
diff --git a/src/cmd/9660/path.c b/src/cmd/9660/path.c
new file mode 100644
index 00000000..f2757dba
--- /dev/null
+++ b/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);
+}
diff --git a/src/cmd/9660/plan9.c b/src/cmd/9660/plan9.c
new file mode 100644
index 00000000..25f04e92
--- /dev/null
+++ b/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);
+}
diff --git a/src/cmd/9660/rune.c b/src/cmd/9660/rune.c
new file mode 100644
index 00000000..3a436f4a
--- /dev/null
+++ b/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;
+}
+
diff --git a/src/cmd/9660/sysuse.c b/src/cmd/9660/sysuse.c
new file mode 100644
index 00000000..dc326c6a
--- /dev/null
+++ b/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;
+}
+
diff --git a/src/cmd/9660/uid.c b/src/cmd/9660/uid.c
new file mode 100644
index 00000000..a6348424
--- /dev/null
+++ b/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 */
diff --git a/src/cmd/9660/unix.c b/src/cmd/9660/unix.c
new file mode 100644
index 00000000..99332af8
--- /dev/null
+++ b/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;
+}
diff --git a/src/cmd/9660/util.c b/src/cmd/9660/util.c
new file mode 100644
index 00000000..16fb21f3
--- /dev/null
+++ b/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;
+}
diff --git a/src/cmd/9660/write.c b/src/cmd/9660/write.c
new file mode 100644
index 00000000..94405317
--- /dev/null
+++ b/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);
+}