Plan 9 from Bell Labs’s /usr/web/sources/contrib/quanstro/root/sys/src/fs/pc/dosfs.c

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


#include "all.h"
#include "io.h"
#include "mem.h"

#include "dosfs.h"

#define	chat(...)	//print(__VA_ARGS__)

/*
 *  block io buffers
 */
typedef struct Clustbuf	Clustbuf;

struct Clustbuf
{
	int	flags;
	int	age;
	Devsize	sector;
	uchar *	iobuf;
	Dos *	dos;
	int	size;
	int	bufsize;
};

enum
{
	Nbio=	16,
	LOCKED=	1,
	MOD=	2,
	IMMED=	4,
};

static void	puttime(Dosdir*);

static Clustbuf	bio[Nbio];

/*
 *  write an io buffer and update its flags
 */
static void
writeclust(Clustbuf *p)
{
	Dos *dos;
	Off addr;

	dos = p->dos;
	addr = (p->sector+dos->start)*dos->sectbytes;
	chat("writeclust @ %lld addr %lld...", (Wideoff)p->sector,
		(Wideoff)addr);
	if(dos->write(dos->dev, p->iobuf, p->size, addr) != p->size)
		panic("writeclust: write");
	p->flags &= ~(MOD|IMMED);
	chat("OK\n");
}

/*
 *  write any dirty buffers
 */
static void
syncclust(void)
{
	Clustbuf *p;

	for(p = bio; p < &bio[Nbio]; p++){
		if(p->flags & LOCKED)
			panic("syncclust");
		if(p->flags & MOD)
			writeclust(p);
	}
}

/*
 *  get an io buffer, possibly with valid data
 */
static Clustbuf*
getclust0(Dos *dos, Off sector)
{
	Clustbuf *p, *oldest;

	chat("getclust0 @ %lld\n", (Wideoff)sector);

	/*
	 *  if we have it, just return it
	 *  otherwise, reuse the oldest unlocked entry
	 */
	oldest = 0;
	for(p = bio; p < &bio[Nbio]; p++){
		if(sector == p->sector && dos == p->dos){
			if(p->flags & LOCKED)
				panic("getclust0 locked");
			chat("getclust0 %lld in cache\n", (Wideoff)sector);
			p->flags |= LOCKED;
			return p;
		}
		if(p->flags & LOCKED)
			continue;
		if(oldest == 0 || p->age <= oldest->age)
			oldest = p;
	}
	p = oldest;
	if(p == 0)
		panic("getclust0 all locked");
	p->flags |= LOCKED;
	if(p->flags & MOD)
		writeclust(p);

	/*
	 *  make sure the buffer is big enough
	 */
	if(p->iobuf==0 || p->bufsize < dos->clustbytes){
		p->bufsize = dos->clustbytes;
		p->iobuf = ialloc(p->bufsize, 0);
	}
	if(sector >= dos->dataaddr)
		p->size = dos->clustbytes;
	else
		p->size = dos->sectbytes;
	p->dos = 0;	/* make it invalid */
	return p;
}

/*
 *  get an io block from an io buffer
 */
static Clustbuf*
getclust(Dos *dos, Off sector)
{
	Clustbuf *p;
	Off addr;

	p = getclust0(dos, sector);
	if(p->dos){
		p->age = Ticks;
		return p;
	}
	addr = (sector+dos->start)*dos->sectbytes;
	chat("getclust read addr %lld\n", (Wideoff)addr);
	if(dos->read(dos->dev, p->iobuf, p->size, addr) != p->size){
		chat("can't read block\n");
		return 0;
	}

	p->age = Ticks;
	p->dos = dos;
	p->sector = sector;
	chat("getclust %lld read\n", (Wideoff)sector);
	return p;
}

/*
 *  get an io block from an io buffer;
 *  any current data is discarded.
 */
