tDump9660 (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. - plan9port - [fork] Plan 9 from user space
 (HTM) git clone git://src.adamsgaard.dk/plan9port
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit 7285a491c1ce1e630a0751b1011fd33e6b17234b
 (DIR) parent e1dddc053287874e82e2b67f95ccee7d7bc63e22
 (HTM) Author: wkj <devnull@localhost>
       Date:   Thu, 17 Jun 2004 01:47:21 +0000
       
       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:
         A src/cmd/9660/boot.c                 |     189 +++++++++++++++++++++++++++++++
         A src/cmd/9660/cdrdwr.c               |     632 +++++++++++++++++++++++++++++++
         A src/cmd/9660/conform.c              |     141 +++++++++++++++++++++++++++++++
         A src/cmd/9660/direc.c                |     222 ++++++++++++++++++++++++++++++
         A src/cmd/9660/dump.c                 |     511 +++++++++++++++++++++++++++++++
         A src/cmd/9660/dump9660.c             |     402 ++++++++++++++++++++++++++++++
         A src/cmd/9660/ichar.c                |     206 +++++++++++++++++++++++++++++++
         A src/cmd/9660/iso9660.h              |     424 ++++++++++++++++++++++++++++++
         A src/cmd/9660/jchar.c                |     138 ++++++++++++++++++++++++++++++
         A src/cmd/9660/mk9660.rc              |       5 +++++
         A src/cmd/9660/mk9660.sh              |       5 +++++
         A src/cmd/9660/mkfile                 |      34 +++++++++++++++++++++++++++++++
         A src/cmd/9660/path.c                 |     155 +++++++++++++++++++++++++++++++
         A src/cmd/9660/plan9.c                |      27 +++++++++++++++++++++++++++
         A src/cmd/9660/rune.c                 |      39 +++++++++++++++++++++++++++++++
         A src/cmd/9660/sysuse.c               |     613 +++++++++++++++++++++++++++++++
         A src/cmd/9660/uid.c                  |      41 +++++++++++++++++++++++++++++++
         A src/cmd/9660/unix.c                 |      84 +++++++++++++++++++++++++++++++
         A src/cmd/9660/util.c                 |      98 +++++++++++++++++++++++++++++++
         A src/cmd/9660/write.c                |     409 +++++++++++++++++++++++++++++++
       
       20 files changed, 4375 insertions(+), 0 deletions(-)
       ---
 (DIR) diff --git a/src/cmd/9660/boot.c b/src/cmd/9660/boot.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/cmd/9660/cdrdwr.c b/src/cmd/9660/cdrdwr.c
       t@@ -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);
       +}
       +
 (DIR) diff --git a/src/cmd/9660/conform.c b/src/cmd/9660/conform.c
       t@@ -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);
       +}
 (DIR) diff --git a/src/cmd/9660/direc.c b/src/cmd/9660/direc.c
       t@@ -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);
       +}
       +
 (DIR) diff --git a/src/cmd/9660/dump.c b/src/cmd/9660/dump.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/cmd/9660/dump9660.c b/src/cmd/9660/dump9660.c
       t@@ -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);
       +
       +}
       +
 (DIR) diff --git a/src/cmd/9660/ichar.c b/src/cmd/9660/ichar.c
       t@@ -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);
       +}
 (DIR) diff --git a/src/cmd/9660/iso9660.h b/src/cmd/9660/iso9660.h
       t@@ -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;
 (DIR) diff --git a/src/cmd/9660/jchar.c b/src/cmd/9660/jchar.c
       t@@ -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);
       +}
       +
 (DIR) diff --git a/src/cmd/9660/mk9660.rc b/src/cmd/9660/mk9660.rc
       t@@ -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 $*
 (DIR) diff --git a/src/cmd/9660/mk9660.sh b/src/cmd/9660/mk9660.sh
       t@@ -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 $*
 (DIR) diff --git a/src/cmd/9660/mkfile b/src/cmd/9660/mkfile
       t@@ -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
 (DIR) diff --git a/src/cmd/9660/path.c b/src/cmd/9660/path.c
       t@@ -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);
       +}
 (DIR) diff --git a/src/cmd/9660/plan9.c b/src/cmd/9660/plan9.c
       t@@ -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);
       +}
 (DIR) diff --git a/src/cmd/9660/rune.c b/src/cmd/9660/rune.c
       t@@ -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;
       +}
       +
 (DIR) diff --git a/src/cmd/9660/sysuse.c b/src/cmd/9660/sysuse.c
       t@@ -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;
       +}
       +
 (DIR) diff --git a/src/cmd/9660/uid.c b/src/cmd/9660/uid.c
       t@@ -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 */
 (DIR) diff --git a/src/cmd/9660/unix.c b/src/cmd/9660/unix.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/cmd/9660/util.c b/src/cmd/9660/util.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/cmd/9660/write.c b/src/cmd/9660/write.c
       t@@ -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);
       +}