#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 *p1;
	Fatinfo *fi;
	uchar buf[512];
	Dosboot *b;
	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;
	}

	/* don't use getsect() before determining sector size */
	if(devread(xf, 0, buf, sizeof(buf)) < 0)
		return -1;

	if(!isdosfs(buf))
		return -1;

	bp = malloc(sizeof(Dosbpb));
	if(bp == nil)
		return -1;
	memset(bp, 0, sizeof(Dosbpb));	/* clear lock */

	b = (Dosboot*)buf;
	xf->sectsize = GSHORT(b->sectsize);
	if(xf->sectsize < 32){
		if(chatty)
			fprint(2, "sectsize %d\n", xf->sectsize);
		goto badfmt;
	}
	xf->sect2trk = GSHORT(b->trksize);
	if(xf->sect2trk == 0){
		if(chatty)
			fprint(2, "sect2trk %d\n", xf->sect2trk);
		goto badfmt;
	}
	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 */
		b32 = (Dosboot32*)buf;
		if(chatty)
			bootsecdump32(2, xf, b32);
		xf->isfat32 = 1;
		bp->fatsize = GLONG(b32->fatsize32);
		if(bp->fatsize == 0){
			if(chatty)
				fprint(2, "fatsize 0\n");
			goto badfmt;
		}
		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;
		bp->dataaddr = bp->rootaddr + (bp->rootsize*DOSDIRSIZE + xf->sectsize-1)/xf->sectsize;
		bp->freeptr = FATRESRV;
	}
	if(bp->clustsize == 0){
		if(chatty)
			fprint(2, "clustsize 0\n");
		goto badfmt;
	}
	if(bp->volsize < bp->dataaddr){
		if(chatty)
			fprint(2, "volsize < dataaddr\n");
		goto badfmt;
	}
	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 (%ld clusters)...", bp->fatbits, bp->fatclusters);
	for(i=0; i<b->nfats; i++)
		chat("fat %d: %lld...", i, bp->fataddr+i*bp->fatsize);
	chat("root: %lld...", bp->rootaddr);
	chat("data: %lld...", bp->dataaddr);

	xf->ptr = bp;
	return 0;
badfmt:
	free(bp);
	return -1;
}

/*
 * 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(vlong addr)
{
	return addr == 0;
}

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

	dp = f->ptr;
	if(dp->p != nil)
		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, f->xf)){
			chat("qid mismatch f=%#llux d=%#llux...", f->qid.path, QIDPATH(dp, f->xf));
			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 == nil)
		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)
{
	Xfs *xf = f->xf;
	Dosbpb *bp = xf->ptr;
	Dosptr *dp = f->ptr;
	Dosdir *d = dp->d;
	long start, clust, nskip, 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(!xf->isfat32 && isroot(dp->addr))
		return -1;

	if(xf->isfat32 && isroot(dp->addr)){
		start = bp->rootstart;
	}else{
		start = getstart(xf, d);
		if(start == 0){
			if(!cflag)
				return -1;
			mlock(bp);
			start = falloc(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(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(xf);
				if(next < 0)
					break;
				putfat(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 
 */
