Subversion Repositories planix.SVN

Rev

Blame | Last modification | View Log | RSS feed

#include <u.h>
#include <libc.h>
#include <bio.h>
#include <auth.h>
#include <fcall.h>
#include "iotrack.h"
#include "dat.h"
#include "fns.h"

static uchar    isdos[256];

int
isdosfs(uchar *buf)
{
        /*
         * When dynamic disc managers move the disc partition,
         * they make it start with 0xE9.
         */
        if(buf[0] == 0xE9)
                return 1;

        /*
         * Check if the jump displacement (magic[1]) is too short for a FAT.
         *
         * check now omitted due to digital cameras that use a 0 jump.
         * the ecma-107 standard says this is okay and that interoperable fat
         * implementations shouldn't assume this:
         * http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-107.pdf,
         * page 11.
         */
        if(buf[0] == 0xEB && buf[2] == 0x90 /* && buf[1] >= 0x30 */)
                return 1;
        if(chatty)
                fprint(2, "bad sig %.2ux %.2ux %.2uxn", buf[0], buf[1], buf[2]);

        return 0;
}

int
dosfs(Xfs *xf)
{
        Iosect *p, *p1;
        Dosboot *b;
        Fatinfo *fi;
        Dosboot32 *b32;
        Dosbpb *bp;
        long fisec, extflags;
        int i;

        if(!isdos['a']){
                for(i = 'a'; i <= 'z'; i++)
                        isdos[i] = 1;
                for(i = 'A'; i <= 'Z'; i++)
                        isdos[i] = 1;
                for(i = '0'; i <= '9'; i++)
                        isdos[i] = 1;
                isdos['$'] = 1;
                isdos['%'] = 1;
                isdos['''] = 1;
                isdos['-'] = 1;
                isdos['_'] = 1;
                isdos['@'] = 1;
                isdos['~'] = 1;
                isdos['`'] = 1;
                isdos['!'] = 1;
                isdos['('] = 1;
                isdos[')'] = 1;
                isdos['{'] = 1;
                isdos['}'] = 1;
                isdos['^'] = 1;
                isdos['#'] = 1;
                isdos['&'] = 1;
        }

        p = getsect(xf, 0);
        if(p == 0)
                return -1;

        b = (Dosboot*)p->iobuf;
        if(b->clustsize == 0 || isdosfs(p->iobuf) == 0){
                putsect(p);
                return -1;
        }

        bp = malloc(sizeof(Dosbpb));
        memset(bp, 0, sizeof(Dosbpb));  /* clear lock */
        xf->ptr = bp;
        xf->fmt = 1;

        bp->sectsize = GSHORT(b->sectsize);
        bp->clustsize = b->clustsize;
        bp->nresrv = GSHORT(b->nresrv);
        bp->nfats = b->nfats;
        bp->rootsize = GSHORT(b->rootsize);
        bp->volsize = GSHORT(b->volsize);
        if(bp->volsize == 0)
                bp->volsize = GLONG(b->bigvolsize);
        bp->mediadesc = b->mediadesc;
        bp->fatsize = GSHORT(b->fatsize);
        bp->fataddr = GSHORT(b->nresrv);

        bp->fatinfo = 0;

        if(bp->fatsize == 0){   /* is FAT32 */
                if(chatty)
                        bootsecdump32(2, xf, (Dosboot32*)b);
                xf->isfat32 = 1;
                b32 = (Dosboot32*)b;
                bp->fatsize = GLONG(b32->fatsize32);
                if(bp->fatsize == 0){
                        putsect(p);
                        if(chatty)
                                fprint(2, "fatsize 0\n");
                        return -1;
                }
                bp->dataaddr = bp->fataddr + bp->nfats*bp->fatsize;
                bp->rootaddr = 0;
                bp->rootstart = GLONG(b32->rootstart);

                /*
                 * disable fat mirroring?
                 */
                extflags = GSHORT(b32->extflags);
                if(extflags & 0x0080){
                        for(i = 0; i < 4; i++){
                                if(extflags & (1 << i)){
                                        bp->fataddr += i * bp->fatsize;
                                        bp->nfats = 1;
                                        break;
                                }
                        }
                }

                /*
                 * fat free list info
                 */
                bp->freeptr = FATRESRV;
                fisec = GSHORT(b32->infospec);
                if(fisec != 0 && fisec < GSHORT(b32->nresrv)){
                        p1 = getsect(xf, fisec);
                        if(p1 != nil){
                                fi = (Fatinfo*)p1->iobuf;
                                if(GLONG(fi->sig1) == FATINFOSIG1 && GLONG(fi->sig) == FATINFOSIG){
                                        bp->fatinfo = fisec;
                                        bp->freeptr = GLONG(fi->nextfree);
                                        bp->freeclusters = GLONG(fi->freeclust);
                                        chat("fat info: %ld free clusters, next free %ld\n", bp->freeclusters, bp->freeptr);
                                }
                                putsect(p1);
                        }
                }
        }else{
                if(chatty)
                        bootdump(2, b);
                bp->rootaddr = bp->fataddr + bp->nfats*bp->fatsize;
                bp->rootstart = 0;
                i = bp->rootsize*DOSDIRSIZE + bp->sectsize-1;
                i /= bp->sectsize;
                bp->dataaddr = bp->rootaddr + i;
                bp->freeptr = FATRESRV;
        }
        bp->fatclusters = FATRESRV+(bp->volsize - bp->dataaddr)/bp->clustsize;

        if(xf->isfat32)
                bp->fatbits = 32;
        else if(bp->fatclusters < 4087)
                bp->fatbits = 12;
        else
                bp->fatbits = 16;

        chat("fatbits=%d (%d clusters)...", bp->fatbits, bp->fatclusters);
        for(i=0; i<b->nfats; i++)
                chat("fat %d: %ld...", i, bp->fataddr+i*bp->fatsize);
        chat("root: %ld...", bp->rootaddr);
        chat("data: %ld...", bp->dataaddr);
        putsect(p);
        return 0;
}

/*
 * initialize f to the root directory
 * this file has no Dosdir entry,
 * so we special case it all over.
 */
void
rootfile(Xfile *f)
{
        Dosptr *dp;

        dp = f->ptr;
        memset(dp, 0, sizeof(Dosptr));
        dp->prevaddr = -1;
}

int
isroot(ulong addr)
{
        return addr == 0;
}

int
getfile(Xfile *f)
{
        Dosptr *dp;
        Iosect *p;

        dp = f->ptr;
        if(dp->p)
                panic("getfile");
        p = getsect(f->xf, dp->addr);
        if(p == nil)
                return -1;

        /*
         * we could also make up a Dosdir for the root
         */
        dp->d = nil;
        if(!isroot(dp->addr)){
                if(f->qid.path != QIDPATH(dp)){
                        chat("qid mismatch f=%#llux d=%#lux...", f->qid.path, QIDPATH(dp));
                        putsect(p);
                        errno = Enonexist;
                        return -1;
                }
                dp->d = (Dosdir *)&p->iobuf[dp->offset];
        }
        dp->p = p;
        return 0;
}

void
putfile(Xfile *f)
{
        Dosptr *dp;

        dp = f->ptr;
        if(!dp->p)
                panic("putfile");
        putsect(dp->p);
        dp->p = nil;
        dp->d = nil;
}

long
getstart(Xfs *xf, Dosdir *d)
{
        long start;

        start = GSHORT(d->start);
        if(xf->isfat32)
                start |= GSHORT(d->hstart)<<16;
        return start;
}

void
putstart(Xfs *xf, Dosdir *d, long start)
{
        PSHORT(d->start, start);
        if(xf->isfat32)
                PSHORT(d->hstart, start>>16);
}

/*
 * return the disk cluster for the iclust cluster in f
 */
long
fileclust(Xfile *f, long iclust, int cflag)
{
        Dosbpb *bp;
        Dosptr *dp;
        Dosdir *d;
        long start, clust, nskip, next;

        bp = f->xf->ptr;
        dp = f->ptr;
        d = dp->d;
        next = 0;

        /* 
         * asking for the cluster of the root directory
         * is not a well-formed question, since the root directory
         * does not begin on a cluster boundary.
         */
        if(!f->xf->isfat32 && isroot(dp->addr))
                return -1;

        if(f->xf->isfat32 && isroot(dp->addr)){
                start = bp->rootstart;
        }else{
                start = getstart(f->xf, d);
                if(start == 0){
                        if(!cflag)
                                return -1;
                        mlock(bp);
                        start = falloc(f->xf);
                        unmlock(bp);
                        if(start <= 0)
                                return -1;
                        puttime(d, 0);
                        putstart(f->xf, d, start);
                        dp->p->flags |= BMOD;
                        dp->clust = 0;
                }
        }
        if(dp->clust == 0 || iclust < dp->iclust){
                clust = start;
                nskip = iclust;
        }else{
                clust = dp->clust;
                nskip = iclust - dp->iclust;
        }
        if(chatty > 1 && nskip > 0)
                chat("clust %#lx, skip %ld...", clust, nskip);
        if(clust <= 0)
                return -1;
        if(nskip > 0){
                mlock(bp);
                while(--nskip >= 0){
                        next = getfat(f->xf, clust);
                        if(chatty > 1)
                                chat("->%#lx", next);
                        if(next > 0){
                                clust = next;
                                continue;
                        }else if(!cflag)
                                break;
                        if(d && (d->attr&DSYSTEM)){
                                next = cfalloc(f);
                                if(next < 0)
                                        break;
                                /* cfalloc will call putfat for us, since clust may change */
                        } else {
                                next = falloc(f->xf);
                                if(next < 0)
                                        break;
                                putfat(f->xf, clust, next);
                        }
                        clust = next;
                }
                unmlock(bp);
                if(next <= 0)
                        return -1;
                dp->clust = clust;
                dp->iclust = iclust;
        }
        if(chatty > 1)
                chat(" clust(%#lx)=%#lx...", iclust, clust);
        return clust;
}

/*
 * return the disk sector for the isect disk sector in f 
 */
long
fileaddr(Xfile *f, long isect, int cflag)
{
        Dosbpb *bp;
        Dosptr *dp;
        long clust;

        bp = f->xf->ptr;
        dp = f->ptr;
        if(!f->xf->isfat32 && isroot(dp->addr)){
                if(isect*bp->sectsize >= bp->rootsize*DOSDIRSIZE)
                        return -1;
                return bp->rootaddr + isect;
        }
        clust = fileclust(f, isect/bp->clustsize, cflag);
        if(clust < 0)
                return -1;

        return clust2sect(bp, clust) + isect%bp->clustsize;
}

/*
 * translate names
 */
void
fixname(char *buf)
{
        int c;
        char *p;

        p = buf;
        while(c = *p){
                if(c == ':' && trspaces)
                        *p = ' ';
                p++;
        }
}

/*
 * classify the file name as one of 
 *      Invalid - contains a bad character
 *      Short - short valid 8.3 name, no lowercase letters
 *      ShortLower - short valid 8.3 name except for lowercase letters
 *      Long - long name 
 */
int
classifyname(char *buf)
{
        char *p, *dot;
        int c, isextended, is8dot3, islower, ndot;

        p = buf;
        isextended = 0;
        islower = 0;
        dot = nil;
        ndot = 0;
        while(c = (uchar)*p){
                if(c&0x80)      /* UTF8 */
                        isextended = 1;
                else if(c == '.'){
                        dot = p;
                        ndot++;
                }else if(strchr("+,:;=[] ", c))
                        isextended = 1;
                else if(!isdos[c])
                        return Invalid;
                if('a' <= c && c <= 'z')
                        islower = 1;
                p++;
        }

        is8dot3 = (ndot==0 && p-buf <= 8) || (ndot==1 && dot-buf <= 8 && p-(dot+1) <= 3);
        
        if(!isextended && is8dot3){
                if(islower)
                        return ShortLower;
                return Short;
        }
        return Long;
}
                
/*
 * make an alias for a valid long file name
 */
void
mkalias(char *name, char *sname, int id)
{
        Rune r;
        char *s, *e, sid[10];
        int i, esuf, v;

        e = strrchr(name, '.');
        if(e == nil)
                e = strchr(name, '\0');

        s = name;
        i = 0;
        while(s < e && i < 6){
                if(isdos[(uchar)*s])
                        sname[i++] = *s++;
                else
                        s += chartorune(&r, s);
        }

        v = snprint(sid, 10, "%d", id);
        if(i + 1 + v > 8)
                i = 8 - 1 - v;
        sname[i++] = '~';
        strcpy(&sname[i], sid);
        i += v;

        sname[i++] = '.';
        esuf = i + 3;
        if(esuf > 12)
                panic("bad mkalias");
        while(*e && i < esuf){
                if(isdos[(uchar)*e])
                        sname[i++] = *e++;
                else
                        e += chartorune(&r, e);
        }
        if(sname[i-1] == '.')
                i--;
        sname[i] = '\0';
}

/*
 * check for valid plan 9 names,
 * rewrite ' ' to ':'
 */
char isfrog[256]={
        /*NUL*/ 1, 1, 1, 1, 1, 1, 1, 1,
        /*BKS*/ 1, 1, 1, 1, 1, 1, 1, 1,
        /*DLE*/ 1, 1, 1, 1, 1, 1, 1, 1,
        /*CAN*/ 1, 1, 1, 1, 1, 1, 1, 1,
/*      [' ']   1,      let's try this -rsc */
        ['/']   1,
        [0x7f]  1,
};

int
nameok(char *elem)
{
        while(*elem) {
                if(*elem == ' ' && trspaces)
                        *elem = ':';
                if(isfrog[*(uchar*)elem])
                        return 0;
                elem++;
        }
        return 1;
}

/*
 * look for a directory entry matching name
 * always searches for long names which match a short name
 */
int
searchdir(Xfile *f, char *name, Dosptr *dp, int cflag, int longtype)
{
        Xfs *xf;
        Iosect *p;
        Dosbpb *bp;
        Dosdir *d;
        char buf[261], *bname;
        int isect, addr, o, addr1, addr2, prevaddr, prevaddr1, o1, islong, have, need, sum;

        xf = f->xf;
        bp = xf->ptr;
        addr1 = -1;
        addr2 = -1;
        prevaddr1 = -1;
        o1 = 0;
        islong = 0;
        sum = -1;

        need = 1;
        if(longtype!=Short && cflag)
                need += (utflen(name) + DOSRUNE-1) / DOSRUNE;

        memset(dp, 0, sizeof(Dosptr));
        dp->prevaddr = -1;
        dp->naddr = -1;
        dp->paddr = ((Dosptr *)f->ptr)->addr;
        dp->poffset = ((Dosptr *)f->ptr)->offset;

        have = 0;
        addr = -1;
        bname = nil;
        for(isect=0;; isect++){
                prevaddr = addr;
                addr = fileaddr(f, isect, cflag);
                if(addr < 0)
                        break;
                p = getsect(xf, addr);
                if(p == 0)
                        break;
                for(o=0; o<bp->sectsize; o+=DOSDIRSIZE){
                        d = (Dosdir *)&p->iobuf[o];
                        if(d->name[0] == 0x00){
                                chat("end dir(0)...");
                                putsect(p);
                                if(!cflag)
                                        return -1;

                                /*
                                 * addr1 & o1 are the start of the dirs
                                 * addr2 is the optional second cluster used if the long name
                                 * entry does not fit within the addr1 cluster
                                 *
                                 * have tells us the number of contiguous free dirs
                                 * starting at addr1.o1; need are necessary to hold the long name.
                                 */
                                if(addr1 < 0){
                                        addr1 = addr;
                                        prevaddr1 = prevaddr;
                                        o1 = o;
                                }
                                if(addr2 < 0 && (bp->sectsize-o)/DOSDIRSIZE + have < need){
                                        addr2 = fileaddr(f, isect+1, cflag);
                                        if(addr2 < 0)
                                                goto breakout;
                                }else if(addr2 < 0)
                                        addr2 = addr;
                                if(addr2 == addr1)
                                        addr2 = -1;
                                dp->addr = addr1;
                                dp->offset = o1;
                                dp->prevaddr = prevaddr1;
                                dp->naddr = addr2;
                                return 0;
                        }
                        if(d->name[0] == DOSEMPTY){
                                if(chatty)
                                        fprint(2, "empty dir\n");

                                have++;
                                if(addr1 == -1){
                                        addr1 = addr;
                                        o1 = o;
                                        prevaddr1 = prevaddr;
                                }
                                if(addr2 == -1 && have >= need)
                                        addr2 = addr;
                                continue;
                        }
                        have = 0;
                        if(addr2 == -1)
                                addr1 = -1;

                        dirdump(d);
                        if((d->attr & 0xf) == 0xf){
                                bname = getnamesect(buf, bname, p->iobuf + o, &islong, &sum, 1);
                                continue;
                        }
                        if(d->attr & DVLABEL){
                                islong = 0;
                                continue;
                        }
                        if(islong != 1 || sum != aliassum(d) || cistrcmp(bname, name) != 0){
                                bname = buf;
                                getname(buf, d);
                        }
                        islong = 0;
                        if(cistrcmp(bname, name) != 0)
                                continue;
                        if(chatty)
                                fprint(2, "found\n");
                        if(cflag){
                                putsect(p);
                                return -1;
                        }
                        dp->addr = addr;
                        dp->prevaddr = prevaddr;
                        dp->offset = o;
                        dp->p = p;
                        dp->d = d;
                        return 0;
                }
                putsect(p);
        }
breakout:
        chat("end dir(1)...");
        return -1;
}

int
emptydir(Xfile *f)
{
        Xfs *xf = f->xf;
        Dosbpb *bp = xf->ptr;
        int isect, addr, o;
        Iosect *p;
        Dosdir *d;

        for(isect=0;; isect++){
                addr = fileaddr(f, isect, 0);
                if(addr < 0)
                        break;
                p = getsect(xf, addr);
                if(p == 0)
                        return -1;
                for(o=0; o<bp->sectsize; o+=DOSDIRSIZE){
                        d = (Dosdir *)&p->iobuf[o];
                        if(d->name[0] == 0x00){
                                putsect(p);
                                return 0;
                        }
                        if(d->name[0] == DOSEMPTY)
                                continue;
                        if(d->name[0] == '.')
                                continue;
                        if(d->attr&DVLABEL)
                                continue;
                        putsect(p);
                        return -1;
                }
                putsect(p);
        }
        return 0;
}

long
readdir(Xfile *f, void *vbuf, long offset, long count)
{
        Xfs *xf;
        Dosbpb *bp;
        Dir dir;
        int isect, addr, o, islong, sum;
        Iosect *p;
        Dosdir *d;
        long rcnt, n;
        char *name, snamebuf[8+1+3+1], namebuf[DOSNAMELEN];
        uchar *buf;

        buf = vbuf;
        rcnt = 0;
        xf = f->xf;
        bp = xf->ptr;
        if(count <= 0)
                return 0;
        islong = 0;
        sum = -1;
        name = nil;
        for(isect=0;; isect++){
                addr = fileaddr(f, isect, 0);
                if(addr < 0)
                        break;
                p = getsect(xf, addr);
                if(p == 0)
                        return -1;
                for(o=0; o<bp->sectsize; o+=DOSDIRSIZE){
                        d = (Dosdir *)&p->iobuf[o];
                        if(d->name[0] == 0x00){
                                putsect(p);
                                return rcnt;
                        }
                        if(d->name[0] == DOSEMPTY)
                                continue;
                        dirdump(d);
                        if(d->name[0] == '.'){
                                if(d->name[1] == ' ' || d->name[1] == 0)
                                        continue;
                                if(d->name[1] == '.' &&
                                  (d->name[2] == ' ' || d->name[2] == 0))
                                        continue;
                        }
                        if((d->attr & 0xf) == 0xf){
                                name = getnamesect(namebuf, name, p->iobuf+o, &islong, &sum, 1);
                                continue;
                        }
                        if(d->attr & DVLABEL){
                                islong = 0;
                                continue;
                        }
                        dir.name = snamebuf;
                        getdir(xf, &dir, d, addr, o);
                        if(islong == 1 && nameok(name) && sum == aliassum(d))
                                dir.name = name;
                        islong = 0;
                        n = convD2M(&dir, &buf[rcnt], count - rcnt);
                        name = nil;
                        if(n <= BIT16SZ){       /* no room for next entry */
                                putsect(p);
                                return rcnt;
                        }
                        rcnt += n;
                        if(offset > 0){
                                offset -= rcnt;
                                rcnt = 0;
                                islong = 0;
                                continue;
                        }
                        if(rcnt == count){
                                putsect(p);
                                return rcnt;
                        }
                }
                putsect(p);
        }
        return rcnt;
}

/*
 * set up ndp for a directory's parent
 * the hardest part is setting up paddr
 */
int
walkup(Xfile *f, Dosptr *ndp)
{
        Dosbpb *bp;
        Dosptr *dp;
        Dosdir *xd;
        Iosect *p;
        long k, o, so, start, pstart, ppstart, st, ppclust;

        bp = f->xf->ptr;
        dp = f->ptr;
        memset(ndp, 0, sizeof(Dosptr));
        ndp->prevaddr = -1;
        ndp->naddr = -1;
        ndp->addr = dp->paddr;
        ndp->offset = dp->poffset;

        chat("walkup: paddr=%#lx...", dp->paddr);

        /*
         * root's paddr is always itself
         */
        if(isroot(dp->paddr))
                return 0;

        /*
         * find the start of our parent's directory
         */
        p = getsect(f->xf, dp->paddr);
        if(p == nil)
                goto error;
        xd = (Dosdir *)&p->iobuf[dp->poffset];
        dirdump(xd);
        start = getstart(f->xf, xd);
        chat("start=%#lx...", start);
        if(start == 0)
                goto error;
        putsect(p);

        /*
         * verify that parent's . points to itself
         */
        p = getsect(f->xf, clust2sect(bp, start));
        if(p == nil)
                goto error;
        xd = (Dosdir *)p->iobuf;
        dirdump(xd);
        st = getstart(f->xf, xd);
        if(xd->name[0]!='.' || xd->name[1]!=' ' || start!=st)
                goto error;

        /*
         * parent's .. is the next entry, and has start of parent's parent
         */
        xd++;
        dirdump(xd);
        if(xd->name[0] != '.' || xd->name[1] != '.')
                goto error;
        pstart = getstart(f->xf, xd);
        putsect(p);

        /*
         * we're done if parent is root
         */
        if(pstart == 0 || f->xf->isfat32 && pstart == bp->rootstart)
                return 0;

        /*
         * verify that parent's . points to itself
         */
        p = getsect(f->xf, clust2sect(bp, pstart));
        if(p == 0){
                chat("getsect %ld failed\n", pstart);
                goto error;
        }
        xd = (Dosdir *)p->iobuf;
        dirdump(xd);
        st = getstart(f->xf, xd);
        if(xd->name[0]!='.' || xd->name[1]!=' ' || pstart!=st)
                goto error;

        /*
         * parent's parent's .. is the next entry, and has start of parent's parent's parent
         */
        xd++;
        dirdump(xd);
        if(xd->name[0] != '.' || xd->name[1] != '.')
                goto error;
        ppstart = getstart(f->xf, xd);
        putsect(p);

        /*
         * open parent's parent's parent, and walk through it until parent's parent is found
         * need this to find parent's parent's addr and offset
         */
        ppclust = ppstart;
        if(f->xf->isfat32 && ppclust == 0){
                ppclust = bp->rootstart;
                chat("ppclust 0, resetting to rootstart\n");
        }
        k = ppclust ? clust2sect(bp, ppclust) : bp->rootaddr;
        p = getsect(f->xf, k);
        if(p == nil){
                chat("getsect %ld failed\n", k);
                goto error;
        }
        xd = (Dosdir *)p->iobuf;
        dirdump(xd);
        if(ppstart){
                st = getstart(f->xf, xd);
                if(xd->name[0]!='.' || xd->name[1]!=' ' || ppstart!=st)
                        goto error;
        }
        for(so=1;; so++){
                for(o=0; o<bp->sectsize; o+=DOSDIRSIZE){
                        xd = (Dosdir *)&p->iobuf[o];
                        if(xd->name[0] == 0x00){
                                chat("end dir\n");
                                goto error;
                        }
                        if(xd->name[0] == DOSEMPTY)
                                continue;
                        st = getstart(f->xf, xd);
                        if(st == pstart)
                                goto out;
                }
                if(ppclust){
                        if(so%bp->clustsize == 0){
                                mlock(bp);
                                ppclust = getfat(f->xf, ppclust);
                                unmlock(bp);
                                if(ppclust < 0){
                                        chat("getfat %ld failed\n", ppclust);
                                        goto error;
                                }
                        }
                        k = clust2sect(bp, ppclust) + 
                                so%bp->clustsize;
                }else{
                        if(so*bp->sectsize >= bp->rootsize*DOSDIRSIZE)
                                goto error;
                        k = bp->rootaddr + so;
                }
                putsect(p);
                p = getsect(f->xf, k);
                if(p == 0){
                        chat("getsect %ld failed\n", k);
                        goto error;
                }
        }
out:
        putsect(p);
        ndp->paddr = k;
        ndp->poffset = o;
        return 0;

error:
        if(p)
                putsect(p);
        return -1;
}

long
readfile(Xfile *f, void *vbuf, long offset, long count)
{
        Xfs *xf = f->xf;
        Dosbpb *bp = xf->ptr;
        Dosptr *dp = f->ptr;
        Dosdir *d = dp->d;
        int isect, addr, o, c;
        Iosect *p;
        uchar *buf;
        long length, rcnt;

        rcnt = 0;
        length = GLONG(d->length);
        buf = vbuf;
        if(offset >= length)
                return 0;
        if(offset+count >= length)
                count = length - offset;
        isect = offset/bp->sectsize;
        o = offset%bp->sectsize;
        while(count > 0){
                addr = fileaddr(f, isect++, 0);
                if(addr < 0)
                        break;
                c = bp->sectsize - o;
                if(c > count)
                        c = count;
                p = getsect(xf, addr);
                if(p == 0)
                        return -1;
                memmove(&buf[rcnt], &p->iobuf[o], c);
                putsect(p);
                count -= c;
                rcnt += c;
                o = 0;
        }
        return rcnt;
}

long
writefile(Xfile *f, void *vbuf, long offset, long count)
{
        Xfs *xf = f->xf;
        Dosbpb *bp = xf->ptr;
        Dosptr *dp = f->ptr;
        Dosdir *d = dp->d;
        int isect, addr = 0, o, c;
        Iosect *p;
        uchar *buf;
        long length, rcnt = 0, dlen;

        buf = vbuf;
        isect = offset/bp->sectsize;
        o = offset%bp->sectsize;
        while(count > 0){
                addr = fileaddr(f, isect++, 1);
                if(addr < 0)
                        break;
                c = bp->sectsize - o;
                if(c > count)
                        c = count;
                if(c == bp->sectsize){
                        p = getosect(xf, addr);
                        p->flags = 0;
                }else{
                        p = getsect(xf, addr);
                        if(p == nil)
                                return -1;
                }
                memmove(&p->iobuf[o], &buf[rcnt], c);
                p->flags |= BMOD;
                putsect(p);
                count -= c;
                rcnt += c;
                o = 0;
        }
        if(rcnt <= 0 && addr < 0)
                return -1;
        length = 0;
        dlen = GLONG(d->length);
        if(rcnt > 0)
                length = offset+rcnt;
        else if(dp->addr && dp->clust){
                c = bp->clustsize*bp->sectsize;
                if(dp->iclust > (dlen+c-1)/c)
                        length = c*dp->iclust;
        }
        if(length > dlen)
                PLONG(d->length, length);
        puttime(d, 0);
        dp->p->flags |= BMOD;
        return rcnt;
}

int
truncfile(Xfile *f, long length)
{
        Xfs *xf = f->xf;
        Dosbpb *bp = xf->ptr;
        Dosptr *dp = f->ptr;
        Dosdir *d = dp->d;
        long clust, next, n;

        mlock(bp);
        clust = getstart(f->xf, d);
        n = length;
        if(n <= 0)
                putstart(f->xf, d, 0);
        else
                n -= bp->sectsize;
        while(clust > 0){
                next = getfat(xf, clust);
                if(n <= 0)
                        putfat(xf, clust, 0);
                else
                        n -= bp->clustsize*bp->sectsize;
                clust = next;
        }
        unmlock(bp);
        PLONG(d->length, length);
        dp->iclust = 0;
        dp->clust = 0;
        dp->p->flags |= BMOD;
        return 0;
}

void
putdir(Dosdir *d, Dir *dp)
{
        if(dp->mode != ~0){
                if(dp->mode & 2)
                        d->attr &= ~DRONLY;
                else
                        d->attr |= DRONLY;
                if(dp->mode & DMEXCL)
                        d->attr |= DSYSTEM;
                else
                        d->attr &= ~DSYSTEM;
        }
        if(dp->mtime != ~0)
                puttime(d, dp->mtime);
}

/*
 * should extend this to deal with
 * creation and access dates
 */
void
getdir(Xfs *xfs, Dir *dp, Dosdir *d, int addr, int offset)
{
        if(d == nil || addr == 0)
                panic("getdir on root");
        dp->type = 0;
        dp->dev = 0;
        getname(dp->name, d);

        dp->qid.path = addr*(Sectorsize/DOSDIRSIZE) +
                        offset/DOSDIRSIZE;
        dp->qid.vers = 0;

        if(d->attr & DRONLY)
                dp->mode = 0444;
        else
                dp->mode = 0666;
        dp->atime = gtime(d);
        dp->mtime = dp->atime;
        dp->qid.type = QTFILE;
        if(d->attr & DDIR){
                dp->qid.type = QTDIR;
                dp->mode |= DMDIR|0111;
                dp->length = 0;
        }else
                dp->length = GLONG(d->length);
        if(d->attr & DSYSTEM){
                dp->mode |= DMEXCL;
                if(iscontig(xfs, d))
                        dp->mode |= DMAPPEND;
        }

        dp->uid = "bill";
        dp->muid = "bill";
        dp->gid = "trog";
}

void
getname(char *p, Dosdir *d)
{
        int c, i;

        for(i=0; i<8; i++){
                c = d->name[i];
                if(c == '\0' || c == ' ')
                        break;
                if(i == 0 && c == 0x05)
                        c = 0xe5;
                *p++ = c;
        }
        for(i=0; i<3; i++){
                c = d->ext[i];
                if(c == '\0' || c == ' ')
                        break;
                if(i == 0)
                        *p++ = '.';
                *p++ = c;
        }
        *p = 0;
}

static char*
getnamerunes(char *dst, uchar *buf, int step)
{
        int i;
        Rune r;
        char dbuf[DOSRUNE * UTFmax + 1], *d;

        d = dbuf;
        r = 1;
        for(i = 1; r && i < 11; i += 2){
                r = buf[i] | (buf[i+1] << 8);
                d += runetochar(d, &r);
        }
        for(i = 14; r && i < 26; i += 2){
                r = buf[i] | (buf[i+1] << 8);
                d += runetochar(d, &r);
        }
        for(i = 28; r && i < 32; i += 2){
                r = buf[i] | (buf[i+1] << 8);
                d += runetochar(d, &r);
        }

        if(step == 1)
                dst -= d - dbuf;

        memmove(dst, dbuf, d - dbuf);

        if(step == -1){
                dst += d - dbuf;
                *dst = '\0';
        }

        return dst;
}

char*
getnamesect(char *dbuf, char *d, uchar *buf, int *islong, int *sum, int step)
{
        /*
         * validation checks to make sure we're
         * making up a consistent name
         */
        if(buf[11] != 0xf || buf[12] != 0){
                *islong = 0;
                return nil;
        }
        if(step == 1){
                if((buf[0] & 0xc0) == 0x40){
                        *islong = buf[0] & 0x3f;
                        *sum = buf[13];
                        d = dbuf + DOSNAMELEN;
                        *--d = '\0';
                }else if(*islong && *islong == buf[0] + 1 && *sum == buf[13]){
                        *islong = buf[0];
                }else{
                        *islong = 0;
                        return nil;
                }
        }else{
                if(*islong + 1 == (buf[0] & 0xbf) && *sum == buf[13]){
                        *islong = buf[0] & 0x3f;
                        if(buf[0] & 0x40)
                                *sum = -1;
                }else{
                        *islong = 0;
                        *sum = -1;
                        return nil;
                }
        }
        if(*islong > 20){
                *islong = 0;
                *sum = -1;
                return nil;
        }

        return getnamerunes(d, buf, step);
}

void
putname(char *p, Dosdir *d)
{
        int i;

        memset(d->name, ' ', sizeof d->name+sizeof d->ext);
        for(i=0; i<sizeof d->name; i++){
                if(*p == 0 || *p == '.')
                        break;
                d->name[i] = toupper(*p++);
        }
        p = strrchr(p, '.');
        if(p){
                for(i=0; i<sizeof d->ext; i++){
                        if(*++p == 0)
                                break;
                        d->ext[i] = toupper(*p);
                }
        }
}

static void
putnamesect(uchar *slot, Rune *longname, int curslot, int first, int sum)
{
        Rune r;
        Dosdir ds;
        int i, j;

        memset(&ds, 0xff, sizeof ds);
        ds.attr = 0xf;
        ds.reserved[0] = 0;
        ds.reserved[1] = sum;
        ds.start[0] = 0;
        ds.start[1] = 0;
        if(first)
                ds.name[0] = 0x40 | curslot;
        else 
                ds.name[0] = curslot;
        memmove(slot, &ds, sizeof ds);

        j = (curslot-1) * DOSRUNE;

        for(i = 1; i < 11; i += 2){
                r = longname[j++];
                slot[i] = r;
                slot[i+1] = r >> 8;
                if(r == 0)
                        return;
        }
        for(i = 14; i < 26; i += 2){
                r = longname[j++];
                slot[i] = r;
                slot[i+1] = r >> 8;
                if(r == 0)
                        return;
        }
        for(i = 28; i < 32; i += 2){
                r = longname[j++];
                slot[i] = r;
                slot[i+1] = r >> 8;
                if(r == 0)
                        return;
        }
}

int
aliassum(Dosdir *d)
{
        int i, sum;

        if(d == nil)
                return -1;
        sum = 0;
        for(i = 0; i < 11; i++)
                sum = (((sum&1)<<7) | ((sum&0xfe)>>1)) + d->name[i];
        return sum & 0xff;
}

int
putlongname(Xfs *xf, Dosptr *ndp, char *name, char sname[13])
{
        Dosbpb *bp;
        Dosdir tmpd;
        Rune longname[DOSNAMELEN+1];
        int i, first, sum, nds, len;

        /* calculate checksum */
        putname(sname, &tmpd);
        sum = aliassum(&tmpd);

        bp = xf->ptr;
        first = 1;
        len = utftorunes(longname, name, DOSNAMELEN);
        if(chatty){
                chat("utftorunes %s =", name);
                for(i=0; i<len; i++)
                        chat(" %.4X", longname[i]);
                chat("\n");
        }
        for(nds = (len + DOSRUNE-1) / DOSRUNE; nds > 0; nds--){
                putnamesect(&ndp->p->iobuf[ndp->offset], longname, nds, first, sum);
                first = 0;
                ndp->offset += 32;
                if(ndp->offset == bp->sectsize){
                        chat("long name moving over sector boundary\n");
                        ndp->p->flags |= BMOD;
                        putsect(ndp->p);
                        ndp->p = nil;

                        /*
                         * switch to the next cluster for a long entry
                         * naddr should be set up correctly by searchdir
                         */
                        ndp->prevaddr = ndp->addr;
                        ndp->addr = ndp->naddr;
                        ndp->naddr = -1;
                        if(ndp->addr == -1)
                                return -1;
                        ndp->p = getsect(xf, ndp->addr);
                        if(ndp->p == nil)
                                return -1;
                        ndp->offset = 0;
                        ndp->d = (Dosdir *)&ndp->p->iobuf[ndp->offset];
                }
        }
        return 0;
}

long
getfat(Xfs *xf, int n)
{
        Dosbpb *bp = xf->ptr;
        Iosect *p;
        ulong k, sect;
        int o, fb;

        if(n < FATRESRV || n >= bp->fatclusters)
                return -1;
        fb = bp->fatbits;
        k = (fb * n) >> 3;
        if(k >= bp->fatsize*bp->sectsize)
                panic("getfat");
        sect = k/bp->sectsize + bp->fataddr;
        o = k%bp->sectsize;
        p = getsect(xf, sect);
        if(p == nil)
                return -1;
        k = p->iobuf[o++];
        if(o >= bp->sectsize){
                putsect(p);
                p = getsect(xf, sect+1);
                if(p == nil)
                        return -1;
                o = 0;
        }
        k |= p->iobuf[o++]<<8;
        if(fb == 32){
                /* fat32 is really fat28 */
                k |= p->iobuf[o++] << 16;
                k |= (p->iobuf[o] & 0x0f) << 24;
                fb = 28;
        }
        putsect(p);
        if(fb == 12){
                if(n&1)
                        k >>= 4;
                else
                        k &= 0xfff;
        }
        if(chatty > 1)
                chat("fat(%#x)=%#lx...", n, k);

        /*
         * This is a very strange check for out of range.
         * As a concrete example, for a 16-bit FAT,
         * FFF8 through FFFF all signify ``end of cluster chain.''
         * This generalizes to other-sized FATs.
         */
        if(k >= (1 << fb) - 8)
                return -1;

        return k;
}

void
putfat(Xfs *xf, int n, ulong val)
{
        Fatinfo *fi;
        Dosbpb *bp;
        Iosect *p;
        ulong k, sect, esect;
        int o;

        bp = xf->ptr;
        if(n < FATRESRV || n >= bp->fatclusters)
                panic("putfat n=%d", n);
        k = (bp->fatbits * n) >> 3;
        if(k >= bp->fatsize*bp->sectsize)
                panic("putfat");
        sect = k/bp->sectsize + bp->fataddr;
        esect = sect + bp->nfats * bp->fatsize;
        for(; sect<esect; sect+=bp->fatsize){
                o = k%bp->sectsize;
                p = getsect(xf, sect);
                if(p == nil)
                        continue;
                switch(bp->fatbits){
                case 12:
                        if(n&1){
                                p->iobuf[o] &= 0x0f;
                                p->iobuf[o++] |= val<<4;
                                if(o >= bp->sectsize){
                                        p->flags |= BMOD;
                                        putsect(p);
                                        p = getsect(xf, sect+1);
                                        if(p == nil)
                                                continue;
                                        o = 0;
                                }
                                p->iobuf[o] = val>>4;
                        }else{
                                p->iobuf[o++] = val;
                                if(o >= bp->sectsize){
                                        p->flags |= BMOD;
                                        putsect(p);
                                        p = getsect(xf, sect+1);
                                        if(p == nil)
                                                continue;
                                        o = 0;
                                }
                                p->iobuf[o] &= 0xf0;
                                p->iobuf[o] |= (val>>8) & 0x0f;
                        }
                        break;
                case 16:
                        p->iobuf[o++] = val;
                        p->iobuf[o] = val>>8;
                        break;
                case 32:        /* fat32 is really fat28 */
                        p->iobuf[o++] = val;
                        p->iobuf[o++] = val>>8;
                        p->iobuf[o++] = val>>16;
                        p->iobuf[o] = (p->iobuf[o] & 0xf0) | ((val>>24) & 0x0f);
                        break;
                default:
                        panic("putfat fatbits");
                }
                p->flags |= BMOD;
                putsect(p);
        }

        if(val == 0)
                bp->freeclusters++;
        else
                bp->freeclusters--;

        if(bp->fatinfo){
                p = getsect(xf, bp->fatinfo);
                if(p != nil){
                        fi = (Fatinfo*)p->iobuf;
                        PLONG(fi->nextfree, bp->freeptr);
                        PLONG(fi->freeclust, bp->freeclusters);
                        p->flags |= BMOD;
                        putsect(p);
                }
        }
}

/*
 * Contiguous falloc; if we can, use lastclust+1.
 * Otherwise, move the file to get some space.
 * If there just isn't enough contiguous space
 * anywhere on disk, fail.
 */
int
cfalloc(Xfile *f)
{
        int l;

        if((l=makecontig(f, 8)) >= 0)
                return l;
        return makecontig(f, 1);
}

/*
 * Check whether a file is contiguous.
 */
int
iscontig(Xfs *xf, Dosdir *d)
{
        long clust, next;

        clust = getstart(xf, d);
        if(clust <= 0)
                return 1;

        for(;;) {
                next = getfat(xf, clust);
                if(next < 0)
                        return 1;
                if(next != clust+1)
                        return 0;
                clust = next;
        }
}

/*
 * Make a file contiguous, with nextra clusters of 
 * free space after it for later expansion.
 * Return the number of the first new cluster.
 */
int
makecontig(Xfile *f, int nextra)
{
        Dosbpb *bp;
        Dosdir *d;
        Dosptr *dp;
        Xfs *xf;
        Iosect *wp, *rp;
        long clust, next, last, start, rclust, wclust, eclust, ostart;
        int isok, i, n, nclust, nrun, rs, ws;

        xf = f->xf;
        bp = xf->ptr;
        dp = f->ptr;
        d = dp->d;

        isok = 1;
        nclust = 0;
        clust = fileclust(f, 0, 0);
        chat("clust %#lux", clust);
        if(clust != -1) {
                for(;;) {
                        nclust++;
                        chat(".");
                        next = getfat(xf, clust);
                        if(next <= 0)
                                break;
                        if(next != clust+1)
                                isok = 0;
                        clust = next;
                }
        }
        chat("nclust %d\n", nclust);

        if(isok && clust != -1) {
                eclust = clust+1;       /* eclust = first cluster past file */
                assert(eclust == fileclust(f, 0, 0)+nclust);
                for(i=0; i<nextra; i++)
                        if(getfat(xf, eclust+i) != 0)
                                break;
                if(i == nextra) {       /* they were all free */
                        chat("eclust=%#lx, getfat eclust-1 = %#lux\n", eclust, getfat(xf, eclust-1));
                        assert(getfat(xf, eclust-1) == 0xffffffff);
                        putfat(xf, eclust-1, eclust);
                        putfat(xf, eclust, 0xffffffff);
                        bp->freeptr = clust+1;  /* to help keep the blocks free */
                        return eclust;
                }
        }

        /* need to search for nclust+nextra contiguous free blocks */
        last = -1;
        n = bp->freeptr;
        nrun = 0;
        for(;;){
                if(getfat(xf, n) == 0) {
                        if(last+1 == n)
                                nrun++;
                        else
                                nrun = 1;
                        if(nrun >= nclust+nextra)
                                break;
                        last = n;
                }
                if(++n >= bp->fatclusters)
                        n = FATRESRV;
                if(n == bp->freeptr) {
                        errno = Econtig;
                        return -1;
                }
        }
        bp->freeptr = n+1;

        /* copy old data over */
        start = n+1 - nrun;

        /* sanity check */
        for(i=0; i<nclust+nextra; i++)
                assert(getfat(xf, start+i) == 0);

        chat("relocate chain %lux -> 0x%lux len %d\n", fileclust(f, 0, 0), start, nclust);

        wclust = start;
        for(rclust = fileclust(f, 0, 0); rclust > 0; rclust = next){
                rs = clust2sect(bp, rclust);
                ws = clust2sect(bp, wclust);
                for(i=0; i<bp->clustsize; i++, rs++, ws++){
                        rp = getsect(xf, rs);
                        if(rp == nil)
                                return -1;
                        wp = getosect(xf, ws);
                        assert(wp != nil);
                        memmove(wp->iobuf, rp->iobuf, bp->sectsize);
                        wp->flags = BMOD;
                        putsect(rp);
                        putsect(wp);
                }
                chat("move cluster %#lx -> %#lx...", rclust, wclust);
                next = getfat(xf, rclust);
                putfat(xf, wclust, wclust+1);
                wclust++;
        }

        /* now wclust points at the first new cluster; chain it in */
        chat("wclust 0x%lux start 0x%lux (fat->0x%lux) nclust %d\n", wclust, start, getfat(xf, start), nclust);
        assert(wclust == start+nclust);
        putfat(xf, wclust, 0xffffffff); /* end of file */

        /* update directory entry to point at new start */
        ostart = fileclust(f, 0, 0);
        putstart(xf, d, start);

        /* check our work */
        i = 0;
        clust = fileclust(f, 0, 0);
        if(clust != -1) {
                for(;;) {
                        i++;
                        next = getfat(xf, clust);
                        if(next <= 0)
                                break;
                        assert(next == clust+1);
                        clust = next;
                }
        }
        chat("chain check: len %d\n", i);
        assert(i == nclust+1);

        /* succeeded; remove old chain. */
        for(rclust = ostart; rclust > 0; rclust = next){
                next = getfat(xf, rclust);
                putfat(xf, rclust, 0);  /* free cluster */
        }

        return start+nclust;
}       

int
falloc(Xfs *xf)
{
        Dosbpb *bp = xf->ptr;
        Iosect *p;
        int n, i, k;

        n = bp->freeptr;
        for(;;){
                if(getfat(xf, n) == 0)
                        break;
                if(++n >= bp->fatclusters)
                        n = FATRESRV;
                if(n == bp->freeptr)
                        return -1;
        }
        bp->freeptr = n+1;
        if(bp->freeptr >= bp->fatclusters)
                bp->freeptr = FATRESRV;
        putfat(xf, n, 0xffffffff);
        k = clust2sect(bp, n);
        for(i=0; i<bp->clustsize; i++){
                p = getosect(xf, k+i);
                memset(p->iobuf, 0, bp->sectsize);
                p->flags = BMOD;
                putsect(p);
        }
        return n;
}

void
ffree(Xfs *xf, long start)
{
        putfat(xf, start, 0);
}

long
clust2sect(Dosbpb *bp, long clust)
{
        return bp->dataaddr + (clust - FATRESRV) * bp->clustsize;
}

long
sect2clust(Dosbpb *bp, long sect)
{
        long c;

        c = (sect - bp->dataaddr) / bp->clustsize + FATRESRV;
        assert(sect == clust2sect(bp, c));
        return c;
}

void
puttime(Dosdir *d, long s)
{
        Tm *t;
        ushort x;

        if(s == 0)
                s = time(0);
        t = localtime(s);

        x = (t->hour<<11) | (t->min<<5) | (t->sec>>1);
        PSHORT(d->time, x);
        x = ((t->year-80)<<9) | ((t->mon+1)<<5) | t->mday;
        PSHORT(d->date, x);
}

long
gtime(Dosdir *dp)
{
        Tm tm;
        int i;

        i = GSHORT(dp->time);
        tm.hour = i >> 11;
        tm.min = (i >> 5) & 63;
        tm.sec = (i & 31) << 1;
        i = GSHORT(dp->date);
        tm.year = 80 + (i >> 9);
        tm.mon = ((i >> 5) & 15) - 1;
        tm.mday = i & 31;
        tm.zone[0] = '\0';
        tm.tzoff = 0;
        tm.yday = 0;

        return tm2sec(&tm);
}

/*
 * structure dumps for debugging
 */
void
bootdump(int fd, Dosboot *b)
{
        Biobuf bp;

        Binit(&bp, fd, OWRITE);
        Bprint(&bp, "magic: 0x%2.2x 0x%2.2x 0x%2.2x\n",
                b->magic[0], b->magic[1], b->magic[2]);
        Bprint(&bp, "version: \"%8.8s\"\n", (char*)b->version);
        Bprint(&bp, "sectsize: %d\n", GSHORT(b->sectsize));
        Bprint(&bp, "clustsize: %d\n", b->clustsize);
        Bprint(&bp, "nresrv: %d\n", GSHORT(b->nresrv));
        Bprint(&bp, "nfats: %d\n", b->nfats);
        Bprint(&bp, "rootsize: %d\n", GSHORT(b->rootsize));
        Bprint(&bp, "volsize: %d\n", GSHORT(b->volsize));
        Bprint(&bp, "mediadesc: 0x%2.2x\n", b->mediadesc);
        Bprint(&bp, "fatsize: %d\n", GSHORT(b->fatsize));
        Bprint(&bp, "trksize: %d\n", GSHORT(b->trksize));
        Bprint(&bp, "nheads: %d\n", GSHORT(b->nheads));
        Bprint(&bp, "nhidden: %ld\n", GLONG(b->nhidden));
        Bprint(&bp, "bigvolsize: %ld\n", GLONG(b->bigvolsize));
        Bprint(&bp, "driveno: %d\n", b->driveno);
        Bprint(&bp, "reserved0: 0x%2.2x\n", b->reserved0);
        Bprint(&bp, "bootsig: 0x%2.2x\n", b->bootsig);
        Bprint(&bp, "volid: 0x%8.8lux\n", GLONG(b->volid));
        Bprint(&bp, "label: \"%11.11s\"\n", (char*)b->label);
        Bterm(&bp);
}

void
bootdump32(int fd, Dosboot32 *b)
{
        Biobuf bp;

        Binit(&bp, fd, OWRITE);
        Bprint(&bp, "magic: 0x%2.2x 0x%2.2x 0x%2.2x\n",
                b->magic[0], b->magic[1], b->magic[2]);
        Bprint(&bp, "version: \"%8.8s\"\n", (char*)b->version);
        Bprint(&bp, "sectsize: %d\n", GSHORT(b->sectsize));
        Bprint(&bp, "clustsize: %d\n", b->clustsize);
        Bprint(&bp, "nresrv: %d\n", GSHORT(b->nresrv));
        Bprint(&bp, "nfats: %d\n", b->nfats);
        Bprint(&bp, "rootsize: %d\n", GSHORT(b->rootsize));
        Bprint(&bp, "volsize: %d\n", GSHORT(b->volsize));
        Bprint(&bp, "mediadesc: 0x%2.2x\n", b->mediadesc);
        Bprint(&bp, "fatsize: %d\n", GSHORT(b->fatsize));
        Bprint(&bp, "trksize: %d\n", GSHORT(b->trksize));
        Bprint(&bp, "nheads: %d\n", GSHORT(b->nheads));
        Bprint(&bp, "nhidden: %ld\n", GLONG(b->nhidden));
        Bprint(&bp, "bigvolsize: %ld\n", GLONG(b->bigvolsize));
        Bprint(&bp, "fatsize32: %ld\n", GLONG(b->fatsize32));
        Bprint(&bp, "extflags: %d\n", GSHORT(b->extflags));
        Bprint(&bp, "version: %d\n", GSHORT(b->version1));
        Bprint(&bp, "rootstart: %ld\n", GLONG(b->rootstart));
        Bprint(&bp, "infospec: %d\n", GSHORT(b->infospec));
        Bprint(&bp, "backupboot: %d\n", GSHORT(b->backupboot));
        Bprint(&bp, "reserved: %d %d %d %d %d %d %d %d %d %d %d %d\n",
                b->reserved[0], b->reserved[1], b->reserved[2], b->reserved[3],
                b->reserved[4], b->reserved[5], b->reserved[6], b->reserved[7],
                b->reserved[8], b->reserved[9], b->reserved[10], b->reserved[11]);
        Bterm(&bp);
}

void
bootsecdump32(int fd, Xfs *xf, Dosboot32 *b32)
{
        Fatinfo *fi;
        Iosect *p1;
        int fisec, bsec, res;

        fprint(fd, "\nfat32\n");
        bootdump32(fd, b32);
        res = GSHORT(b32->nresrv);
        bsec = GSHORT(b32->backupboot);
        if(bsec < res && bsec != 0){
                p1 = getsect(xf, bsec);
                if(p1 == nil)
                        fprint(fd, "\ncouldn't get backup boot sector: %r\n");
                else{
                        fprint(fd, "\nbackup boot\n");
                        bootdump32(fd, (Dosboot32*)p1->iobuf);
                        putsect(p1);
                }
        }else if(bsec != 0xffff)
                fprint(fd, "bad backup boot sector: %d reserved %d\n", bsec, res);
        fisec = GSHORT(b32->infospec);
        if(fisec < res && fisec != 0){
                p1 = getsect(xf, fisec);
                if(p1 == nil)
                        fprint(fd, "\ncouldn't get fat info sector: %r\n");
                else{
                        fprint(fd, "\nfat info %d\n", fisec);
                        fi = (Fatinfo*)p1->iobuf;
                        fprint(fd, "sig1: 0x%lux sb 0x%lux\n", GLONG(fi->sig1), FATINFOSIG1);
                        fprint(fd, "sig: 0x%lux sb 0x%lux\n", GLONG(fi->sig), FATINFOSIG);
                        fprint(fd, "freeclust: %lud\n", GLONG(fi->freeclust));
                        fprint(fd, "nextfree: %lud\n", GLONG(fi->nextfree));
                        fprint(fd, "reserved: %lud %lud %lud\n", GLONG(fi->resrv), GLONG(fi->resrv+4), GLONG(fi->resrv+8));
                        putsect(p1);
                }
        }else if(fisec != 0xffff)
                fprint(2, "bad fat info sector: %d reserved %d\n", bsec, res);
}

void
dirdump(void *vdbuf)
{
        static char attrchar[] = "rhsvda67";
        Dosdir *d;
        char *name, namebuf[DOSNAMELEN];
        char buf[128], *s, *ebuf;
        uchar *dbuf;
        int i;

        if(!chatty)
                return;

        d = vdbuf;

        ebuf = buf + sizeof(buf);
        if(d->attr == 0xf){
                dbuf = vdbuf;
                name = namebuf + DOSNAMELEN;
                *--name = '\0';
                name = getnamerunes(name, dbuf, 1);
                seprint(buf, ebuf, "\"%s\" %2.2x %2.2ux %2.2ux %d", name, dbuf[0], dbuf[12], dbuf[13], GSHORT(d->start));
        }else{
                s = seprint(buf, ebuf, "\"%.8s.%.3s\" ", (char*)d->name, (char*)d->ext);
                for(i=7; i>=0; i--)
                        *s++ = d->attr&(1<<i) ? attrchar[i] : '-';
        
                i = GSHORT(d->time);
                s = seprint(s, ebuf, " %2.2d:%2.2d:%2.2d", i>>11, (i>>5)&63, (i&31)<<1);
                i = GSHORT(d->date);
                s = seprint(s, ebuf, " %2.2d.%2.2d.%2.2d", 80+(i>>9), (i>>5)&15, i&31);
        
                i = GSHORT(d->ctime);
                s = seprint(s, ebuf, " %2.2d:%2.2d:%2.2d", i>>11, (i>>5)&63, (i&31)<<1);
                i = GSHORT(d->cdate);
                s = seprint(s, ebuf, " %2.2d.%2.2d.%2.2d", 80+(i>>9), (i>>5)&15, i&31);
        
                i = GSHORT(d->adate);
                s = seprint(s, ebuf, " %2.2d.%2.2d.%2.2d", 80+(i>>9), (i>>5)&15, i&31);

                seprint(s, ebuf, " %d %d", GSHORT(d->start), GSHORT(d->length));
        }
        chat("%s\n", buf);
}

int
cistrcmp(char *s1, char *s2)
{
        int c1, c2;

        while(*s1){
                c1 = *s1++;
                c2 = *s2++;

                if(c1 >= 'A' && c1 <= 'Z')
                        c1 -= 'A' - 'a';

                if(c2 >= 'A' && c2 <= 'Z')
                        c2 -= 'A' - 'a';

                if(c1 != c2)
                        return c1 - c2;
        }
        return -*s2;
}

int
utftorunes(Rune *rr, char *s, int n)
{
        Rune *r, *re;
        int c;

        r = rr;
        re = r + n - 1;
        while(c = (uchar)*s){
                if(c < Runeself){
                        *r = c;
                        s++;
                }else
                        s += chartorune(r, s);
                r++;
                if(r >= re)
                        break;
        }
        *r = 0;
        return r - rr;
}