static Clustbuf*
getclustz(Dos *dos, Off sector)
{
	Clustbuf *p;

	p = getclust0(dos, sector);
	p->age = Ticks;
	p->dos = dos;
	p->sector = sector;
	memset(p->iobuf, 0, p->size);
	p->flags |= MOD;
	chat("getclustz %lld\n", (Wideoff)sector);
	return p;
}

/*
 *  release an io buffer
 */
static void
putclust(Clustbuf *p)
{
	if(!(p->flags & LOCKED))
		panic("putclust lock");
	if((p->flags & (MOD|IMMED)) == (MOD|IMMED))
		writeclust(p);
	p->flags &= ~LOCKED;
	chat("putclust @ sector %lld...", (Wideoff)p->sector);
}

/*
 *  walk the fat one level ( n is a current cluster number ).
 *  return the new cluster number or -1 if no more.
 */
static long
fatwalk(Dos *dos, int n)
{
	ulong k, sect;
	Clustbuf *p;
	int o;

	chat("fatwalk %d\n", n);

	if(n < 2 || n >= dos->fatclusters)
		return -1;

	switch(dos->fatbits){
	case 12:
		k = (3*n)/2; break;
	case 16:
		k = 2*n; break;
	default:
		return -1;
	}
	if(k >= dos->fatbytes)
		panic("getfat");

	sect = k/dos->sectbytes + dos->fataddr;
	o = k%dos->sectbytes;
	p = getclust(dos, sect);
	k = p->iobuf[o++];
	if(o >= dos->sectbytes){
		putclust(p);
		p = getclust(dos, sect+1);
		o = 0;
	}
	k |= p->iobuf[o]<<8;
	putclust(p);
	if(dos->fatbits == 12){
		if(n&1)
			k >>= 4;
		else
			k &= 0xfff;
		if(k >= 0xff8)
			k |= 0xf000;
	}
	k = k < 0xfff8 ? k : -1;
	chat("fatwalk %d -> %lud\n", n, k);
	return k;
}

/*
 *  write a value into each copy of the fat.
 */
static void
fatwrite(Dos *dos, int n, int val)
{
	Off k, sect;
	Clustbuf *p;
	int i, o;

	chat("fatwrite %d %d...", n, val);

	if(n < 2 || n >= dos->fatclusters)
		panic("fatwrite n");

	switch(dos->fatbits){
	case 12:
		k = (3*n)/2; break;
	case 16:
		k = 2*n; break;
	default:
		panic("fatwrite fatbits");
		return;
	}
	if(k >= dos->fatbytes)
		panic("fatwrite k");

	for(i=0; i<dos->nfats; i++, k+=dos->fatbytes){
		sect = k/dos->sectbytes + dos->fataddr;
		o = k%dos->sectbytes;
		p = getclust(dos, sect);
		if(p == 0)
			panic("fatwrite getclust");
		switch(dos->fatbits){
		case 12:
			if(n&1){
				p->iobuf[o] &= 0x0f;
				p->iobuf[o++] |= val<<4;
			}else
				p->iobuf[o++] = val;
			if(o >= dos->sectbytes){
				p->flags |= MOD;
				putclust(p);
				p = getclust(dos, sect+1);
				if(p == 0)
					panic("fatwrite getclust");
				o = 0;
			}
			if(n&1)
				p->iobuf[o] = val>>4;
			else{
				p->iobuf[o] &= 0xf0;
				p->iobuf[o] |= (val>>8)&0x0f;
			}
			break;
		case 16:
			p->iobuf[o++] = val;
			p->iobuf[o] = val>>8;
			break;
		}
		p->flags |= MOD;
		putclust(p);
	}
	chat("OK\n");
}

/*
 *  allocate a free cluster from the fat.
 */
static int
fatalloc(Dos *dos)
{
	Clustbuf *p;
	int n;

	n = dos->freeptr;
	for(;;){
		if(fatwalk(dos, n) == 0)
			break;
		if(++n >= dos->fatclusters)
			n = 2;
		if(n == dos->freeptr)
			return -1;
	}
	dos->freeptr = n+1;
	if(dos->freeptr >= dos->fatclusters)
		dos->freeptr = 2;
	fatwrite(dos, n, 0xffff);
	p = getclustz(dos, dos->dataaddr + (n-2)*dos->clustsize);
	putclust(p);
	return n;
}

