|  | #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((uchar)*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; | 
|  | } |