#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.
        * DOS 4.0 MBR has a displacement of 8.
        */
       if(buf[0] == 0xEB && buf[2] == 0x90 && buf[1] != 0x08)
               return 1;


       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(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);
                       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;
       }
       return 0;       /* not reached */
}

/*
* 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;

       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;
}