/*
 *  map a file's logical sector address to a physical sector address
 */
static long
fileaddr(Dosfile *fp, Off ltarget, Clustbuf *pdir)
{
	Dos *dos = fp->dos;
	Dosdir *dp;
	Off p;

	chat("fileaddr %8.8s %lld\n", fp->name, (Wideoff)ltarget);
	/*
	 *  root directory is contiguous and easy
	 */
	if(fp->pdir == 0){
		if(ltarget*dos->sectbytes >= dos->rootsize*sizeof(Dosdir))
			return -1;
		p = dos->rootaddr + ltarget;
		chat("fileaddr %lld -> %lld\n", (Wideoff)ltarget, (Wideoff)p);
		return p;
	}
	if(fp->pstart == 0){	/* empty file */
		if(!pdir)
			return -1;
		p = fatalloc(dos);
		if(p <= 0)
			return -1;
		chat("fileaddr initial alloc %lld\n", (Wideoff)p);
		dp = (Dosdir *)(pdir->iobuf + fp->odir);
		puttime(dp);
		dp->start[0] = p;
		dp->start[1] = p>>8;
		pdir->flags |= MOD;
		fp->pstart = p;
		fp->pcurrent = p;
		fp->lcurrent = 0;
	}
	/*
	 *  anything else requires a walk through the fat
	 *  [lp]current will point to the last cluster if we run off the end
	 */
	ltarget /= dos->clustsize;
	if(fp->pcurrent == 0 || fp->lcurrent > ltarget){
		/* go back to the beginning */
		fp->lcurrent = 0;
		fp->pcurrent = fp->pstart;
	}
	while(fp->lcurrent < ltarget){
		/* walk the fat */
		p = fatwalk(dos, fp->pcurrent);
		if(p < 0){
			if(!pdir)
				return -1;
			p = fatalloc(dos);
			if(p < 0){
				print("file system full\n");
				return -1;
			}
			fatwrite(dos, fp->pcurrent, p);
		}
		fp->pcurrent = p;
		++fp->lcurrent;
	}

	/*
	 *  clusters start at 2 instead of 0 (why? - presotto)
	 */
	p = dos->dataaddr + (fp->pcurrent-2)*dos->clustsize;
	chat("fileaddr %lld -> %lld\n", (Wideoff)ltarget, (Wideoff)p);
	return p;
}

/*
 *  set up a dos file name
 */
static void
setname(char *name, char *ext, char *from)
{
	char *to;

	memset(name, ' ', 8);
	memset(ext, ' ', 3);

	to = name;
	for(; *from && to-name < 8; from++, to++){
		if(*from == '.'){
			from++;
			break;
		}
		if(*from >= 'a' && *from <= 'z')
			*to = *from + 'A' - 'a';
		else
			*to = *from;
	}
	to = ext;
	for(; *from && to-ext < 3; from++, to++){
		if(*from >= 'a' && *from <= 'z')
			*to = *from + 'A' - 'a';
		else
			*to = *from;
	}

	chat("name is %8.8s %3.3s\n", name, ext);
}

/*
 *  walk a directory returns
 * 	-1 if something went wrong
 *	 0 if not found
 *	 1 if found
 */