vlong
fileaddr(Xfile *f, long isect, int cflag)
{
	Xfs *xf = f->xf;
	Dosbpb *bp;
	Dosptr *dp;
	long clust;

	bp = xf->ptr;
	dp = f->ptr;
	if(!f->xf->isfat32 && isroot(dp->addr)){
		if(isect*xf->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;
	Dosdir *d;
	char buf[261], *bname;
	int isect, o, o1, islong, have, need, sum;
	vlong addr, addr1, addr2, prevaddr, prevaddr1;

	xf = f->xf;
	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 == nil)
			break;
		for(o=0; o<xf->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 && (xf->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;
	int isect, o;
	vlong addr;
	Iosect *p;
	Dosdir *d;

	for(isect=0;; isect++){
		addr = fileaddr(f, isect, 0);
		if(addr < 0)
			break;
		p = getsect(xf, addr);
		if(p == nil)
			return -1;
		for(o=0; o<xf->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, vlong offset, long count)
{
	Xfs *xf;
	Dir dir;
	int isect, o, islong, sum;
	vlong addr;
	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;
	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 == nil)
			return -1;
		for(o=0; o<xf->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)
{
	Xfs *xf = f->xf;
	Dosbpb *bp = xf->ptr;
	Dosptr *dp = f->ptr;
	Dosdir *xd;
	Iosect *p;
	long o, so, start, pstart, ppstart, st, ppclust;
	vlong k;

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

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

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

	/*
	 * find the start of our parent's directory
	 */
	p = getsect(xf, dp->paddr);
	if(p == nil)
		goto error;
	xd = (Dosdir *)&p->iobuf[dp->poffset];
	dirdump(xd);
	start = getstart(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(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(xf, xd);
	putsect(p);

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

	/*
	 * verify that parent's . points to itself
	 */
	p = getsect(xf, clust2sect(bp, pstart));
	if(p == nil){
		chat("getsect %ld failed\n", pstart);
		goto error;
	}
	xd = (Dosdir *)p->iobuf;
	dirdump(xd);
	st = getstart(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(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(xf->isfat32 && ppclust == 0){
		ppclust = bp->rootstart;
		chat("ppclust 0, resetting to rootstart\n");
	}
	k = ppclust ? clust2sect(bp, ppclust) : bp->rootaddr;
	p = getsect(xf, k);
	if(p == nil){
		chat("getsect %lld failed\n", k);
		goto error;
	}
	xd = (Dosdir *)p->iobuf;
	dirdump(xd);
	if(ppstart){
		st = getstart(xf, xd);
		if(xd->name[0]!='.' || xd->name[1]!=' ' || ppstart!=st)
			goto error;
	}
	for(so=1;; so++){
		for(o=0; o<xf->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(xf, xd);
			if(st == pstart)
				goto out;
		}
		if(ppclust){
			if(so%bp->clustsize == 0){
				mlock(bp);
				ppclust = getfat(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*xf->sectsize >= bp->rootsize*DOSDIRSIZE)
				goto error;
			k = bp->rootaddr + so;
		}
		putsect(p);
		p = getsect(xf, k);
		if(p == nil){
			chat("getsect %lld failed\n", k);
			goto error;
		}
	}
out:
	putsect(p);
	ndp->paddr = k;
	ndp->poffset = o;
	return 0;

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

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

	if(offset >= MAXFILELEN)
		return 0;
	if(offset+count > MAXFILELEN)
		count = MAXFILELEN - offset;

	rcnt = 0;
	length = GLONG(d->length);
	buf = vbuf;
	if(offset >= length)
		return 0;
	if(offset+count >= length)
		count = length - offset;
	isect = offset/xf->sectsize;
	o = offset%xf->sectsize;
	while(count > 0){
		addr = fileaddr(f, isect++, 0);
		if(addr < 0)
			break;
		c = xf->sectsize - o;
		if(c > count)
			c = count;
		p = getsect(xf, addr);
		if(p == nil)
			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, vlong offset, long count)
{
	Xfs *xf = f->xf;
	Dosbpb *bp = xf->ptr;
	Dosptr *dp = f->ptr;
	Dosdir *d = dp->d;
	int isect, o, c;
	vlong addr;
	Iosect *p;
	uchar *buf;
	ulong length, rcnt, dlen;

	if(offset >= MAXFILELEN)
		return 0;
	if(offset+count > MAXFILELEN)
		count = MAXFILELEN - offset;

	rcnt = 0;
	addr = 0;
	buf = vbuf;
	isect = offset/xf->sectsize;
	o = offset%xf->sectsize;
	while(count > 0){
		addr = fileaddr(f, isect++, 1);
		if(addr < 0)
			break;
		c = xf->sectsize - o;
		if(c > count)
			c = count;
		if(c == xf->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(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*xf->sectsize;
		if(dp->iclust > (dlen+c-1)/c)
			length = (ulong)c*dp->iclust;
	}
	if(length > dlen)
		PLONG(d->length, length);
	puttime(d, 0);
	dp->p->flags |= BMOD;
	return rcnt;
}

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

	if(length > MAXFILELEN)
		return -1;

	mlock(bp);
	clust = getstart(xf, d);
	n = length;
	if(n <= 0)
		putstart(xf, d, 0);
	else
		n -= xf->sectsize;
	while(clust > 0){
		next = getfat(xf, clust);
		if(n <= 0)
			putfat(xf, clust, 0);
		else
			n -= bp->clustsize*xf->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 *xf, Dir *dp, Dosdir *d, vlong 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*(xf->sectsize/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(xf, 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++ = tolower(c);
	}
	for(i=0; i<3; i++){
		c = d->ext[i];
		if(c == '\0' || c == ' ')
			break;
		if(i == 0)
			*p++ = '.';
		*p++ = tolower(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])
{
	Dosdir tmpd;
	Rune longname[DOSNAMELEN+1];
	int i, first, sum, nds, len;

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

	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 == xf->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, long n)
{
	Dosbpb *bp = xf->ptr;
	Iosect *p;
	ulong k, o;
	vlong sect;
	int fb;

	if(n < FATRESRV || n >= bp->fatclusters)
		return -1;
	fb = bp->fatbits;
	k = (fb * n) >> 3;
	if(k >= bp->fatsize*xf->sectsize)
		panic("getfat");
	sect = k/xf->sectsize + bp->fataddr;
	o = k%xf->sectsize;
	p = getsect(xf, sect);
	if(p == nil)
		return -1;
	k = p->iobuf[o++];
	if(o >= xf->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(%#lx)=%#lux...", 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 >= (1UL << fb) - 8)
		return -1;

	return k;
}

void
putfat(Xfs *xf, int n, ulong val)
{
	Fatinfo *fi;
	Dosbpb *bp;
	Iosect *p;
	ulong k;
	vlong 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*xf->sectsize)
		panic("putfat");
	sect = k/xf->sectsize + bp->fataddr;
	esect = sect + bp->nfats * bp->fatsize;
	for(; sect<esect; sect+=bp->fatsize){
		o = k%xf->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 >= xf->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 >= xf->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;
	vlong 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, xf->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;
	vlong 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, xf->sectsize);
		p->flags = BMOD;
		putsect(p);
	}
	return n;
}

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

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

long
sect2clust(Dosbpb *bp, vlong sect)
{
	return ((sect - bp->dataaddr) / bp->clustsize) + FATRESRV;
}

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);
	PSHORT(d->adate, x);
}

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

	i = GSHORT(dp->time);

	memset(&tm, 0, sizeof(tm));
	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;
	tm.tz = tzload("local");

	tmnorm(&tm);

	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: %lud\n", GLONG(b->nhidden));
	Bprint(&bp, "bigvolsize: %lud\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: %lud\n", GLONG(b->nhidden));
	Bprint(&bp, "bigvolsize: %lud\n", GLONG(b->bigvolsize));
	Bprint(&bp, "fatsize32: %lud\n", GLONG(b->fatsize32));
	Bprint(&bp, "extflags: %d\n", GSHORT(b->extflags));
	Bprint(&bp, "version: %d\n", GSHORT(b->version1));
	Bprint(&bp, "rootstart: %lud\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 != 0 && bsec != 0xffff){
		if(bsec >= res)
			fprint(fd, "bad backup boot sector: %d reserved %d\n", bsec, res);
		else {
			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);
			}
		}
	}
	fisec = GSHORT(b32->infospec);
	if(fisec != 0 && fisec != 0xffff){
		if(fisec >= res)
			fprint(2, "bad fat info sector: %d reserved %d\n", fisec, res);
		else {
			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);
			}
		}
	}
}

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) == 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+%d", i>>11, (i>>5)&63, (i&31)<<1, (int)d->ctimetenth);
		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 %lud", GSHORT(d->start), GLONG(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;
}