static int
doswalk(Dosfile *fp, char *name)
{
	char dname[8], dext[3];
	Clustbuf *p;
	Dosdir *dp;
	Off o, addr;

	chat("walk(%s)\n", name);
	if((fp->attr & DOSDIR) == 0){
		chat("walking non-directory!\n");
		return -1;
	}

	setname(dname, dext, name);

	fp->offset = 0;	/* start at the beginning */
	for(;;){
		addr = fileaddr(fp, fp->offset/fp->dos->sectbytes, 0);
		if(addr < 0)
			return 0;
		p = getclust(fp->dos, addr);
		if(p == 0)
			return -1;
		for(o=0; o<p->size; o += sizeof(Dosdir)){
			dp = (Dosdir *)(p->iobuf + o);
			chat("comparing to %8.8s.%3.3s\n", (char*)dp->name, (char*)dp->ext);
			if(memcmp(dname, dp->name, sizeof(dp->name)) != 0)
				continue;
			if(memcmp(dext, dp->ext, sizeof(dp->ext)) == 0)
				goto Found;
		}
		fp->offset += p->size;
		putclust(p);
	}

Found:
	fp->pdir = p->sector;
	fp->odir = o;
	putclust(p);
	memmove(fp->name, dname, sizeof(fp->name));
	memmove(fp->ext, dext, sizeof(fp->ext));
	fp->attr = dp->attr;
	fp->length = GLONG(dp->length);
	fp->pstart = GSHORT(dp->start);
	fp->pcurrent = 0;
	fp->lcurrent = 0;
	fp->offset = 0;
	return 1;
}

static void
bootdump(Dosboot *b)
{
	USED(b);
	chat("magic: 0x%2.2x 0x%2.2x 0x%2.2x\n",
		b->magic[0], b->magic[1], b->magic[2]);
	chat("version: \"%8.8s\"\n", (char*)b->version);
	chat("sectbytes: %d\n", GSHORT(b->sectbytes));
	chat("allocsize: %d\n", b->clustsize);
	chat("nresrv: %d\n", GSHORT(b->nresrv));
	chat("nfats: %d\n", b->nfats);
	chat("rootsize: %d\n", GSHORT(b->rootsize));
	chat("volsize: %d\n", GSHORT(b->volsize));
	chat("mediadesc: 0x%2.2x\n", b->mediadesc);
	chat("fatsize: %d\n", GSHORT(b->fatsize));
	chat("trksize: %d\n", GSHORT(b->trksize));
	chat("nheads: %d\n", GSHORT(b->nheads));
	chat("nhidden: %d\n", GLONG(b->nhidden));
	chat("bigvolsize: %d\n", GLONG(b->bigvolsize));
	chat("driveno: %d\n", b->driveno);
	chat("reserved0: 0x%2.2x\n", b->reserved0);
	chat("bootsig: 0x%2.2x\n", b->bootsig);
	chat("volid: 0x%8.8x\n", GLONG(b->volid));
	chat("label: \"%11.11s\"\n", (char*)b->label);
}

/*
 *  instructions that boot blocks can start with
 */
#define	JMPSHORT	0xeb
#define JMPNEAR		0xe9

/*
 *  read dos file system properties
 */
int
dosinit(Dos *dos)
{
	Clustbuf *p;
	Dospart *dp;
	Dosboot *b;
	int i;

	chat("dosinit()\n");
	/* defaults till we know better */
	dos->start = 0;
	dos->sectbytes = 512;
	dos->clustsize = 1;
	dos->clustbytes = 512;

	/* get first sector */
	p = getclust(dos, 0);
	if(p == 0){
		chat("can't read boot block\n");
		return -1;
	}
	p->dos = 0;

	/* if a hard disk format, look for an active partition */
	b = (Dosboot *)p->iobuf;
	if(b->magic[0] != JMPNEAR && (b->magic[0] != JMPSHORT || b->magic[2] != 0x90)){
		/* is the 0x55 in error here? */
		if(p->iobuf[0x1fe] != 0x55 || p->iobuf[0x1ff] != 0xaa){
			print("no dos file system or partition table\n");
print("b->magic[] = %.2ux %.2ux %.2ux\n", b->magic[0], b->magic[1], b->magic[2]);
print("iobuf[1fe] = %.2ux [1ff] = %.2ux\n", p->iobuf[0x1fe], p->iobuf[0x1ff]);
			putclust(p);
			return -1;
		}
		dp = (Dospart*)&p->iobuf[0x1be];
		for(i = 0; i < 4; i++, dp++)
			if(dp->type && dp->flag == 0x80)
				break;
		if(i == 4){
			putclust(p);
			return -1;
		}
		dos->start += GLONG(dp->start);
		putclust(p);
		p = getclust(dos, 0);
		if(p == 0){
			chat("can't read boot block\n");
			putclust(p);
			return -1;
		}
		p->dos = 0;
	}

	b = (Dosboot *)p->iobuf;
	if(b->magic[0] != JMPNEAR && (b->magic[0] != JMPSHORT || b->magic[2] != 0x90)){
		print("no dos file system\n");
		putclust(p);
		return -1;
	}

	bootdump(b);/**/

	/*
	 *  determine the systems' wonderous properties
	 */
	dos->sectbytes = GSHORT(b->sectbytes);
	dos->clustsize = b->clustsize;
	dos->clustbytes = dos->sectbytes*dos->clustsize;
	dos->nresrv = GSHORT(b->nresrv);
	dos->nfats = b->nfats;
	dos->rootsize = GSHORT(b->rootsize);
	dos->volsize = GSHORT(b->volsize);
	if(dos->volsize == 0)
		dos->volsize = GLONG(b->bigvolsize);
	dos->mediadesc = b->mediadesc;
	dos->fatsize = GSHORT(b->fatsize);
	dos->fatbytes = dos->sectbytes*dos->fatsize;
	dos->fataddr = dos->nresrv;
	dos->rootaddr = dos->fataddr + dos->nfats*dos->fatsize;
	i = dos->rootsize*sizeof(Dosdir) + dos->sectbytes - 1;
	i = i/dos->sectbytes;
	dos->dataaddr = dos->rootaddr + i;
	dos->fatclusters = 2+(dos->volsize - dos->dataaddr)/dos->clustsize;
	if(dos->fatclusters < 4087)
		dos->fatbits = 12;
	else
		dos->fatbits = 16;
	dos->freeptr = 2;
	putclust(p);

	/*
	 *  set up the root
	 */
	dos->root.dos = dos;
	dos->root.pdir = 0;
	dos->root.odir = 0;
	memmove(dos->root.name, "<root>  ", 8);
	memmove(dos->root.ext, "   ", 3);
	dos->root.attr = DOSDIR;
	dos->root.length = dos->rootsize*sizeof(Dosdir);
	dos->root.pstart = 0;
	dos->root.lcurrent = 0;
	dos->root.pcurrent = 0;
	dos->root.offset = 0;

	syncclust();
	return 0;
}

static char *
nextelem(char *path, char *elem)
{
	int i;

	while(*path == '/')
		path++;
	if(*path==0 || *path==' ')
		return 0;
	for(i=0; *path && *path != '/' && *path != ' '; i++){
		if(i >= NAMELEN){
			print("name component too long\n");
			return 0;
		}
		*elem++ = *path++;
	}
	*elem = 0;
	return path;
}

static void
puttime(Dosdir *d)
{
	Timet secs;
	Rtc rtc;
	ushort x;

	secs = rtctime();
	sec2rtc(secs, &rtc);
	x = (rtc.hour<<11) | (rtc.min<<5) | (rtc.sec>>1);
	d->time[0] = x;
	d->time[1] = x>>8;
	x = ((rtc.year-80)<<9) | ((rtc.mon+1)<<5) | rtc.mday;
	d->date[0] = x;
	d->date[1] = x>>8;
}

Dosfile*
dosopen(Dos *dos, char *path, Dosfile *fp)
{
	char element[NAMELEN];

	chat("dosopen(%s)\n", path);
	*fp = dos->root;
	while(path = nextelem(path, element)){
		switch(doswalk(fp, element)){
		case -1:
			print("error walking to %s\n", element);
			return 0;
		case 0:
			print("%s not found\n", element);
			return 0;
		case 1:
			print("found %s attr 0x%ux start 0x%llux len %lld\n",
				element, fp->attr, (Wideoff)fp->pstart,
				(Wideoff)fp->length);
			break;
		}
	}

	syncclust();
	return fp;
}

/*
 *  read from a dos file
 */
long
dosread(Dosfile *fp, void *a, long n)
{
	Off addr, k, o;
	Clustbuf *p;
	uchar *to;

	chat("dosread(,,%ld)\n", n);
	if((fp->attr & DOSDIR) == 0){
		if(fp->offset >= fp->length)
			return 0;
		if(fp->offset+n > fp->length)
			n = fp->length - fp->offset;
	}
	to = a;
	while(n > 0){
		/*
		 *  read the data; sectors below dos->dataaddr
		 *  are read one at a time.
		 */
		addr = fileaddr(fp, fp->offset/fp->dos->sectbytes, 0);
		if(addr < 0)
			return -1;
		p = getclust(fp->dos, addr);
		if(p == 0)
			return -1;
		/*
		 *  copy the bytes we need
		 */
		o = fp->offset % p->size;
		k = p->size - o;
		if(k > n)
			k = n;
		memmove(to, p->iobuf+o, k);
		putclust(p);
		to += k;
		fp->offset += k;
		n -= k;
	}
	syncclust();
	return to - (uchar *)a;
}

/*
 *  write to a dos file
 */
long
doswrite(Dosfile *fp, void *a, long n)
{
	Off blksize, addr, k, o;
	Clustbuf *p, *pdir;
	Dosdir *dp;
	uchar *from;

	if(fp->attr & DOSDIR){
		print("write dir\n");
		return -1;
	}
	if(fp->pdir){
		pdir = getclust(fp->dos, fp->pdir);
		/*
		 *  should do consistency check if
		 *  concurrent access is possible.
		 */
		if(pdir == 0)
			panic("doswrite");
	}else
		pdir = 0;
	blksize = pdir ? fp->dos->clustbytes : fp->dos->sectbytes;
	from = a;
	while(n > 0){
		addr = fileaddr(fp, fp->offset/fp->dos->sectbytes, pdir);
		if(addr < 0)
			return -1;
		o = fp->offset % blksize;
		if(o == 0 && n >= blksize)
			p = getclustz(fp->dos, addr);
		else
			p = getclust(fp->dos, addr);
		if(p == 0)
			return -1;
		/*
		 *  copy the bytes we need
		 */
		k = p->size - o;
		if(k > n)
			k = n;
		memmove(p->iobuf+o, from, k);
		p->flags |= MOD;
		putclust(p);
		from += k;
		fp->offset += k;
		n -= k;
	}
	if(pdir){
		dp = (Dosdir *)(pdir->iobuf + fp->odir);
		puttime(dp);
		if(fp->offset > fp->length){
			fp->length = fp->offset;
			dp->length[0] = fp->length;
			dp->length[1] = fp->length>>8;
			dp->length[2] = fp->length>>16;
			dp->length[3] = fp->length>>24;
		}
		pdir->flags |= MOD;
		putclust(pdir);
	}
	syncclust();
	return from - (uchar *)a;
}

/*
 *  truncate a dos file to zero length
 */
int
dostrunc(Dosfile *fp)
{
	Clustbuf *pdir;
	Dosdir *dp;
	Off p, np;

	if(fp->attr & DOSDIR){
		print("trunc dir\n");
		return -1;
	}
	pdir = getclust(fp->dos, fp->pdir);
	if(pdir == 0)
		panic("dostrunc");
	p = fatwalk(fp->dos, fp->pstart);
	fatwrite(fp->dos, fp->pstart, 0xffff);
	while(p >= 0){
		np = fatwalk(fp->dos, p);
		fatwrite(fp->dos, p, 0);
		p = np;
	}
	fp->length = 0;
	dp = (Dosdir *)(pdir->iobuf + fp->odir);
	puttime(dp);
	dp->length[0] = 0;
	dp->length[1] = 0;
	dp->length[2] = 0;
	dp->length[3] = 0;
	pdir->flags |= MOD;
	putclust(pdir);
	syncclust();
	return 0;
}

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.