9fans - fans of the OS Plan 9 from Bell Labs
 help / color / mirror / Atom feed
* Re: [9fans] jukebox configuration changes
@ 2002-11-02  9:37 Geoff Collyer
  0 siblings, 0 replies; 2+ messages in thread
From: Geoff Collyer @ 2002-11-02  9:37 UTC (permalink / raw)
  To: 9fans

[-- Attachment #1: Type: text/plain, Size: 612 bytes --]

As promised, here are changes to the file server kernel to permit the
use of jukeboxes with different-sized discs on a single file server.
I've tested it with an HP 80EX with 5.2GB discs and an HP 40FX with
2.6GB discs on one file server.

I've enclosed the diffs (a little over 200 lines) and a bundle of the
changed files.  I've done a little other file server work, including
incorporating nigel's/forsyth's wormcopy, since I last synced kernels
with nemo, but I don't think these changes will be hard to integrate.
The files in the fs directory are the prototypes for cloning file
server kernels.

[-- Attachment #2.1: Type: text/plain, Size: 308 bytes --]

The following attachment had content that we can't
prove to be harmless.  To avoid possible automatic
execution, we changed the content headers.
The original header was:

	Content-Disposition: attachment; filename=fs.diffs
	Content-Type: text/plain; charset="US-ASCII"
	Content-Transfer-Encoding: 7bit

[-- Attachment #2.2: fs.diffs.suspect --]
[-- Type: application/octet-stream, Size: 5643 bytes --]

diff /n/dump/2002/1020/sys/src/fs/dev/cw.c ./dev/cw.c
125c125
< 	long hmsize, hmaddr;
---
> 	long hmsize, hmaddr, dsize, dsizepct;
188a189,196
> 	/* print stats in terms of (first-)disc sides */
> 	dsize = wormsizeside(dev, 0);
> 	if (dsize < 1) {
> 		print("wormsizeside returned a size of %ld for %Z side 0\n",
> 			dsize, dev);
> 		dsize = 100;
> 	}
> 	dsizepct = dsize/100;
190,191c198
< 				h->fsize/DSIZE,
< 				(h->fsize%DSIZE)/(DSIZE/100));
---
> 				h->fsize/dsize, (h->fsize%dsize)/dsizepct);
195,196c202
< 				h->wmax/DSIZE,
< 				(h->wmax%DSIZE)/(DSIZE/100));
---
> 				h->wmax/dsize, (h->wmax%dsize)/dsizepct);
198,199c204
< 				h->wsize/DSIZE,
< 				(h->wsize%DSIZE)/(DSIZE/100));
---
> 				h->wsize/dsize, (h->wsize%dsize)/dsizepct);
1640c1645
< mvstates(Device *dev, int s1, int s2, int drive)
---
> mvstates(Device *dev, int s1, int s2, int side)
1651,1654c1656,1663
< 	hi = lo + 500*DSIZE;	// BOTCH arbitrary large number
< 	if(drive >= 0) {
< 		lo = drive * DSIZE;
< 		hi = lo + DSIZE;
---
> 	hi = lo + devsize(dev->cw.w);	/* size of all sides totalled */
> 	if(side >= 0) {
> 		/* operate on only a single disc side */
> 		Sidestarts ss;
>
> 		wormsidestarts(dev, side, &ss);
> 		lo = ss.sstart;
> 		hi = ss.s1start;
1881a1891
> 	Sidestarts ss;
1891a1902,1903
> 	if (dskno >= 0)
> 		wormsidestarts(dev, dskno, &ss);
1904,1905c1916,1917
< 			if(dskno < 0 ||
< 			   m >= dskno*DSIZE && m < (dskno+1)*DSIZE) {
---
> 			/* if given a diskno, restrict to just that disc side */
> 			if(dskno < 0 || m >= ss.sstart && m < ss.s1start) {
1926a1939
> 	Sidestarts ss;
1935c1948,1949
< 	ml = dskno*DSIZE;
---
> 	wormsidestarts(dev, dskno, &ss);
> 	ml = ss.sstart;		/* start at beginning of disc side #dskno */
2206d2219
< 		s2 = DSIZE;
2208a2222,2223
> 		else
> 			s2 = wormsizeside(dev, s1); /* default to 1 disc side */

diff /n/dump/2002/1020/sys/src/fs/dev/juke.c ./dev/juke.c
6c6
< #define	FIXEDSIZE	0
---
>
58a59,60
> extern int FIXEDSIZE;
>
282c284
< 	if(FIXEDSIZE)
---
> 	if(FIXEDSIZE)	// TODO? push FIXEDSIZE into Device or Juke struct
289a292,347
> /*
>  * d must be, or be on, the same jukebox as `side' resides on.
>  * have to paw through Devmcat of sides.
>  */
> long
> wormsizeside(Device *d, int side)
> {
> 	int s;
> 	Device *x;
>
> 	switch(d->type) {
> 	default:
> 		print("wormsizeside: dev not ro, cw, juke nor mcat: %Z\n", d);
> 		return 0;
>
> 	case Devmcat:					/* of sides */
> 		break;
> 	case Devjuke:
> 		return wormsizeside(d->j.m, side);	/* mcat of sides */
> 	case Devcw:
> 		return wormsizeside(d->cw.w, side);	/* should be juke */
> 	case Devro:
> 		return wormsizeside(d->ro.parent, side);	/* cw */
> 	}
>
> 	if (side < 0 || side >= d->cat.ndev) {
> 		print("wormsizeside: side %d not in range [0..%d] for %Z\n",
> 			side, d->cat.ndev, d);
> 		return 0;
> 	}
> 	x = d->cat.first;
> 	for (s = 0; s < side; s++) {
> 		x = x->link;
> 		if (x == nil)
> 			panic("wormsizeside nil link");
> 	}
> 	if (x->type != Devworm && x->type != Devlworm) {
> 		print("wormsizeside: %Z of %Z type not (l)worm\n", x, d);
> 		return 0;
> 	}
> 	return wormsize(x);
> }
>
> /* returns starts of side #side and #(side+1) in *stp */
> void
> wormsidestarts(Device *dev, int side, Sidestarts *stp)
> {
> 	int s;
> 	long dstart;
>
> 	for (dstart = s = 0; s < side; s++)
> 		dstart += wormsizeside(dev, s);
> 	stp->sstart = dstart;
> 	stp->s1start = dstart + wormsizeside(dev, side);
> }
>
583a642
> 	Device *dev = d;
587c646
< 		print("juke platter not devworm: %Z\n", d);
---
> 		print("juke platter not (devmcat of) dev(l)worm: %Z\n", d);
590a650,653
> 		/*
> 		 * we don't call mcatinit(d) here, so we have to set d->cat.ndev
> 		 * ourselves.
> 		 */
592a656
> 		dev->cat.ndev = o;

diff /n/dump/2002/1020/sys/src/fs/fs/9fsfs.c ./fs/9fsfs.c
7a8,14
> /*
>  * setting this to zero permits the use of discs of different sizes, but
>  * can make jukeinit() quite slow while the robotics work through each disc
>  * twice (once per side).
>  */
> int FIXEDSIZE = 1;
>

diff /n/dump/2002/1020/sys/src/fs/fs/dat.h ./fs/dat.h
0a1,6
> /*
>  * The most fundamental constant.
>  * Will the code compile with RBUFSIZE a variable?
>  * Nope, for one thing, RBUFSIZE determines FEPERBUF, which determines
>  * the number of elements in a free-list-block array.
>  */
2d7
< #define	DSIZE		(631736-1)	/* 5.2GB on first disc */

diff /n/dump/2002/1020/sys/src/fs/port/chk.c ./port/chk.c
163,165c163,166
< 		oldblock = fsize/DSIZE;
< 		oldblock *= DSIZE;
< 		if(oldblock < 0)
---
> 		/* round fsize down to start of current side */
> 		int s;
> 		long dsize;
>
166a168,170
> 		for (s = 0; dsize = wormsizeside(dev, s),
> 		     dsize > 0 && oldblock + dsize < fsize; s++)
> 			oldblock += dsize;

diff /n/dump/2002/1020/sys/src/fs/port/portdat.h ./port/portdat.h
153c153
< 		struct			/* worm wren */
---
> 		struct			/* worm wren, (l)worm in targ */
178,179c178,179
< 			Device*	j;
< 			Device*	m;
---
> 			Device*	j;	/* (robotics, worm drives) - wrens */
> 			Device*	m;	/* (sides) - r or l devices */
195c195
< 		struct			/* part */
---
> 		struct			/* byte-swapped */
200a201,205
>
> typedef struct Sidestarts {
> 	long	sstart;			/* blocks before start of side */
> 	long	s1start;		/* blocks before start of next side */
> } Sidestarts;

diff /n/dump/2002/1020/sys/src/fs/port/portfns.h ./port/portfns.h
265a266,267
> long	wormsizeside(Device *, int side);
> void	wormsidestarts(Device *dev, int side, Sidestarts *stp);

[-- Attachment #3.1: Type: text/plain, Size: 306 bytes --]

The following attachment had content that we can't
prove to be harmless.  To avoid possible automatic
execution, we changed the content headers.
The original header was:

	Content-Disposition: attachment; filename=fs.bun
	Content-Type: text/plain; charset="US-ASCII"
	Content-Transfer-Encoding: 7bit

[-- Attachment #3.2: fs.bun.suspect --]
[-- Type: application/octet-stream, Size: 111894 bytes --]

# To unbundle, run this file
echo dev/cw.c
sed 's/^X//' >dev/cw.c <<'!'
X#include "all.h"

X#define	DEBUG		0
X#define	FIRST		SUPER_ADDR

X#define	ADDFREE		(100)
X#define	CACHE_ADDR	SUPER_ADDR
X#define	MAXAGE		10000

X#define	CDEV(d)		(d->cw.c)
X#define	WDEV(d)		(d->cw.w)
X#define	RDEV(d)		(d->cw.ro)

X/* cache state */
enum
X{
X	/* states -- beware these are recorded on the cache */
X				/*    cache    worm	*/
X	Cnone = 0,		/*	0	?	*/
X	Cdirty,			/*	1	0	*/
X	Cdump,			/*	1	0->1	*/
X	Cread,			/*	1	1	*/
X	Cwrite,			/*	2	1	*/
X	Cdump1,			/* inactive form of dump */
X	Cerror,

X	/* opcodes -- these are not recorded */
X	Onone,
X	Oread,
X	Owrite,
X	Ogrow,
X	Odump,
X	Orele,
X	Ofree,
X};

typedef	struct	Cw	Cw;
struct	Cw
X{
X	Device*	dev;
X	Device*	cdev;
X	Device*	wdev;
X	Device*	rodev;
X	Cw*	link;

X	Filter	ncwio[3];
X	int	dbucket;	/* last bucket dumped */
X	long	daddr;		/* last block dumped */
X	long	ncopy;
X	int	nodump;
X/*
X * following are cached variables for dumps
X */
X	long	fsize;
X	long	ndump;
X	int	depth;
X	int	all;		/* local flag to recur on modified directories */
X	int	allflag;	/* global flag to recur on modified directories */
X	long	falsehits;	/* times recur found modified blocks */
X	struct
X	{
X		char	name[500];
X		char	namepad[NAMELEN+10];
X	};
X};

static
char*	cwnames[] =
X{
X	[Cnone]		"none",
X	[Cdirty]	"dirty",
X	[Cdump]		"dump",
X	[Cread]		"read",
X	[Cwrite]	"write",
X	[Cdump1]	"dump1",
X	[Cerror]	"error",

X	[Onone]		"none",
X	[Oread]		"read",
X	[Owrite]	"write",
X	[Ogrow]		"grow",
X	[Odump]		"dump",
X	[Orele]		"rele",
X};

Centry*	getcentry(Bucket*, long);
int	cwio(Device*, long, void*, int);
void	cmd_cwcmd(int, char*[]);

X/*
X * console command
X * initiate a dump
X */
void
cmd_dump(int argc, char *argv[])
X{
X	Filsys *fs;

X	fs = cons.curfs;
X	if(argc > 1)
X		fs = fsstr(argv[1]);
X	if(fs == 0) {
X		print("%s: unknown file system\n", argv[1]);
X		return;
X	}
X	cfsdump(fs);
X}

X/*
X * console command
X * worm stats
X */
static
void
cmd_statw(int, char*[])
X{
X	Filsys *fs;
X	Iobuf *p;
X	Superb *sb;
X	Cache *h;
X	Bucket *b;
X	Centry *c, *ce;
X	long m, nw, bw, state[Onone];
X	long sbfsize, sbcwraddr, sbroraddr, sblast, sbnext;
X	long hmsize, hmaddr, dsize, dsizepct;
X	Device *dev;
X	Cw *cw;
X	int s;

X	fs = cons.curfs;
X	dev = fs->dev;
X	if(dev->type != Devcw) {
X		print("curfs not type cw\n");
X		return;
X	}

X	cw = dev->private;
X	if(cw == 0) {
X		print("curfs not inited\n");
X		return;
X	}

X	print("cwstats %s\n", fs->name);

X	sbfsize = 0;
X	sbcwraddr = 0;
X	sbroraddr = 0;
X	sblast = 0;
X	sbnext = 0;

X	print("	filesys %s\n", fs->name);
X	print("	nio   =%7W%7W%7W\n", cw->ncwio+0, cw->ncwio+1, cw->ncwio+2);
X	p = getbuf(dev, cwsaddr(dev), Bread);
X	if(!p || checktag(p, Tsuper, QPSUPER)) {
X		print("cwstats: checktag super\n");
X		if(p) {
X			putbuf(p);
X			p = 0;
X		}
X	}
X	if(p) {
X		sb = (Superb*)p->iobuf;
X		sbfsize = sb->fsize;
X		sbcwraddr = sb->cwraddr;
X		sbroraddr = sb->roraddr;
X		sblast = sb->last;
X		sbnext = sb->next;
X		putbuf(p);
X	}

X	p = getbuf(cw->cdev, CACHE_ADDR, Bread|Bres);
X	if(!p || checktag(p, Tcache, QPSUPER)) {
X		print("cwstats: checktag c bucket\n");
X		if(p)
X			putbuf(p);
X		return;
X	}
X	h = (Cache*)p->iobuf;
X	hmaddr = h->maddr;
X	hmsize = h->msize;

X	print("		maddr  = %8ld\n", hmaddr);
X	print("		msize  = %8ld\n", hmsize);
X	print("		caddr  = %8ld\n", h->caddr);
X	print("		csize  = %8ld\n", h->csize);
X	print("		sbaddr = %8ld\n", h->sbaddr);
X	print("		craddr = %8ld %8ld\n", h->cwraddr, sbcwraddr);
X	print("		roaddr = %8ld %8ld\n", h->roraddr, sbroraddr);
X	/* print stats in terms of (first-)disc sides */
X	dsize = wormsizeside(dev, 0);
X	if (dsize < 1) {
X		print("wormsizeside returned a size of %ld for %Z side 0\n",
X			dsize, dev);
X		dsize = 100;
X	}
X	dsizepct = dsize/100;
X	print("		fsize  = %8ld %8ld %2ld+%2ld%%\n", h->fsize, sbfsize,
X				h->fsize/dsize, (h->fsize%dsize)/dsizepct);
X	print("		slast  =          %8ld\n", sblast);
X	print("		snext  =          %8ld\n", sbnext);
X	print("		wmax   = %8ld          %2ld+%2ld%%\n", h->wmax,
X				h->wmax/dsize, (h->wmax%dsize)/dsizepct);
X	print("		wsize  = %8ld          %2ld+%2ld%%\n", h->wsize,
X				h->wsize/dsize, (h->wsize%dsize)/dsizepct);
X	putbuf(p);

X	bw = 0;	/* max filled bucket */
X	memset(state, 0, sizeof(state));
X	for(m=0; m<hmsize; m++) {
X		p = getbuf(cw->cdev, hmaddr + m/BKPERBLK, Bread);
X		if(!p || checktag(p, Tbuck, hmaddr + m/BKPERBLK)) {
X			print("cwstats: checktag c bucket\n");
X			if(p)
X				putbuf(p);
X			return;
X		}
X		b = (Bucket*)p->iobuf + m%BKPERBLK;
X		ce = b->entry + CEPERBK;
X		nw = 0;
X		for(c=b->entry; c<ce; c++) {
X			s = c->state;
X			state[s]++;
X			if(s != Cnone && s != Cread)
X				nw++;
X		}
X		putbuf(p);
X		if(nw > bw)
X			bw = nw;
X	}
X	for(s=Cnone; s<Cerror; s++)
X		print("		%6ld %s\n", state[s], cwnames[s]);
X	print("		cache %2ld%% full\n", (bw*100)/CEPERBK);
X}

int
dumpblock(Device *dev)
X{
X	Iobuf *p, *cb, *p1, *p2;
X	Cache *h;
X	Centry *c, *ce, *bc;
X	Bucket *b;
X	long m, a, msize, maddr, wmax, caddr;
X	int s1, s2, count;
X	Cw *cw;

X	cw = dev->private;
X	if(cw == 0 || cw->nodump)
X		return 0;

X	cb = getbuf(cw->cdev, CACHE_ADDR, Bread|Bres);
X	h = (Cache*)cb->iobuf;
X	msize = h->msize;
X	maddr = h->maddr;
X	wmax = h->wmax;
X	caddr = h->caddr;
X	putbuf(cb);

X	for(m=msize; m>=0; m--) {
X		a = cw->dbucket + 1;
X		if(a < 0 || a >= msize)
X			a = 0;
X		cw->dbucket = a;
X		p = getbuf(cw->cdev, maddr + a/BKPERBLK, Bread);
X		b = (Bucket*)p->iobuf + a%BKPERBLK;
X		ce = b->entry + CEPERBK;
X		bc = 0;
X		for(c=b->entry; c<ce; c++)
X			if(c->state == Cdump) {
X				if(bc == 0) {
X					bc = c;
X					continue;
X				}
X				if(c->waddr < cw->daddr) {
X					if(bc->waddr < cw->daddr &&
X					   bc->waddr > c->waddr)
X						bc = c;
X					continue;
X				}
X				if(bc->waddr < cw->daddr ||
X				   bc->waddr > c->waddr)
X					bc = c;
X			}
X		if(bc) {
X			c = bc;
X			goto found;
X		}
X		putbuf(p);
X	}
X	if(cw->ncopy) {
X		print("%ld blocks copied to worm\n", cw->ncopy);
X		cw->ncopy = 0;
X	}
X	cw->nodump = 1;
X	return 0;

found:
X	a = a*CEPERBK + (c - b->entry) + caddr;
X	p1 = getbuf(devnone, Cwdump1, 0);
X	count = 0;

retry:
X	count++;
X	if(count > 10)
X		goto stop;
X	if(devread(cw->cdev, a, p1->iobuf))
X		goto stop;
X	m = c->waddr;
X	cw->daddr = m;
X	s1 = devwrite(cw->wdev, m, p1->iobuf);
X	if(s1) {
X		p2 = getbuf(devnone, Cwdump2, 0);
X		s2 = devread(cw->wdev, m, p2->iobuf);
X		if(s2) {
X			if(s1 == 0x61 && s2 == 0x60) {
X				putbuf(p2);
X				goto retry;
X			}
X			goto stop1;
X		}
X		if(memcmp(p1->iobuf, p2->iobuf, RBUFSIZE))
X			goto stop1;
X		putbuf(p2);
X	}
X	/*
X	 * reread and compare
X	 */
X	if(conf.dumpreread) {
X		p2 = getbuf(devnone, Cwdump2, 0);
X		s1 = devread(cw->wdev, m, p2->iobuf);
X		if(s1)
X			goto stop1;
X		if(memcmp(p1->iobuf, p2->iobuf, RBUFSIZE)) {
X			print("reread C%ld W%ld didnt compare\n", a, m);
X			goto stop1;
X		}
X		putbuf(p2);
X	}

X	putbuf(p1);
X	c->state = Cread;
X	p->flags |= Bmod;
X	putbuf(p);

X	if(m > wmax) {
X		cb = getbuf(cw->cdev, CACHE_ADDR, Bread|Bmod|Bres);
X		h = (Cache*)cb->iobuf;
X		if(m > h->wmax)
X			h->wmax = m;
X		putbuf(cb);
X	}
X	cw->ncopy++;
X	return 1;

stop1:
X	putbuf(p2);
X	putbuf(p1);
X	c->state = Cdump1;
X	p->flags |= Bmod;
X	putbuf(p);
X	return 1;

stop:
X	putbuf(p1);
X	putbuf(p);
X	print("stopping dump!!\n");
X	cw->nodump = 1;
X	return 0;
X}

void
cwinit1(Device *dev)
X{
X	Cw *cw;
X	static int first;

X	cw = dev->private;
X	if(cw)
X		return;

X	if(first == 0) {
X		cmd_install("dump", "-- make dump backup to worm", cmd_dump);
X		cmd_install("statw", "-- cache/worm stats", cmd_statw);
X		cmd_install("cwcmd", "subcommand -- cache/worm errata", cmd_cwcmd);
X		roflag = flag_install("ro", "-- ro reads and writes");
X		first = 1;
X	}
X	cw = ialloc(sizeof(Cw), 0);
X	dev->private = cw;

X	cw->allflag = 0;
X	dofilter(cw->ncwio+0, C0a, C0b, 1);
X	dofilter(cw->ncwio+1, C1a, C1b, 1);
X	dofilter(cw->ncwio+2, C2a, C2b, 1);

X	cw->dev = dev;
X	cw->cdev = CDEV(dev);
X	cw->wdev = WDEV(dev);
X	cw->rodev = RDEV(dev);

X	devinit(cw->cdev);
X	devinit(cw->wdev);
X}

void
cwinit(Device *dev)
X{
X	Cw *cw;
X	Cache *h;
X	Iobuf *cb, *p;
X	long l, m;

X	cwinit1(dev);

X	cw = dev->private;
X	l = devsize(cw->wdev);
X	cb = getbuf(cw->cdev, CACHE_ADDR, Bread|Bmod|Bres);
X	h = (Cache*)cb->iobuf;
X	h->toytime = toytime() + SECOND(30);
X	h->time = time();
X	m = h->wsize;
X	if(l != m) {
X		print("wdev changed size %ld to %ld\n", m, l);
X		h->wsize = l;
X		cb->flags |= Bmod;
X	}

X	for(m=0; m<h->msize; m++) {
X		p = getbuf(cw->cdev, h->maddr + m/BKPERBLK, Bread);
X		if(!p || checktag(p, Tbuck, h->maddr + m/BKPERBLK))
X			panic("cwinit: checktag c bucket");
X		putbuf(p);
X	}
X	putbuf(cb);
X}

long
cwsaddr(Device *dev)
X{
X	Iobuf *cb;
X	long sa;

X	cb = getbuf(CDEV(dev), CACHE_ADDR, Bread|Bres);
X	sa = ((Cache*)cb->iobuf)->sbaddr;
X	putbuf(cb);
X	return sa;
X}

long
cwraddr(Device *dev)
X{
X	Iobuf *cb;
X	long ra;

X	switch(dev->type) {
X	default:
X		print("unknown dev in cwraddr %Z\n", dev);
X		return 1;

X	case Devcw:
X		cb = getbuf(CDEV(dev), CACHE_ADDR, Bread|Bres);
X		ra = ((Cache*)cb->iobuf)->cwraddr;
X		break;

X	case Devro:
X		cb = getbuf(CDEV(dev->ro.parent), CACHE_ADDR, Bread|Bres);
X		ra = ((Cache*)cb->iobuf)->roraddr;
X		break;
X	}
X	putbuf(cb);
X	return ra;
X}

long
cwsize(Device *dev)
X{
X	Iobuf *cb;
X	long fs;

X	cb = getbuf(CDEV(dev), CACHE_ADDR, Bread|Bres);
X	fs = ((Cache*)cb->iobuf)->fsize;
X	putbuf(cb);
X	return fs;
X}

int
cwread(Device *dev, long b, void *c)
X{
X	return cwio(dev, b, c, Oread) == Cerror;
X}

int
cwwrite(Device *dev, long b, void *c)
X{
X	return cwio(dev, b, c, Owrite) == Cerror;
X}

int
roread(Device *dev, long b, void *c)
X{
X	Device *d;
X	int s;

X	/*
X	 * maybe better is to try buffer pool first
X	 */
X	d = dev->ro.parent;
X	if(d == 0 || d->type != Devcw ||
X	   d->private == 0 || RDEV(d) != dev) {
X		print("bad rodev %Z\n", dev);
X		return 1;
X	}
X	s = cwio(d, b, 0, Onone);
X	if(s == Cdump || s == Cdump1 || s == Cread) {
X		s = cwio(d, b, c, Oread);
X		if(s == Cdump || s == Cdump1 || s == Cread) {
X			if(cons.flags & roflag)
X				print("roread: %Z %ld -> %Z(hit)\n", dev, b, d);
X			return 0;
X		}
X	}
X	if(cons.flags & roflag)
X		print("roread: %Z %ld -> %Z(miss)\n", dev, b, WDEV(d));
X	return devread(WDEV(d), b, c);
X}

int
cwio(Device *dev, long addr, void *buf, int opcode)
X{
X	Iobuf *p, *p1, *p2, *cb;
X	Cache *h;
X	Bucket *b;
X	Centry *c;
X	long bn, a1, a2, max, newmax;
X	int state;
X	Cw *cw;

X	cw = dev->private;
X	cw->ncwio[0].count++;
X	cw->ncwio[1].count++;
X	cw->ncwio[2].count++;

X	cb = getbuf(cw->cdev, CACHE_ADDR, Bread|Bres);
X	h = (Cache*)cb->iobuf;
X	if(toytime() >= h->toytime) {
X		cb->flags |= Bmod;
X		h->toytime = toytime() + SECOND(30);
X		h->time = time();
X	}

X	if(addr < 0) {
X		putbuf(cb);
X		return Cerror;
X	}

X	bn = addr % h->msize;
X	a1 = h->maddr + bn/BKPERBLK;
X	a2 = bn*CEPERBK + h->caddr;
X	max = h->wmax;

X	putbuf(cb);
X	newmax = 0;

X	p = getbuf(cw->cdev, a1, Bread|Bmod);
X	if(!p || checktag(p, Tbuck, a1))
X		panic("cwio: checktag c bucket");
X	b = (Bucket*)p->iobuf + bn%BKPERBLK;

X	c = getcentry(b, addr);
X	if(c == 0) {
X		putbuf(p);
X		print("disk cache bucket %ld is full\n", a1);
X		return Cerror;
X	}
X	a2 += c - b->entry;

X	state = c->state;
X	switch(opcode)
X	{
X	default:
X		goto bad;

X	case Onone:
X		break;

X	case Oread:
X		switch(state) {
X		default:
X			goto bad;

X		case Cread:
X			if(!devread(cw->cdev, a2, buf))
X				break;
X			c->state = Cnone;

X		case Cnone:
X			if(devread(cw->wdev, addr, buf)) {
X				state = Cerror;
X				break;
X			}
X			if(addr > max)
X				newmax = addr;
X			if(!devwrite(cw->cdev, a2, buf))
X				c->state = Cread;
X			break;

X		case Cdirty:
X		case Cdump:
X		case Cdump1:
X		case Cwrite:
X			if(devread(cw->cdev, a2, buf))
X				state = Cerror;
X			break;
X		}
X		break;

X	case Owrite:
X		switch(state) {
X		default:
X			goto bad;

X		case Cdump:
X		case Cdump1:
X			/*
X			 * this is hard part -- a dump block must be
X			 * sent to the worm if it is rewritten.
X			 * if this causes an error, there is no
X			 * place to save the dump1 data. the block
X			 * is just reclassified as 'dump1' (botch)
X			 */
X			p1 = getbuf(devnone, Cwio1, 0);
X			if(devread(cw->cdev, a2, p1->iobuf)) {
X				putbuf(p1);
X				print("cwio: write induced dump error - r cache\n");

X			casenone:
X				if(devwrite(cw->cdev, a2, buf)) {
X					state = Cerror;
X					break;
X				}
X				c->state = Cdump1;
X				break;
X			}
X			if(devwrite(cw->wdev, addr, p1->iobuf)) {
X				p2 = getbuf(devnone, Cwio2, 0);
X				if(devread(cw->wdev, addr, p2->iobuf)) {
X					putbuf(p1);
X					putbuf(p2);
X					print("cwio: write induced dump error - r+w worm\n");
X					goto casenone;
X				}
X				if(memcmp(p1->iobuf, p2->iobuf, RBUFSIZE)) {
X					putbuf(p1);
X					putbuf(p2);
X					print("cwio: write induced dump error - w worm\n");
X					goto casenone;
X				}
X				putbuf(p2);
X			}
X			putbuf(p1);
X			c->state = Cread;
X			if(addr > max)
X				newmax = addr;
X			cw->ncopy++;

X		case Cnone:
X		case Cread:
X			if(devwrite(cw->cdev, a2, buf)) {
X				state = Cerror;
X				break;
X			}
X			c->state = Cwrite;
X			break;

X		case Cdirty:
X		case Cwrite:
X			if(devwrite(cw->cdev, a2, buf))
X				state = Cerror;
X			break;
X		}
X		break;

X	case Ogrow:
X		if(state != Cnone) {
X			print("cwgrow with state = %s\n",
X				cwnames[state]);
X			break;
X		}
X		c->state = Cdirty;
X		break;

X	case Odump:
X		if(state != Cdirty) {	/* BOTCH */
X			print("cwdump with state = %s\n",
X				cwnames[state]);
X			break;
X		}
X		c->state = Cdump;
X		cw->ndump++;	/* only called from dump command */
X		break;

X	case Orele:
X		if(state != Cwrite) {
X			if(state != Cdump1)
X				print("cwrele with state = %s\n",
X					cwnames[state]);
X			break;
X		}
X		c->state = Cnone;
X		break;

X	case Ofree:
X		if(state == Cwrite || state == Cread)
X			c->state = Cnone;
X		break;
X	}
X	if(DEBUG)
X		print("cwio: %ld s=%s o=%s ns=%s\n",
X			addr, cwnames[state],
X			cwnames[opcode],
X			cwnames[c->state]);
X	putbuf(p);
X	if(newmax) {
X		cb = getbuf(cw->cdev, CACHE_ADDR, Bread|Bmod|Bres);
X		h = (Cache*)cb->iobuf;
X		if(newmax > h->wmax)
X			h->wmax = newmax;
X		putbuf(cb);
X	}
X	return state;

bad:
X	print("cw state = %s; cw opcode = %s",
X		cwnames[state], cwnames[opcode]);
X	return Cerror;
X}

extern Filsys* dev2fs(Device *dev);

int
cwgrow(Device *dev, Superb *sb, int uid)
X{
X	char str[NAMELEN];
X	Iobuf *cb;
X	Cache *h;
X	Filsys *filsys;
X	long fs, nfs, ws;

X	cb = getbuf(CDEV(dev), CACHE_ADDR, Bread|Bmod|Bres);
X	h = (Cache*)cb->iobuf;
X	ws = h->wsize;
X	fs = h->fsize;
X	if(fs >= ws)
X		return 0;
X	nfs = fs + ADDFREE;
X	if(nfs >= ws)
X		nfs = ws;
X	h->fsize = nfs;
X	putbuf(cb);

X	sb->fsize = nfs;
X	filsys = dev2fs(dev);
X	if (filsys == nil)
X		print("%Z", dev);
X	else
X		print("%s", filsys->name);
X	uidtostr(str, uid, 1);
X	print(" grow from %ld to %ld limit %ld by %s uid=%d\n",
X		fs, nfs, ws, str, uid);
X	for(nfs--; nfs>=fs; nfs--) {
X		switch(cwio(dev, nfs, 0, Ogrow)) {
X		case Cerror:
X			return 0;
X		case Cnone:
X			addfree(dev, nfs, sb);
X		}
X	}
X	return 1;
X}

int
cwfree(Device *dev, long addr)
X{
X	int state;

X	if(dev->type == Devcw) {
X		state = cwio(dev, addr, 0, Ofree);
X		if(state != Cdirty)
X			return 1;	/* do not put in freelist */
X	}
X	return 0;			/* put in freelist */
X}

int
bktcheck(Bucket *b)
X{
X	Centry *c, *c1, *c2, *ce;
X	int err;

X	err = 0;
X	if(b->agegen < CEPERBK || b->agegen > MAXAGE) {
X		print("agegen %ld\n", b->agegen);
X		err = 1;
X	}

X	ce = b->entry + CEPERBK;
X	c1 = 0;		/* lowest age last pass */
X	for(;;) {
X		c2 = 0;		/* lowest age this pass */
X		for(c = b->entry; c < ce; c++) {
X			if(c1 != 0 && c != c1) {
X				if(c->age == c1->age) {
X					print("same age %d\n", c->age);
X					err = 1;
X				}
X				if(c1->waddr == c->waddr)
X				if(c1->state != Cnone)
X				if(c->state != Cnone) {
X					print("same waddr %ld\n", c->waddr);
X					err = 1;
X				}
X			}
X			if(c1 != 0 && c->age <= c1->age)
X				continue;
X			if(c2 == 0 || c->age < c2->age)
X				c2 = c;
X		}
X		if(c2 == 0)
X			break;
X		c1 = c2;
X		if(c1->age >= b->agegen) {
X			print("age >= generator %d %ld\n", c1->age, b->agegen);
X			err = 1;
X		}
X	}
X	return err;
X}

void
resequence(Bucket *b)
X{
X	Centry *c, *ce, *cr;
X	int age, i;

X	ce = b->entry + CEPERBK;
X	for(c = b->entry; c < ce; c++) {
X		c->age += CEPERBK;
X		if(c->age < CEPERBK)
X			c->age = MAXAGE;
X	}
X	b->agegen += CEPERBK;

X	age = 0;
X	for(i=0;; i++) {
X		cr = 0;
X		for(c = b->entry; c < ce; c++) {
X			if(c->age < i)
X				continue;
X			if(cr == 0 || c->age < age) {
X				cr = c;
X				age = c->age;
X			}
X		}
X		if(cr == 0)
X			break;
X		cr->age = i;
X	}
X	b->agegen = i;
X	cons.nreseq++;
X}

Centry*
getcentry(Bucket *b, long addr)
X{
X	Centry *c, *ce, *cr;
X	int s, age;

X	/*
X	 * search for cache hit
X	 * find oldest block as byproduct
X	 */
X	ce = b->entry + CEPERBK;
X	age = 0;
X	cr = 0;
X	for(c = b->entry; c < ce; c++) {
X		s = c->state;
X		if(s == Cnone) {
X			cr = c;
X			age = 0;
X			continue;
X		}
X		if(c->waddr == addr)
X			goto found;
X		if(s == Cread) {
X			if(cr == 0 || c->age < age) {
X				cr = c;
X				age = c->age;
X			}
X		}
X	}

X	/*
X	 * remap entry
X	 */
X	c = cr;
X	if(c == 0)
X		return 0;	/* bucket is full */

X	c->state = Cnone;
X	c->waddr = addr;

found:
X	/*
X	 * update the age to get filo cache.
X	 * small number in age means old
X	 */
X	if(!cons.noage || c->state == Cnone) {
X		age = b->agegen;
X		c->age = age;
X		age++;
X		b->agegen = age;
X		if(age < 0 || age >= MAXAGE)
X			resequence(b);
X	}
X	return c;
X}

X/*
X * ream the cache
X * calculate new buckets
X */
Iobuf*
cacheinit(Device *dev)
X{
X	Iobuf *cb, *p;
X	Cache *h;
X	Device *cdev;
X	long m;

X	print("cache init %Z\n", dev);
X	cdev = CDEV(dev);
X	devinit(cdev);

X	cb = getbuf(cdev, CACHE_ADDR, Bmod|Bres);
X	memset(cb->iobuf, 0, RBUFSIZE);
X	settag(cb, Tcache, QPSUPER);
X	h = (Cache*)cb->iobuf;

X	/*
X	 * calculate csize such that
X	 * tsize = msize/BKPERBLK + csize and
X	 * msize = csize/CEPERBK
X	 */
X	h->maddr = CACHE_ADDR + 1;
X	m = devsize(cdev) - h->maddr;
X	h->csize = ((long long)(m-1) * CEPERBK*BKPERBLK) / (CEPERBK*BKPERBLK+1);
X	h->msize = h->csize/CEPERBK - 5;
X	while(!prime(h->msize))
X		h->msize--;
X	h->csize = h->msize*CEPERBK;
X	h->caddr = h->maddr + (h->msize+BKPERBLK-1)/BKPERBLK;
X	h->wsize = devsize(WDEV(dev));

X	if(h->msize <= 0)
X		panic("cache too small");
X	if(h->caddr + h->csize > m)
X		panic("cache size error");

X	/*
X	 * setup cache map
X	 */
X	for(m=h->maddr; m<h->caddr; m++) {
X		p = getbuf(cdev, m, Bmod);
X		memset(p->iobuf, 0, RBUFSIZE);
X		settag(p, Tbuck, m);
X		putbuf(p);
X	}
X	print("done cacheinit\n");
X	return cb;
X}

long
getstartsb(Device *dev)
X{
X	Filsys *f;
X	Startsb *s;

X	for(f=filsys; f->name; f++)
X		if(devcmpr(f->dev, dev) == 0)
X			goto found;
print("getstartsb: not found 1 %Z\n", dev);
X	return FIRST;

found:
X	for(s=startsb; s->name; s++)
X		if(strcmp(f->name, s->name) == 0)
X			return s->startsb;
print("getstartsb: not found 2 %Z %s\n", dev, f->name);
X	return FIRST;
X}

X/*
X * ream the cache
X * calculate new buckets
X * get superblock from
X * last worm dump block.
X */
void
cwrecover(Device *dev)
X{
X	Iobuf *p, *cb;
X	Cache *h;
X	Superb *s;
X	long m, baddr;
X	Device *wdev;

X	cwinit1(dev);
X	wdev = WDEV(dev);

X	p = getbuf(devnone, Cwxx1, 0);
X	s = (Superb*)p->iobuf;
X	baddr = 0;
X	m = getstartsb(dev);
X	localconfinit();
X	if(conf.firstsb)
X		m = conf.firstsb;
X	for(;;) {
X		memset(p->iobuf, 0, RBUFSIZE);
X		if(devread(wdev, m, p->iobuf) ||
X		   checktag(p, Tsuper, QPSUPER))
X			break;
X		baddr = m;
X		m = s->next;
X		print("dump %ld is good; %ld next\n", baddr, m);
X		if(baddr == conf.recovsb)
X			break;
X	}
X	putbuf(p);
X	if(!baddr)
X		panic("recover: no superblock\n");

X	p = getbuf(wdev, baddr, Bread);
X	s = (Superb*)p->iobuf;

X	cb = cacheinit(dev);
X	h = (Cache*)cb->iobuf;
X	h->sbaddr = baddr;
X	h->cwraddr = s->cwraddr;
X	h->roraddr = s->roraddr;
X	h->fsize = s->fsize + 100;		/* this must be conservative */
X	if(conf.recovcw)
X		h->cwraddr = conf.recovcw;
X	if(conf.recovro)
X		h->roraddr = conf.recovro;

X	putbuf(cb);
X	putbuf(p);

X	p = getbuf(dev, baddr, Bread|Bmod);
X	s = (Superb*)p->iobuf;

X	memset(&s->fbuf, 0, sizeof(s->fbuf));
X	s->fbuf.free[0] = 0;
X	s->fbuf.nfree = 1;
X	s->tfree = 0;
X	if(conf.recovcw)
X		s->cwraddr = conf.recovcw;
X	if(conf.recovro)
X		s->roraddr = conf.recovro;

X	putbuf(p);
X	print("done recover\n");
X}

X/*
X * ream the cache
X * calculate new buckets
X * initialize superblock.
X */
void
cwream(Device *dev)
X{
X	Iobuf *p, *cb;
X	Cache *h;
X	Superb *s;
X	long m, baddr;
X	Device *cdev;

X	print("cwream %Z\n", dev);
X	cwinit1(dev);
X	cdev = CDEV(dev);
X	devinit(cdev);

X	baddr = FIRST;	/*	baddr   = super addr
X				baddr+1 = cw root
X				baddr+2 = ro root
X				baddr+3 = reserved next superblock */

X	cb = cacheinit(dev);
X	h = (Cache*)cb->iobuf;

X	h->sbaddr = baddr;
X	h->cwraddr = baddr+1;
X	h->roraddr = baddr+2;
X	h->fsize = 0;	/* prevents superream from freeing */

X	putbuf(cb);

X	for(m=0; m<3; m++)
X		cwio(dev, baddr+m, 0, Ogrow);
X	superream(dev, baddr);
X	rootream(dev, baddr+1);			/* cw root */
X	rootream(dev, baddr+2);			/* ro root */

X	cb = getbuf(cdev, CACHE_ADDR, Bread|Bmod|Bres);
X	h = (Cache*)cb->iobuf;
X	h->fsize = baddr+4;
X	putbuf(cb);

X	p = getbuf(dev, baddr, Bread|Bmod|Bimm);
X	s = (Superb*)p->iobuf;
X	s->last = baddr;
X	s->cwraddr = baddr+1;
X	s->roraddr = baddr+2;
X	s->next = baddr+3;
X	s->fsize = baddr+4;
X	putbuf(p);

X	for(m=0; m<3; m++)
X		cwio(dev, baddr+m, 0, Odump);
X}

long
rewalk1(Cw *cw, long addr, int slot, Wpath *up)
X{
X	Iobuf *p, *p1;
X	Dentry *d;

X	if(up == 0)
X		return cwraddr(cw->dev);
X	up->addr = rewalk1(cw, up->addr, up->slot, up->up);
X	p = getbuf(cw->dev, up->addr, Bread|Bmod);
X	d = getdir(p, up->slot);
X	if(!d || !(d->mode & DALLOC)) {
X		print("rewalk1 1\n");
X		if(p)
X			putbuf(p);
X		return addr;
X	}
X	p1 = dnodebuf(p, d, slot/DIRPERBUF, 0, 0);
X	if(!p1) {
X		print("rewalk1 2\n");
X		if(p)
X			putbuf(p);
X		return addr;
X	}
X	if(DEBUG)
X		print("rewalk1 %ld to %ld \"%s\"\n",
X			addr, p1->addr, d->name);
X	addr = p1->addr;
X	p1->flags |= Bmod;
X	putbuf(p1);
X	putbuf(p);
X	return addr;
X}

long
rewalk2(Cw *cw, long addr, int slot, Wpath *up)
X{
X	Iobuf *p, *p1;
X	Dentry *d;

X	if(up == 0)
X		return cwraddr(cw->rodev);
X	up->addr = rewalk2(cw, up->addr, up->slot, up->up);
X	p = getbuf(cw->rodev, up->addr, Bread);
X	d = getdir(p, up->slot);
X	if(!d || !(d->mode & DALLOC)) {
X		print("rewalk2 1\n");
X		if(p)
X			putbuf(p);
X		return addr;
X	}
X	p1 = dnodebuf(p, d, slot/DIRPERBUF, 0, 0);
X	if(!p1) {
X		print("rewalk2 2\n");
X		if(p)
X			putbuf(p);
X		return addr;
X	}
X	if(DEBUG)
X		print("rewalk2 %ld to %ld \"%s\"\n",
X			addr, p1->addr, d->name);
X	addr = p1->addr;
X	putbuf(p1);
X	putbuf(p);
X	return addr;
X}

void
rewalk(Cw *cw)
X{
X	int h;
X	File *f;

X	for(h=0; h<nelem(flist); h++) {
X		for(f=flist[h]; f; f=f->next) {
X			if(!f->fs)
X				continue;
X			if(cw->dev == f->fs->dev)
X				f->addr = rewalk1(cw, f->addr, f->slot, f->wpath);
X			else
X			if(cw->rodev == f->fs->dev)
X				f->addr = rewalk2(cw, f->addr, f->slot, f->wpath);
X		}
X	}
X}

long
split(Cw *cw, Iobuf *p, long addr)
X{
X	long na;
X	int state;

X	na = 0;
X	if(p && (p->flags & Bmod)) {
X		p->flags |= Bimm;
X		putbuf(p);
X		p = 0;
X	}
X	state = cwio(cw->dev, addr, 0, Onone);	/* read the state (twice?) */
X	switch(state)
X	{
X	default:
X		panic("split: unknown state %s", cwnames[state]);

X	case Cerror:
X	case Cnone:
X	case Cdump:
X	case Cread:
X		break;

X	case Cdump1:
X	case Cwrite:
X		/*
X		 * botch.. could be done by relabeling
X		 */
X		if(!p) {
X			p = getbuf(cw->dev, addr, Bread);
X			if(!p) {
X				print("split: null getbuf\n");
X				break;
X			}
X		}
X		na = cw->fsize;
X		cw->fsize = na+1;
X		cwio(cw->dev, na, 0, Ogrow);
X		cwio(cw->dev, na, p->iobuf, Owrite);
X		cwio(cw->dev, na, 0, Odump);
X		cwio(cw->dev, addr, 0, Orele);
X		break;

X	case Cdirty:
X		cwio(cw->dev, addr, 0, Odump);
X		break;
X	}
X	if(p)
X		putbuf(p);
X	return na;
X}

int
isdirty(Cw *cw, Iobuf *p, long addr, int tag)
X{
X	int s;

X	if(p && (p->flags & Bmod))
X		return 1;
X	s = cwio(cw->dev, addr, 0, Onone);
X	if(s == Cdirty || s == Cwrite)
X		return 1;
X	if(tag == Tind1 || tag == Tind2)	/* botch, get these modified */
X		if(s != Cnone)
X			return 1;
X	return 0;
X}

long
cwrecur(Cw *cw, long addr, int tag, int tag1, long qp)
X{
X	Iobuf *p;
X	Dentry *d;
X	int i, j, shouldstop;
X	long na;
X	char *np;


X	shouldstop = 0;
X	p = getbuf(cw->dev, addr, Bprobe);
X	if(!isdirty(cw, p, addr, tag)) {
X		if(!cw->all) {
X			if(DEBUG)
X				print("cwrecur: %ld t=%s not dirty %s\n",
X					addr, tagnames[tag], cw->name);
X			if(p)
X				putbuf(p);
X			return 0;
X		}
X		shouldstop = 1;
X	}
X	if(DEBUG)
X		print("cwrecur: %ld t=%s %s\n",
X			addr, tagnames[tag], cw->name);
X	if(cw->depth >= 100) {
X		print("dump depth too great %s\n", cw->name);
X		if(p)
X			putbuf(p);
X		return 0;
X	}
X	cw->depth++;

X	switch(tag)
X	{
X	default:
X		print("cwrecur: unknown tag %d %s\n", tag, cw->name);

X	case Tfile:
X		break;

X	case Tsuper:
X	case Tdir:
X		if(!p) {
X			p = getbuf(cw->dev, addr, Bread);
X			if(!p) {
X				print("cwrecur: Tdir p null %s\n",
X					cw->name);
X				break;
X			}
X		}
X		if(tag == Tdir) {
X			cw->namepad[0] = 0;	/* force room */
X			np = strchr(cw->name, 0);
X			*np++ = '/';
X		} else {
X			np = 0;	/* set */
X			cw->name[0] = 0;
X		}

X		for(i=0; i<DIRPERBUF; i++) {
X			d = getdir(p, i);
X			if(!(d->mode & DALLOC))
X				continue;
X			qp = d->qid.path & ~QPDIR;
X			if(tag == Tdir)
X				strncpy(np, d->name, NAMELEN);
X			else
X			if(i > 0)
X				print("cwrecur: root with >1 directory\n");
X			tag1 = Tfile;
X			if(d->mode & DDIR)
X				tag1 = Tdir;
X			for(j=0; j<NDBLOCK; j++) {
X				if(na = d->dblock[j]) {
X					na = cwrecur(cw, na, tag1, 0, qp);
X					if(na) {
X						d->dblock[j] = na;
X						p->flags |= Bmod;
X					}
X				}
X			}
X			if(na = d->iblock) {
X				na = cwrecur(cw, na, Tind1, tag1, qp);
X				if(na) {
X					d->iblock = na;
X					p->flags |= Bmod;
X				}
X			}
X			if(na = d->diblock) {
X				na = cwrecur(cw, na, Tind2, tag1, qp);
X				if(na) {
X					d->diblock = na;
X					p->flags |= Bmod;
X				}
X			}
X		}
X		break;

X	case Tind1:
X		j = tag1;
X		tag1 = 0;
X		goto tind;

X	case Tind2:
X		j = Tind1;

X	tind:
X		if(!p) {
X			p = getbuf(cw->dev, addr, Bread);
X			if(!p) {
X				print("cwrecur: Tind p null %s\n",
X					cw->name);
X				break;
X			}
X		}
X		for(i=0; i<INDPERBUF; i++) {
X			if(na = ((long*)p->iobuf)[i]) {
X				na = cwrecur(cw, na, j, tag1, qp);
X				if(na) {
X					((long*)p->iobuf)[i] = na;
X					p->flags |= Bmod;
X				}
X			}
X		}
X		break;
X	}
X	na = split(cw, p, addr);
X	cw->depth--;
X	if(na && shouldstop) {
X		if(cw->falsehits < 10)
X			print("shouldstop %ld %ld t=%s %s\n",
X				addr, na, tagnames[tag], cw->name);
X		cw->falsehits++;
X	}
X	return na;
X}

void
cfsdump(Filsys *fs)
X{
X	Iobuf *pr, *p1, *p;
X	Dentry *dr, *d1, *d;
X	Cache *h;
X	Superb *s;
X	long orba, rba, oroa, roa, sba, a, m, n, i, tim;
X	char tstr[20];
X	Cw *cw;

X	if(fs->dev->type != Devcw) {
X		print("cant dump; not cw device: %Z\n", fs->dev);
X		return;
X	}
X	cw = fs->dev->private;
X	if(cw == 0) {
X		print("cant dump: has not been inited: %Z\n", fs->dev);
X		return;
X	}

X	tim = toytime();
X	wlock(&mainlock);		/* dump */

X	/*
X	 * set up static structure
X	 * with frequent variables
X	 */
X	cw->ndump = 0;
X	cw->name[0] = 0;
X	cw->depth = 0;

X	/*
X	 * cw root
X	 */
X	sync("before dump");
X	cw->fsize = cwsize(cw->dev);
X	orba = cwraddr(cw->dev);
X	print("cwroot %ld", orba);
X	cons.noage = 1;
X	cw->all = cw->allflag;
X	rba = cwrecur(cw, orba, Tsuper, 0, QPROOT);
X	if(rba == 0)
X		rba = orba;
X	print("->%ld\n", rba);
X	sync("after cw");

X	/*
X	 * partial super block
X	 */
X	p = getbuf(cw->dev, cwsaddr(cw->dev), Bread|Bmod|Bimm);
X	s = (Superb*)p->iobuf;
X	s->fsize = cw->fsize;
X	s->cwraddr = rba;
X	putbuf(p);

X	/*
X	 * partial cache block
X	 */
X	p = getbuf(cw->cdev, CACHE_ADDR, Bread|Bmod|Bimm|Bres);
X	h = (Cache*)p->iobuf;
X	h->fsize = cw->fsize;
X	h->cwraddr = rba;
X	putbuf(p);

X	/*
X	 * ro root
X	 */
X	oroa = cwraddr(cw->rodev);
X	pr = getbuf(cw->dev, oroa, Bread|Bmod);
X	dr = getdir(pr, 0);

X	datestr(tstr, time());	/* tstr = "yyyymmdd" */
X	n = 0;
X	for(a=0;; a++) {
X		p1 = dnodebuf(pr, dr, a, Tdir, 0);
X		if(!p1)
X			goto bad;
X		n++;
X		for(i=0; i<DIRPERBUF; i++) {
X			d1 = getdir(p1, i);
X			if(!d1)
X				goto bad;
X			if(!(d1->mode & DALLOC))
X				goto found1;
X			if(!memcmp(d1->name, tstr, 4))
X				goto found2;	/* found entry */
X		}
X		putbuf(p1);
X	}

X	/*
X	 * no year directory, create one
X	 */
found1:
X	p = getbuf(cw->dev, rba, Bread);
X	d = getdir(p, 0);
X	d1->qid = d->qid;
X	d1->qid.version += n;
X	memmove(d1->name, tstr, 4);
X	d1->mode = d->mode;
X	d1->uid = d->uid;
X	d1->gid = d->gid;
X	putbuf(p);
X	accessdir(p1, d1, FWRITE, 0);

X	/*
X	 * put mmdd[count] in year directory
X	 */
found2:
X	accessdir(p1, d1, FREAD, 0);
X	putbuf(pr);
X	pr = p1;
X	dr = d1;

X	n = 0;
X	m = 0;
X	for(a=0;; a++) {
X		p1 = dnodebuf(pr, dr, a, Tdir, 0);
X		if(!p1)
X			goto bad;
X		n++;
X		for(i=0; i<DIRPERBUF; i++) {
X			d1 = getdir(p1, i);
X			if(!d1)
X				goto bad;
X			if(!(d1->mode & DALLOC))
X				goto found;
X			if(!memcmp(d1->name, tstr+4, 4))
X				m++;
X		}
X		putbuf(p1);
X	}

X	/*
X	 * empty slot put in root
X	 */
found:
X	if(m)	/* how many dumps this date */
X		sprint(tstr+8, "%ld", m);

X	p = getbuf(cw->dev, rba, Bread);
X	d = getdir(p, 0);
X	*d1 = *d;				/* qid is QPROOT */
X	putbuf(p);
X	strcpy(d1->name, tstr+4);
X	d1->qid.version += n;
X	accessdir(p1, d1, FWRITE, 0);
X	putbuf(p1);
X	putbuf(pr);

X	cw->fsize = cwsize(cw->dev);
X	oroa = cwraddr(cw->rodev);		/* probably redundant */
X	print("roroot %ld", oroa);

X	cons.noage = 0;
X	cw->all = 0;
X	roa = cwrecur(cw, oroa, Tsuper, 0, QPROOT);
X	if(roa == 0) {
X		print("[same]");
X		roa = oroa;
X	}
X	print("->%ld /%.4s/%s\n", roa, tstr, tstr+4);
X	sync("after ro");

X	/*
X	 * final super block
X	 */
X	a = cwsaddr(cw->dev);
X	print("sblock %ld", a);
X	p = getbuf(cw->dev, a, Bread|Bmod|Bimm);
X	s = (Superb*)p->iobuf;
X	s->last = a;
X	sba = s->next;
X	s->next = cw->fsize;
X	cw->fsize++;
X	s->fsize = cw->fsize;
X	s->roraddr = roa;

X	cwio(cw->dev, sba, 0, Ogrow);
X	cwio(cw->dev, sba, p->iobuf, Owrite);
X	cwio(cw->dev, sba, 0, Odump);
X	print("->%ld (->%ld)\n", sba, s->next);

X	putbuf(p);

X	/*
X	 * final cache block
X	 */
X	p = getbuf(cw->cdev, CACHE_ADDR, Bread|Bmod|Bimm|Bres);
X	h = (Cache*)p->iobuf;
X	h->fsize = cw->fsize;
X	h->roraddr = roa;
X	h->sbaddr = sba;
X	putbuf(p);

X	rewalk(cw);
X	sync("all done");

X	print("%ld blocks queued for worm\n", cw->ndump);
X	print("%ld falsehits\n", cw->falsehits);
X	cw->nodump = 0;

X	/*
X	 * extend all of the locks
X	 */
X	tim = toytime() - tim;
X	for(i=0; i<NTLOCK; i++)
X		if(tlocks[i].time > 0)
X			tlocks[i].time += tim;

X	wunlock(&mainlock);
X	return;

bad:
X	panic("dump: bad");
X}

void
mvstates(Device *dev, int s1, int s2, int side)
X{
X	Iobuf *p, *cb;
X	Cache *h;
X	Bucket *b;
X	Centry *c, *ce;
X	long m, lo, hi, msize, maddr;
X	Cw *cw;

X	cw = dev->private;
X	lo = 0;
X	hi = lo + devsize(dev->cw.w);	/* size of all sides totalled */
X	if(side >= 0) {
X		/* operate on only a single disc side */
X		Sidestarts ss;

X		wormsidestarts(dev, side, &ss);
X		lo = ss.sstart;
X		hi = ss.s1start;
X	}
X	cb = getbuf(cw->cdev, CACHE_ADDR, Bread|Bres);
X	if(!cb || checktag(cb, Tcache, QPSUPER))
X		panic("cwstats: checktag c bucket");
X	h = (Cache*)cb->iobuf;
X	msize = h->msize;
X	maddr = h->maddr;
X	putbuf(cb);

X	for(m=0; m<msize; m++) {
X		p = getbuf(cw->cdev, maddr + m/BKPERBLK, Bread|Bmod);
X		if(!p || checktag(p, Tbuck, maddr + m/BKPERBLK))
X			panic("cwtest: checktag c bucket");
X		b = (Bucket*)p->iobuf + m%BKPERBLK;
X		ce = b->entry + CEPERBK;
X		for(c=b->entry; c<ce; c++)
X			if(c->state == s1 && c->waddr >= lo && c->waddr < hi)
X				c->state = s2;
X		putbuf(p);
X	}
X}

void
prchain(Device *dev, long m, int flg)
X{
X	Iobuf *p;
X	Superb *s;

X	if(m == 0) {
X		if(flg)
X			m = cwsaddr(dev);
X		else
X			m = getstartsb(dev);
X	}
X	p = getbuf(devnone, Cwxx2, 0);
X	s = (Superb*)p->iobuf;
X	for(;;) {
X		memset(p->iobuf, 0, RBUFSIZE);
X		if(devread(WDEV(dev), m, p->iobuf) ||
X		   checktag(p, Tsuper, QPSUPER))
X			break;
X		if(flg) {
X			print("dump %ld is good; %ld prev\n", m, s->last);
X			print("\t%ld cwroot; %ld roroot\n", s->cwraddr, s->roraddr);
X			if(m <= s->last)
X				break;
X			m = s->last;
X		} else {
X			print("dump %ld is good; %ld next\n", m, s->next);
X			print("\t%ld cwroot; %ld roroot\n", s->cwraddr, s->roraddr);
X			if(m >= s->next)
X				break;
X			m = s->next;
X		}
X	}
X	putbuf(p);
X}

void
touchsb(Device *dev)
X{
X	Iobuf *p;
X	long m;

X	m = cwsaddr(dev);
X	p = getbuf(devnone, Cwxx2, 0);

X	memset(p->iobuf, 0, RBUFSIZE);
X	if(devread(WDEV(dev), m, p->iobuf) ||
X	   checktag(p, Tsuper, QPSUPER))
X		print("WORM SUPER BLOCK READ FAILED\n");
X	else
X		print("touch superblock %ld\n", m);
X	putbuf(p);
X}

void
storesb(Device *dev, long last, int doit)
X{
X	Iobuf *ph, *ps;
X	Cache *h;
X	Superb *s;
X	long sbaddr, qidgen;

X	sbaddr = cwsaddr(dev);

X	ps = getbuf(devnone, Cwxx2, 0);
X	if(!ps) {
X		print("sbstore: getbuf\n");
X		return;
X	}

X	/*
X	 * try to read last sb
X	 */
X	memset(ps->iobuf, 0, RBUFSIZE);
X	if(devread(WDEV(dev), last, ps->iobuf) ||
X	   checktag(ps, Tsuper, QPSUPER))
X		print("read last failed\n");
X	else
X		print("read last succeeded\n");

X	s = (Superb*)ps->iobuf;
X	qidgen = s->qidgen;
X	if(qidgen == 0)
X		qidgen = 0x31415;
X	qidgen += 1000;
X	if(s->next != sbaddr)
X		print("next(last) is not sbaddr %ld %ld\n",
X			s->next, sbaddr);
X	else
X		print("next(last) is sbaddr\n");

X	/*
X	 * read cached superblock
X	 */
X	ph = getbuf(CDEV(dev), CACHE_ADDR, Bread|Bres);
X	if(!ph || checktag(ph, Tcache, QPSUPER)) {
X		print("cwstats: checktag c bucket\n");
X		if(ph)
X			putbuf(ph);
X		putbuf(ps);
X		return;
X	} else
X		print("read cached sb succeeded\n");
X
X	h = (Cache*)ph->iobuf;

X	memset(ps->iobuf, 0, RBUFSIZE);
X	settag(ps, Tsuper, QPSUPER);
X	ps->flags = 0;
X	s = (Superb*)ps->iobuf;

X	s->cwraddr = h->cwraddr;
X	s->roraddr = h->roraddr;
X	s->fsize = h->fsize;
X	s->fstart = 2;
X	s->last = last;
X	s->next = h->roraddr+1;

X	s->qidgen = qidgen;
X	putbuf(ph);

X	if(s->fsize-1 != s->next ||
X	   s->fsize-2 != s->roraddr ||
X	   s->fsize-5 != s->cwraddr) {
X		print("addrs not in relationship %ld %ld %ld %ld\n",
X			s->cwraddr, s->roraddr, s->next, s->fsize);
X		putbuf(ps);
X		return;
X	} else
X		print("addresses in relation\n");

X	if(doit)
X	if(devwrite(WDEV(dev), sbaddr, ps->iobuf))
X		print("WORM SUPER BLOCK WRITE FAILED\n");
X	ps->flags = 0;
X	putbuf(ps);
X}

void
savecache(Device *dev)
X{
X	Iobuf *p, *cb;
X	Cache *h;
X	Bucket *b;
X	Centry *c, *ce;
X	long m, n, maddr, msize, left, *longp, nbyte;
X	Device *cdev;

X	if(walkto("/adm/cache"))
X		goto bad;
X	if(con_open(FID2, OWRITE|OTRUNC))
X		goto bad;

X	cdev = CDEV(dev);
X	cb = getbuf(cdev, CACHE_ADDR, Bread|Bres);
X	if(!cb || checktag(cb, Tcache, QPSUPER))
X		panic("savecache: checktag c bucket");
X	h = (Cache*)cb->iobuf;
X	msize = h->msize;
X	maddr = h->maddr;
X	putbuf(cb);

X	n = BUFSIZE;			/* calculate write size */
X	if(n > MAXDAT)
X		n = MAXDAT;

X	cb = getbuf(devnone, Cwxx4, 0);
X	longp = (long*)cb->iobuf;
X	left = n/sizeof(long);
X	cons.offset = 0;

X	for(m=0; m<msize; m++) {
X		if(left < BKPERBLK) {
X			nbyte = (n/sizeof(long) - left) * sizeof(long);
X			con_write(FID2, cb->iobuf, cons.offset, nbyte);
X			cons.offset += nbyte;
X			longp = (long*)cb->iobuf;
X			left = n/sizeof(long);
X		}
X		p = getbuf(cdev, maddr + m/BKPERBLK, Bread);
X		if(!p || checktag(p, Tbuck, maddr + m/BKPERBLK))
X			panic("cwtest: checktag c bucket");
X		b = (Bucket*)p->iobuf + m%BKPERBLK;
X		ce = b->entry + CEPERBK;
X		for(c=b->entry; c<ce; c++)
X			if(c->state == Cread) {
X				*longp++ = c->waddr;
X				left--;
X			}
X		putbuf(p);
X	}
X	nbyte = (n/sizeof(long) - left) * sizeof(long);
X	con_write(FID2, cb->iobuf, cons.offset, nbyte);
X	putbuf(cb);
X	return;

bad:
X	print("cant open /adm/cache\n");
X}

void
loadcache(Device *dev, int dskno)
X{
X	Iobuf *p, *cb;
X	long m, nbyte, *longp, count;
X	Sidestarts ss;

X	if(walkto("/adm/cache"))
X		goto bad;
X	if(con_open(FID2, OREAD))
X		goto bad;

X	cb = getbuf(devnone, Cwxx4, 0);
X	cons.offset = 0;
X	count = 0;

X	if (dskno >= 0)
X		wormsidestarts(dev, dskno, &ss);
X	for(;;) {
X		memset(cb->iobuf, 0, BUFSIZE);
X		nbyte = con_read(FID2, cb->iobuf, cons.offset, 100) / sizeof(long);
X		if(nbyte <= 0)
X			break;
X		cons.offset += nbyte * sizeof(long);
X		longp = (long*)cb->iobuf;
X		while(nbyte > 0) {
X			m = *longp++;
X			nbyte--;
X			if(m == 0)
X				continue;
X			/* if given a diskno, restrict to just that disc side */
X			if(dskno < 0 || m >= ss.sstart && m < ss.s1start) {
X				p = getbuf(dev, m, Bread);
X				if(p)
X					putbuf(p);
X				count++;
X			}
X		}
X	}
X	putbuf(cb);
X	print("%ld blocks loaded from worm %d\n", count, dskno);
X	return;

bad:
X	print("cant open /adm/cache\n");
X}

void
morecache(Device *dev, int dskno, long size)
X{
X	Iobuf *p;
X	long m, ml, mh, mm, count;
X	Cache *h;
X	Sidestarts ss;

X	p = getbuf(CDEV(dev), CACHE_ADDR, Bread|Bres);
X	if(!p || checktag(p, Tcache, QPSUPER))
X		panic("savecache: checktag c bucket");
X	h = (Cache*)p->iobuf;
X	mm = h->wmax;
X	putbuf(p);

X	wormsidestarts(dev, dskno, &ss);
X	ml = ss.sstart;		/* start at beginning of disc side #dskno */
X	mh = ml + size;
X	if(mh > mm) {
X		mh = mm;
X		print("limited to %ld\n", mh-ml);
X	}

X	count = 0;
X	for(m=ml; m < mh; m++) {
X		p = getbuf(dev, m, Bread);
X		if(p)
X			putbuf(p);
X		count++;
X	}
X	print("%ld blocks loaded from worm %d\n", count, dskno);
X}

void
blockcmp(Device *dev, long wa, long ca)
X{
X	Iobuf *p1, *p2;
X	int i, c;

X	p1 = getbuf(WDEV(dev), wa, Bread);
X	if(!p1) {
X		print("dowcmp: wdev error\n");
X		return;
X	}

X	p2 = getbuf(CDEV(dev), ca, Bread);
X	if(!p2) {
X		print("dowcmp: cdev error\n");
X		putbuf(p1);
X		return;
X	}

X	c = 0;
X	for(i=0; i<RBUFSIZE; i++)
X		if(p1->iobuf[i] != p2->iobuf[i]) {
X			print("%4d: %.2x %.2x\n",
X				i,
X				p1->iobuf[i]&0xff,
X				p2->iobuf[i]&0xff);
X			c++;
X			if(c >= 10)
X				break;
X		}

X	if(c == 0)
X		print("no error\n");
X	putbuf(p1);
X	putbuf(p2);
X}

void
wblock(Device *dev, long addr)
X{
X	Iobuf *p1;
X	int i;

X	p1 = getbuf(dev, addr, Bread);
X	if(p1) {
X		i = devwrite(WDEV(dev), addr, p1->iobuf);
X		print("i = %d\n", i);
X		putbuf(p1);
X	}
X}

void
cwtest(Device*)
X{
X}

X#ifdef	XXX
X/* garbage to change sb size
X * probably will need it someday
X */
X	fsz = number(0, 0, 10);
X	count = 0;
X	if(fsz == number(0, -1, 10))
X		count = -1;		/* really do it */
X	print("fsize = %ld\n", fsz);
X	cdev = CDEV(dev);
X	cb = getbuf(cdev, CACHE_ADDR, Bread|Bres);
X	if(!cb || checktag(cb, Tcache, QPSUPER))
X		panic("cwstats: checktag c bucket");
X	h = (Cache*)cb->iobuf;
X	for(m=0; m<h->msize; m++) {
X		p = getbuf(cdev, h->maddr + m/BKPERBLK, Bread|Bmod);
X		if(!p || checktag(p, Tbuck, h->maddr + m/BKPERBLK))
X			panic("cwtest: checktag c bucket");
X		b = (Bucket*)p->iobuf + m%BKPERBLK;
X		ce = b->entry + CEPERBK;
X		for(c=b->entry; c<ce; c++) {
X			if(c->waddr < fsz)
X				continue;
X			if(count < 0) {
X				c->state = Cnone;
X				continue;
X			}
X			if(c->state != Cdirty)
X				count++;
X		}
X		putbuf(p);
X	}
X	if(count < 0) {
X		print("old cache hsize = %ld\n", h->fsize);
X		h->fsize = fsz;
X		cb->flags |= Bmod;
X		p = getbuf(dev, h->sbaddr, Bread|Bmod);
X		s = (Superb*)p->iobuf;
X		print("old super hsize = %ld\n", s->fsize);
X		s->fsize = fsz;
X		putbuf(p);
X	}
X	putbuf(cb);
X	print("count = %ld\n", count);
X#endif

int
convstate(char *name)
X{
X	int i;

X	for(i=0; i<nelem(cwnames); i++)
X		if(cwnames[i])
X			if(strcmp(cwnames[i], name) == 0)
X				return i;
X	return -1;
X}

void
searchtag(Device *d, long a, int tag, int n)
X{
X	Iobuf *p;
X	Tag *t;
X	int i;

X	if(a == 0)
X		a = getstartsb(d);
X	p = getbuf(devnone, Cwxx2, 0);
X	t = (Tag*)(p->iobuf+BUFSIZE);
X	for(i=0; i<n; i++) {
X		memset(p->iobuf, 0, RBUFSIZE);
X		if(devread(WDEV(d), a+i, p->iobuf)) {
X			if(n == 1000)
X				break;
X			continue;
X		}
X		if(t->tag == tag) {
X			print("tag %d found at %Z %ld\n", tag, d, a+i);
X			break;
X		}
X	}
X	putbuf(p);
X}

void
cmd_cwcmd(int argc, char *argv[])
X{
X	Device *dev;
X	char *arg;
X	long s1, s2, a, b, n;
X	Cw *cw;
X	char str[28];

X	if(argc <= 1) {
X		print("	cwcmd mvstate state1 state2 [platter]\n");
X		print("	cwcmd prchain [start] [bakflg]\n");
X		print("	cwcmd searchtag [start] [tag]\n");
X		print("	cwcmd touchsb\n");
X		print("	cwcmd savecache\n");
X		print("	cwcmd loadcache [dskno]\n");
X		print("	cwcmd morecache dskno [count]\n");
X		print("	cwcmd blockcmp wbno cbno\n");
X		print("	cwcmd startdump [01]\n");
X		print("	cwcmd acct\n");
X		print("	cwcmd clearacct\n");
X		goto out;
X	}
X	arg = argv[1];

X	/*
X	 * items not depend on a cw filesystem
X	 */
X	if(strcmp(arg, "acct") == 0) {
X		for(a=0; a<nelem(growacct); a++) {
X			b = growacct[a];
X			if(b) {
X				uidtostr(str, a, 1);
X				print("%10ld %s\n",
X					(b*ADDFREE*RBUFSIZE+500000)/1000000,
X						str);
X			}
X		}
X		goto out;
X	}
X	if(strcmp(arg, "clearacct") == 0) {
X		memset(growacct, 0, sizeof(growacct));
X		goto out;
X	}

X	/*
X	 * items depend on cw filesystem
X	 */
X	dev = cons.curfs->dev;
X	if(dev == 0 || dev->type != Devcw || dev->private == 0) {
X		print("cfs not a cw filesystem: %Z\n", dev);
X		goto out;
X	}
X	cw = dev->private;
X	if(strcmp(arg, "searchtag") == 0) {
X		a = 0;
X		if(argc > 2)
X			a = number(argv[2], 0, 10);
X		b = Tsuper;
X		if(argc > 3)
X			b = number(argv[3], 0, 10);
X		n = 1000;
X		if(argc > 4)
X			n = number(argv[4], 0, 10);
X		searchtag(dev, a, b, n);
X		goto out;
X	}
X	if(strcmp(arg, "mvstate") == 0) {
X		if(argc < 4)
X			goto bad;
X		s1 = convstate(argv[2]);
X		s2 = convstate(argv[3]);
X		if(s1 < 0 || s2 < 0)
X			goto bad;
X		a = -1;
X		if(argc > 4)
X			a = number(argv[4], 0, 10);
X		mvstates(dev, s1, s2, a);
X		goto out;
X	bad:
X		print("cwcmd mvstate: bad args\n");
X		goto out;
X	}
X	if(strcmp(arg, "prchain") == 0) {
X		a = 0;
X		if(argc > 2)
X			a = number(argv[2], 0, 10);
X		s1 = 0;
X		if(argc > 3)
X			s1 = number(argv[3], 0, 10);
X		prchain(dev, a, s1);
X		goto out;
X	}
X	if(strcmp(arg, "touchsb") == 0) {
X		touchsb(dev);
X		goto out;
X	}
X	if(strcmp(arg, "savecache") == 0) {
X		savecache(dev);
X		goto out;
X	}
X	if(strcmp(arg, "loadcache") == 0) {
X		s1 = -1;
X		if(argc > 2)
X			s1 = number(argv[2], 0, 10);
X		loadcache(dev, s1);
X		goto out;
X	}
X	if(strcmp(arg, "morecache") == 0) {
X		if(argc <= 2) {
X			print("arg count\n");
X			goto out;
X		}
X		s1 = number(argv[2], 0, 10);
X		if(argc > 3)
X			s2 = number(argv[3], 0, 10);
X		else
X			s2 = wormsizeside(dev, s1); /* default to 1 disc side */
X		morecache(dev, s1, s2);
X		goto out;
X	}
X	if(strcmp(arg, "blockcmp") == 0) {
X		if(argc < 4) {
X			print("cannot arg count\n");
X			goto out;
X		}
X		s1 = number(argv[2], 0, 10);
X		s2 = number(argv[3], 0, 10);
X		blockcmp(dev, s1, s2);
X		goto out;
X	}
X	if(strcmp(arg, "startdump") == 0) {
X		if(argc > 2)
X			cw->nodump = number(argv[2], 0, 10);
X		cw->nodump = !cw->nodump;
X		if(cw->nodump)
X			print("dump stopped\n");
X		else
X			print("dump allowed\n");
X		goto out;
X	}
X	if(strcmp(arg, "allflag") == 0) {
X		if(argc > 2)
X			cw->allflag = number(argv[2], 0, 10);
X		else
X			cw->allflag = !cw->allflag;
X		print("allflag = %d; falsehits = %ld\n",
X			cw->allflag, cw->falsehits);
X		goto out;
X	}
X	if(strcmp(arg, "storesb") == 0) {
X		a = 4168344;
X		b = 0;
X		if(argc > 2)
X			a = number(argv[2], 4168344, 10);
X		if(argc > 3)
X			b = number(argv[3], 0, 10);
X		storesb(dev, a, b);
X		goto out;
X	}
X	if(strcmp(arg, "test") == 0) {
X		cwtest(dev);
X		goto out;
X	}
X	print("unknown cwcmd %s\n", arg);
out:;
X}
!
echo dev/juke.c
sed 's/^X//' >dev/juke.c <<'!'
X#include	"all.h"

X#define	SCSInone	SCSIread
X#define	MAXDRIVE	10
X#define	MAXSIDE		500

X#define	TWORM		MINUTE(10)
X#define	THYSTER		SECOND(10)

typedef	struct	Side	Side;
struct	Side
X{
X	QLock;			/* protects loading/unloading */
X	int	elem;		/* element number */
X	int	drive;		/* if loaded, where */
X	uchar	status;		/* Sunload, etc */
X	uchar	rot;		/* if backside */
X	int	ord;		/* ordinal number for labeling */

X	long	time;		/* time since last access, to unspin */
X	long	stime;		/* time since last spinup, for hysteresis */
X	long	nblock;		/* number of native blocks */
X	long	block;		/* bytes per native block */
X	long	mult;		/* multiplier to get plan9 blocks */
X	long	max;		/* max size in plan9 blocks */
X};

typedef	struct	Juke	Juke;
struct	Juke
X{
X	QLock;				/* protects drive mechanism */
X	Side	side[MAXSIDE];
X	int	nside;			/* how many storage elements (*2 if rev) */
X	int	ndrive;			/* number of transfer elements */
X	Device*	juke;			/* devworm of changer */
X	Device*	drive[MAXDRIVE];	/* devworm for i/o */
X	uchar	offline[MAXDRIVE];	/* drives removed from service */
X	long	fixedsize;		/* one size fits all */
X	int	probeok;		/* wait for init to probe */

X	/* geometry returned by mode sense */
X	int	mt0,	nmt;
X	int	se0,	nse;
X	int	ie0,	nie;
X	int	dt0,	ndt;
X	int	rot;

X	Juke*	link;
X};
static	Juke*	jukelist;

enum
X{
X	Sempty = 0,	/* does not exist */
X	Sunload,	/* on the shelf */
X	Sstart,		/* loaded and spinning */
X};

extern int FIXEDSIZE;

static	int	wormsense(Device*);
static	Side*	wormunit(Device*);
static	void	shelves(void);
static	int	mmove(Juke*, int, int, int, int);
static	int	bestdrive(Juke*, int);
static	void	waitready(Device*);
static	void	element(Juke*, int);

X/*
X * mounts and spins up the device
X *	locks the structure
X */
static
Side*
wormunit(Device *d)
X{
X	int p, s, drive;
X	Side *v;
X	Juke *w;
X	uchar cmd[10], buf[8];

X	w = d->private;
X	p = d->wren.targ;
X	if(p < 0 || p >= w->nside) {
X//		panic("wormunit partition %Z\n", d);
X		return 0;
X	}

X	/*
X	 * if disk is unloaded, must load it
X	 * into next (circular) logical unit
X	 */
X	v = &w->side[p];
X	qlock(v);
X	if(v->status == Sunload) {
X		for(;;) {
X			qlock(w);
X			drive = bestdrive(w, p);
X			if(drive >= 0)
X				break;
X			qunlock(w);
X			waitsec(100);
X		}
X		print("	load   r%ld drive %Z\n", v-w->side, w->drive[drive]);
X		if(mmove(w, w->mt0, v->elem, w->dt0+drive, v->rot)) {
X			qunlock(w);
X			goto sbad;
X		}
X		v->drive = drive;
X		v->status = Sstart;
X		v->stime = toytime();
X		qunlock(w);
X		waitready(w->drive[drive]);
X		v->stime = toytime();
X	}
X	if(v->status != Sstart) {
X		if(v->status == Sempty)
X			print("worm: unit empty %Z\n", d);
X		else
X			print("worm: not started %Z\n", d);
X		goto sbad;
X	}

X	v->time = toytime();
X	if(v->block)
X		return v;

X	/*
X	 * capacity command
X	 */
X	memset(cmd, 0, sizeof(cmd));
X	memset(buf, 0, sizeof(buf));
X	cmd[0] = 0x25;	/* read capacity */
X	s = scsiio(w->drive[v->drive], SCSIread,
X		cmd, sizeof(cmd), buf, sizeof(buf));
X	if(s)
X		goto sbad;

X	v->nblock =
X		(buf[0]<<24) |
X		(buf[1]<<16) |
X		(buf[2]<<8) |
X		(buf[3]<<0);
X	v->block =
X		(buf[4]<<24) |
X		(buf[5]<<16) |
X		(buf[6]<<8) |
X		(buf[7]<<0);
X	v->mult =
X		(RBUFSIZE + v->block - 1) /
X		v->block;
X	v->max =
X		(v->nblock + 1) / v->mult;

X	print("	worm %Z: drive %Z\n", d, w->drive[v->drive]);
X	print("		%ld blocks at %ld bytes each\n",
X		v->nblock, v->block);
X	print("		%ld logical blocks at %d bytes each\n",
X		v->max, RBUFSIZE);
X	print("		%ld multiplier\n",
X		v->mult);
X	if(d->type != Devlworm)
X		return v;
X	/* check for label */
X	print("label %Z ordinal %d\n", d, v->ord);
X	qunlock(v);
X	return wormunit(d);

sbad:
X	qunlock(v);
X//	panic("wormunit sbad");
X	return 0;
X}

static
void
waitready(Device *d)
X{
X	uchar cmd[6];
X	int s, e;

X	for(e=0;e<100;e++) {
X		memset(cmd, 0, sizeof(cmd));
X		s = scsiio(d, SCSInone, cmd, sizeof(cmd), cmd, 0);
X		if(s == 0)
X			break;
X		waitsec(100);
X	}
X}

static
int
bestdrive(Juke *w, int side)
X{
X	Side *v, *bv[MAXDRIVE];
X	int i, s, e, drive;
X	long t, t0;

loop:
X	/* build table of what platters on what drives */
X	for(i=0; i<w->ndt; i++)
X		bv[i] = 0;

X	v = &w->side[0];
X	for(i=0; i<w->nside; i++, v++) {
X		s = v->status;
X		if(s == Sstart) {
X			drive = v->drive;
X			if(drive >= 0 && drive < w->ndt)
X				bv[drive] = v;
X		}
X	}

X	/*
X	 * find oldest drive, but must be
X	 * at least THYSTER old.
X	 */
X	e = w->side[side].elem;
X	t0 = toytime() - THYSTER;
X	t = t0;
X	drive = -1;
X	for(i=0; i<w->ndt; i++) {
X		v = bv[i];
X		if(v == 0) {		/* 2nd priority: empty drive */
X			if(w->offline[i])
X				continue;
X			if(w->drive[i] != devnone) {
X				drive = i;
X				t = 0;
X			}
X			continue;
X		}
X		if(v->elem == e) {	/* 1st priority: other side */
X			drive = -1;
X			if(v->stime < t0)
X				drive = i;
X			break;
X		}
X		if(v->stime < t) {	/* 3rd priority: by time */
X			drive = i;
X			t = v->stime;
X		}
X	}

X	if(drive >= 0) {
X		v = bv[drive];
X		if(v) {
X			qlock(v);
X			if(v->status != Sstart) {
X				qunlock(v);
X				goto loop;
X			}
X			print("	unload r%ld drive %Z\n",
X				v-w->side, w->drive[drive]);
X			if(mmove(w, w->mt0, w->dt0+drive, v->elem, v->rot)) {
X				qunlock(v);
X				goto loop;
X			}
X			v->status = Sunload;
X			qunlock(v);
X		}
X	}
X	return drive;
X}

long
wormsize(Device *d)
X{
X	Side *v;
X	Juke *w;
X	long size;

X	w = d->private;
X	if(w->fixedsize) {
X		size = w->fixedsize;
X		goto out;
X	}

X	v = wormunit(d);
X	if(v == 0)
X		return 0;
X	size = v->max;
X	qunlock(v);
X	if(FIXEDSIZE)	// TODO? push FIXEDSIZE into Device or Juke struct
X		w->fixedsize = size;
out:
X	if(d->type == Devlworm)
X		return size-1;
X	return size;
X}

X/*
X * d must be, or be on, the same jukebox as `side' resides on.
X * have to paw through Devmcat of sides.
X */
long
wormsizeside(Device *d, int side)
X{
X	int s;
X	Device *x;

X	switch(d->type) {
X	default:
X		print("wormsizeside: dev not ro, cw, juke nor mcat: %Z\n", d);
X		return 0;

X	case Devmcat:					/* of sides */
X		break;
X	case Devjuke:
X		return wormsizeside(d->j.m, side);	/* mcat of sides */
X	case Devcw:
X		return wormsizeside(d->cw.w, side);	/* should be juke */
X	case Devro:
X		return wormsizeside(d->ro.parent, side);	/* cw */
X	}

X	if (side < 0 || side >= d->cat.ndev) {
X		print("wormsizeside: side %d not in range [0..%d] for %Z\n",
X			side, d->cat.ndev, d);
X		return 0;
X	}
X	x = d->cat.first;
X	for (s = 0; s < side; s++) {
X		x = x->link;
X		if (x == nil)
X			panic("wormsizeside nil link");
X	}
X	if (x->type != Devworm && x->type != Devlworm) {
X		print("wormsizeside: %Z of %Z type not (l)worm\n", x, d);
X		return 0;
X	}
X	return wormsize(x);
X}

X/* returns starts of side #side and #(side+1) in *stp */
void
wormsidestarts(Device *dev, int side, Sidestarts *stp)
X{
X	int s;
X	long dstart;

X	for (dstart = s = 0; s < side; s++)
X		dstart += wormsizeside(dev, s);
X	stp->sstart = dstart;
X	stp->s1start = dstart + wormsizeside(dev, side);
X}

static
int
wormiocmd(Device *d, int io, long b, void *c)
X{
X	Side *v;
X	Juke *w;
X	long l, m;
X	int s;
X	uchar cmd[10];

X	w = d->private;
X	v = wormunit(d);
X	if(v == 0)
X		return 0x71;
X	if(b >= v->max) {
X		qunlock(v);
X		print("worm: wormiocmd out of range %Z(%ld)\n", d, b);
X		return 0x071;
X	}

X	memset(cmd, 0, sizeof(cmd));
X	cmd[0] = 0x28;		/* extended read */
X	if(io != SCSIread)
X		cmd[0] = 0x2a;	/* extended write */

X	m = v->mult;
X	l = b * m;
X	cmd[2] = l>>24;
X	cmd[3] = l>>16;
X	cmd[4] = l>>8;
X	cmd[5] = l;

X	cmd[7] = m>>8;
X	cmd[8] = m;

X	s = scsiio(w->drive[v->drive], io, cmd, sizeof(cmd), c, RBUFSIZE);
X	qunlock(v);
X	return s;
X}

int
wormread(Device *d, long b, void *c)
X{
X	int s;

X	s = wormiocmd(d, SCSIread, b, c);
X	if(s) {
X		print("wormread: %Z(%ld) bad status #%x\n", d, b, s);
X		cons.nwormre++;
X		return s;
X	}
X	return 0;
X}

int
wormwrite(Device *d, long b, void *c)
X{
X	int s;

X	s = wormiocmd(d, SCSIwrite, b, c);
X	if(s) {
X		print("wormwrite: %Z(%ld) bad status #%x\n", d, b, s);
X		cons.nwormwe++;
X		return s;
X	}
X	return 0;
X}

static
int
mmove(Juke *w, int trans, int from, int to, int rot)
X{
X	uchar cmd[12], buf[4];
X	int s;
X	static recur = 0;

X	memset(cmd, 0, sizeof(cmd));
X	cmd[0] = 0xa5;	/* move medium */
X	cmd[2] = trans>>8;
X	cmd[3] = trans;
X	cmd[4] = from>>8;
X	cmd[5] = from;
X	cmd[6] = to>>8;
X	cmd[7] = to;
X	if(rot)
X		cmd[10] = 1;
X	s = scsiio(w->juke, SCSInone, cmd, sizeof(cmd), buf, 0);
X	if(s) {
X		print("scsio status #%x\n", s);
X		print("move medium t=%d fr=%d to=%d rot=%d", trans, from, to, rot);
X//		panic("mmove");
X		if(recur == 0) {
X			recur = 1;
X			print("element from=%d\n", from);
X			element(w, from);
X			print("element to=%d\n", to);
X			element(w, to);
X			print("element trans=%d\n", trans);
X			element(w, trans);
X			recur = 0;
X		}
X		return 1;
X	}
X	return 0;
X}

static
void
geometry(Juke *w)
X{
X	int s;
X	uchar cmd[6], buf[4+20];

X	memset(cmd, 0, sizeof(cmd));
X	memset(buf, 0, sizeof(buf));
X	cmd[0] = 0x1a;		/* mode sense */
X	cmd[2] = 0x1d;		/* element address assignment */
X	cmd[4] = sizeof(buf);	/* allocation length */

X	s = scsiio(w->juke, SCSIread, cmd, sizeof(cmd), buf, sizeof(buf));
X	if(s)
X		panic("geometry #%x\n", s);

X	w->mt0 = (buf[4+2]<<8) | buf[4+3];
X	w->nmt = (buf[4+4]<<8) | buf[4+5];
X	w->se0 = (buf[4+6]<<8) | buf[4+7];
X	w->nse = (buf[4+8]<<8) | buf[4+9];
X	w->ie0 = (buf[4+10]<<8) | buf[4+11];
X	w->nie = (buf[4+12]<<8) | buf[4+13];
X	w->dt0 = (buf[4+14]<<8) | buf[4+15];
X	w->ndt = (buf[4+16]<<8) | buf[4+17];

X	memset(cmd, 0, 6);
X	memset(buf, 0, sizeof(buf));
X	cmd[0] = 0x1a;		/* mode sense */
X	cmd[2] = 0x1e;		/* transport geometry */
X	cmd[4] = sizeof(buf);	/* allocation length */

X	s = scsiio(w->juke, SCSIread, cmd, sizeof(cmd), buf, sizeof(buf));
X	if(s)
X		panic("geometry #%x\n", s);

X	w->rot = buf[4+2] & 1;

X	print("	mt %d %d\n", w->mt0, w->nmt);
X	print("	se %d %d\n", w->se0, w->nse);
X	print("	ie %d %d\n", w->ie0, w->nie);
X	print("	dt %d %d\n", w->dt0, w->ndt);
X	print("	rot %d\n", w->rot);
X	prflush();

X}

static
void
element(Juke *w, int e)
X{
X	uchar cmd[12], buf[8+8+88];
X	int s, t;

X//loop:
X	memset(cmd, 0, sizeof(cmd));
X	memset(buf, 0, sizeof(buf));
X	cmd[0] = 0xb8;		/* read element status */
X	cmd[2] = e>>8;		/* starting element */
X	cmd[3] = e;
X	cmd[5] = 1;		/* number of elements */
X	cmd[9] = sizeof(buf);	/* allocation length */

X	s = scsiio(w->juke, SCSIread, cmd, sizeof(cmd), buf, sizeof(buf));
X	if(s) {
X		print("scsiio #%x\n", s);
X		goto bad;
X	}

X	s = (buf[0]<<8) | buf[1];
X	if(s != e) {
X		print("element = %d\n", s);
X		goto bad;
X	}
X	if(buf[3] != 1) {
X		print("number reported = %d\n", buf[3]);
X		goto bad;
X	}
X	s = (buf[8+8+0]<<8) | buf[8+8+1];
X	if(s != e) {
X		print("element1 = %d\n", s);
X		goto bad;
X	}

X	switch(buf[8+0]) {	/* element type */
X	default:
X		print("unknown element %d: %d\n", e, buf[8+0]);
X		goto bad;
X	case 1:			/* transport */
X		s = e - w->mt0;
X		if(s < 0 || s >= w->nmt)
X			goto bad;
X		if(buf[8+8+2] & 1)
X			print("transport %d full %d.%d\n", s,
X				(buf[8+8+10]<<8) | buf[8+8+11],
X				(buf[8+8+9]>>6) & 1);
X		break;
X	case 2:			/* storage */
X		s = e - w->se0;
X		if(s < 0 || s >= w->nse)
X			goto bad;
X		w->side[s].status = Sempty;
X		if(buf[8+8+2] & 1)
X			w->side[s].status = Sunload;
X		if(w->rot)
X			w->side[w->nse+s].status = w->side[s].status;
X		break;
X	case 3:			/* import/export */
X		s = e - w->ie0;
X		if(s < 0 || s >= w->nie)
X			goto bad;
X		print("import/export %d #%.2x %d.%d\n", s,
X			buf[8+8+2],
X			(buf[8+8+10]<<8) | buf[8+8+11],
X			(buf[8+8+9]>>6) & 1);
X		break;
X	case 4:			/* data transfer */
X		s = e - w->dt0;
X		if(s < 0 || s >= w->ndt)
X			goto bad;
X		print("data transfer %d #%.2x %d.%d\n", s,
X			buf[8+8+2],
X			(buf[8+8+10]<<8) | buf[8+8+11],
X			(buf[8+8+9]>>6) & 1);
X		if(buf[8+8+2] & 1) {
X			t = ((buf[8+8+10]<<8) | buf[8+8+11]) - w->se0;
X			print("r%d in drive %d\n", t, s);
X			if(mmove(w, w->mt0, w->dt0+s, w->se0+t, (buf[8+8+9]>>6) & 1)) {
X				print("mmove initial unload\n");
X				goto bad;
X			}
X			w->side[t].status = Sunload;
X			if(w->rot)
X				w->side[w->nse+t].status = Sunload;
X		}
X		if(buf[8+8+2] & 4) {
X			print("drive w%d has exception #%.2x #%.2x\n", s,
X				buf[8+8+4], buf[8+8+5]);
X			goto bad;
X		}
X		break;
X	}
X	return;

bad:
X//	panic("element");
X	return;
X}

static
void
positions(Juke *w)
X{
X	int i, f;

X	/* mark empty shelves */
X	for(i=0; i<w->nse; i++)
X		element(w, w->se0+i);
X	for(i=0; i<w->nmt; i++)
X		element(w, w->mt0+i);
X	for(i=0; i<w->nie; i++)
X		element(w, w->ie0+i);
X	for(i=0; i<w->ndt; i++)
X		element(w, w->dt0+i);

X	f = 0;
X	for(i=0; i<w->nse; i++) {
X		if(w->side[i].status == Sempty) {
X			if(f) {
X				print("r%d\n", i-1);
X				f = 0;
X			}
X		} else {
X			if(!f) {
X				print("	shelves r%d-", i);
X				f = 1;
X			}
X		}
X	}
X	if(f)
X		print("r%d\n", i-1);
X}

static
void
jinit(Juke *w, Device *d, int o)
X{
X	int p;
X	Device *dev = d;

X	switch(d->type) {
X	default:
X		print("juke platter not (devmcat of) dev(l)worm: %Z\n", d);
X		goto bad;

X	case Devmcat:
X		/*
X		 * we don't call mcatinit(d) here, so we have to set d->cat.ndev
X		 * ourselves.
X		 */
X		for(d=d->cat.first; d; d=d->link)
X			jinit(w, d, o++);
X		dev->cat.ndev = o;
X		break;

X	case Devlworm:
X		p = d->wren.targ;
X		if(p < 0 || p >= w->nside)
X			panic("jinit partition %Z\n", d);
X		w->side[p].ord = o;

X	case Devworm:
X		if(d->private) {
X			print("juke platter private pointer set %p\n",
X				d->private);
X			goto bad;
X		}
X		d->private = w;
X		break;
X	}
X	return;

bad:
X	panic("jinit");
X}

Side*
wormi(char *arg)
X{
X	int i, j;
X	Juke *w;
X	Side *v;

X	w = jukelist;
X	i = number(arg, -1, 10) - 1;
X	if(i < 0 || i >= w->nside) {
X		print("bad unit number %s (%d)\n", arg, i+1);
X		return 0;
X	}
X	j = i;
X	if(j >= w->nse)
X		j -= w->nse;
X	if(j < w->nside) {
X		v = &w->side[j];
X		qlock(v);
X		if(v->status == Sstart) {
X			if(mmove(w, w->mt0, w->dt0+v->drive, v->elem, v->rot)) {
X				qunlock(v);
X				return 0;
X			}
X			v->status = Sunload;
X		}
X		qunlock(v);
X	}
X	j += w->nse;
X	if(j < w->nside) {
X		v = &w->side[j];
X		qlock(v);
X		if(v->status == Sstart) {
X			if(mmove(w, w->mt0, w->dt0+v->drive, v->elem, v->rot)) {
X				qunlock(v);
X				return 0;
X			}
X			v->status = Sunload;
X		}
X		qunlock(v);
X	}
X	v = &w->side[i];
X	qlock(v);
X	return v;
X}

static
void
cmd_wormoffline(int argc, char *argv[])
X{
X	int u, i;
X	Juke *w;

X	w = jukelist;
X	if(argc <= 1) {
X		print("usage: wormoffline drive\n");
X		return;
X	}
X	u = number(argv[1], -1, 10);
X	if(u < 0 || u >= w->ndrive) {
X		print("bad drive %s (0<=%d<%d)\n", argv[1], u, w->ndrive);
X		return;
X	}
X	if(w->offline[u])
X		print("drive %d already offline\n", u);
X	w->offline[u] = 1;
X	for(i=0; i<w->ndrive; i++)
X		if(w->offline[i] == 0)
X			return;
X	print("that would take all drives offline\n");
X	w->offline[u] = 0;
X}

static
void
cmd_wormonline(int argc, char *argv[])
X{
X	int u;
X	Juke *w;

X	w = jukelist;
X	if(argc <= 1) {
X		print("usage: wormonline drive\n");
X		return;
X	}
X	u = number(argv[1], -1, 10);
X	if(u < 0 || u >= w->ndrive) {
X		print("bad drive %s (0<=%d<%d)\n", argv[1], u, w->ndrive);
X		return;
X	}
X	if(w->offline[u] == 0)
X		print("drive %d already online\n", u);
X	w->offline[u] = 0;
X}

static
void
cmd_wormreset(int, char *[])
X{
X	Juke *w;

X	for(w=jukelist; w; w=w->link) {
X		qlock(w);
X		positions(w);
X		qunlock(w);
X	}
X}

static
void
cmd_wormeject(int argc, char *argv[])
X{
X	Juke *w;
X	Side *v;

X	if(argc <= 1) {
X		print("usage: wormeject unit\n");
X		return;
X	}
X	w = jukelist;
X	v = wormi(argv[1]);
X	if(v == 0)
X		return;
X	mmove(w, w->mt0, v->elem, w->ie0, 0);
X	qunlock(v);
X}

static
void
cmd_wormingest(int argc, char *argv[])
X{
X	Juke *w;
X	Side *v;

X	w = jukelist;
X	if(argc <= 1) {
X		print("usage: wormingest unit\n");
X		return;
X	}
X	v = wormi(argv[1]);
X	if(v == 0)
X		return;
X	mmove(w, w->mt0, w->ie0, v->elem, 0);
X	qunlock(v);
X}

void
jukeinit(Device *d)
X{
X	Juke *w;
X	Device *xdev;
X	Side *v;
X	int i;

X	/* j(w<changer>w<station0>...)(r<platters>) */
X	xdev = d->j.j;
X	if(xdev->type != Devmcat) {
X		print("juke union not mcat\n");
X		goto bad;
X	}

X	/*
X	 * pick up the changer device
X	 */
X	xdev = xdev->cat.first;
X	if(xdev->type != Devwren) {
X		print("juke changer not wren %Z\n", xdev);
X		goto bad;
X	}
X	for(w=jukelist; w; w=w->link)
X		if(xdev == w->juke)
X			goto found;

X	/*
X	 * allocate a juke structure
X	 * no locking problems.
X	 */
X	w = ialloc(sizeof(Juke), 0);
X	w->link = jukelist;
X	jukelist = w;

X	print("alloc juke %Z\n", xdev);
X	qlock(w);
X	qunlock(w);
X	w->name = "juke";
X	w->juke = xdev;
X	geometry(w);

X	/*
X	 * pick up each side
X	 */
X	w->nside = w->nse;
X	if(w->rot)
X		w->nside += w->nside;
X	if(w->nside > MAXSIDE) {
X		print("too many sides: %d max %d\n", w->nside, MAXSIDE);
X		goto bad;
X	}
X	for(i=0; i<w->nse; i++) {
X		v = &w->side[i];
X		qlock(v);
X		qunlock(v);
X		v->name = "shelf";
X		v->elem = w->se0 + i;
X		v->rot = 0;
X		v->status = Sempty;
X		v->time = toytime();
X		if(w->rot) {
X			v += w->nse;
X			qlock(v);
X			qunlock(v);
X			v->name = "shelf";
X			v->elem = w->se0 + i;
X			v->rot = 1;
X			v->status = Sempty;
X			v->time = toytime();
X		}
X	}
X	positions(w);

X	w->ndrive = w->ndt;
X	if(w->ndrive > MAXDRIVE) {
X		print("ndrives truncated to %d\n", MAXDRIVE);
X		w->ndrive = MAXDRIVE;
X	}

X	/*
X	 * pick up each drive
X	 */
X	for(i=0; i<w->ndrive; i++)
X		w->drive[i] = devnone;

X	cmd_install("wormreset", "-- put drives back where jukebox thinks they belong", cmd_wormreset);
X	cmd_install("wormeject", "unit -- shelf to outside", cmd_wormeject);
X	cmd_install("wormingest", "unit -- outside to shelf", cmd_wormingest);
X	cmd_install("wormoffline", "unit -- disable drive", cmd_wormoffline);
X	cmd_install("wormonline", "unit -- enable drive", cmd_wormonline);

found:
X	i = 0;
X	while(xdev = xdev->link) {
X		if(xdev->type != Devwren) {
X			print("drive not devwren: %Z\n", xdev);
X			goto bad;
X		}
X		if(w->drive[i]->type != Devnone &&
X		   xdev != w->drive[i]) {
X			print("double init drive %d %Z %Z\n", i, w->drive[i], xdev);
X			goto bad;
X		}
X		if(i >= w->ndrive) {
X			print("too many drives %Z\n", xdev);
X			goto bad;
X		}
X		w->drive[i++] = xdev;
X	}

X	if(i <= 0) {
X		print("no drives\n");
X		goto bad;
X	}

X	/*
X	 * put w pointer in each platter
X	 */
X	d->private = w;
X	jinit(w, d->j.m, 0);
X	w->probeok = 1;
X	return;

bad:
X	panic("juke init");
X}

int
dowcp(void)
X{
X	return 0;
X}

X/*
X * called periodically
X */
void
wormprobe(void)
X{
X	int i, drive;
X	long t;
X	Side *v;
X	Juke *w;

X	t = toytime() - TWORM;
X	for(w=jukelist; w; w=w->link) {
X		if(w->probeok == 0 || !canqlock(w))
X			continue;
X		for(i=0; i<w->nside; i++) {
X			v = &w->side[i];
X			if(!canqlock(v))
X				continue;
X			if(v->status == Sstart && t > v->time) {
X				drive = v->drive;
X				print("	time   r%ld drive %Z\n",
X					v-w->side, w->drive[drive]);
X				mmove(w, w->mt0, w->dt0+drive, v->elem, v->rot);
X				v->status = Sunload;
X			}
X			qunlock(v);
X		}
X		qunlock(w);
X	}
X}
!
echo fs/9fsfs.c
sed 's/^X//' >fs/9fsfs.c <<'!'
X#include "all.h"
X#include "mem.h"
X#include "io.h"
X#include "ureg.h"

X#include "../pc/dosfs.h"

X/*
X * setting this to zero permits the use of discs of different sizes, but
X * can make jukeinit() quite slow while the robotics work through each disc
X * twice (once per side).
X */
int FIXEDSIZE = 1;

X#ifndef	DATE
X#define	DATE	568011600L+4*3600
X#endif

ulong	mktime		= DATE;				/* set by mkfile */
Startsb	startsb[] =
X{
X	"main",		2,
X	0
X};

Dos dos;

static
struct
X{
X	char	*name;
X	long	(*read)(int, void*, long);
X	vlong	(*seek)(int, vlong);
X	long	(*write)(int, void*, long);
X	int	(*part)(int, char*);
X} nvrdevs[] =
X{
X	{ "fd", floppyread, floppyseek, floppywrite, 0, },
X	{ "hd", ataread,   ataseek,   atawrite,   setatapart, },
X	/*
X	{ "sd", scsiread,   scsiseek,   scsiwrite,   setscsipart, },
X	 */
X	{ 0, },
X};

void
otherinit(void)
X{
X	int dev, i, nfd, nhd, s;
X	char *p, *q, buf[sizeof(nvrfile)+8];

X	kbdinit();
X	printcpufreq();
X	etherinit();
X	scsiinit();

X	s = spllo();
X	nhd = atainit();
X	nfd = floppyinit();
X	dev = 0;
X	if(p = getconf("nvr")){
X		strncpy(buf, p, sizeof(buf)-2);
X		buf[sizeof(buf)-1] = 0;
X		p = strchr(buf, '!');
X		q = strrchr(buf, '!');
X		if(p == 0 || q == 0 || strchr(p+1, '!') != q)
X			panic("malformed nvrfile: %s\n", buf);
X		*p++ = 0;
X		*q++ = 0;
X		dev = strtoul(p, 0, 0);
X		strcpy(nvrfile, q);
X		p = buf;
X	} else
X	if(p = getconf("bootfile")){
X		strncpy(buf, p, sizeof(buf)-2);
X		buf[sizeof(buf)-1] = 0;
X		p = strchr(buf, '!');
X		q = strrchr(buf, '!');
X		if(p == 0 || q == 0 || strchr(p+1, '!') != q)
X			panic("malformed bootfile: %s\n", buf);
X		*p++ = 0;
X		*q = 0;
X		dev = strtoul(p, 0, 0);
X		p = buf;
X	} else
X	if(nfd)
X		p = "fd";
X	else
X	if(nhd)
X		p = "hd";
X	else
X		p = "sd";

X	for(i = 0; nvrdevs[i].name; i++){
X		if(strcmp(p, nvrdevs[i].name) == 0){
X			dos.dev = dev;
X			if(nvrdevs[i].part && (*nvrdevs[i].part)(dos.dev, "disk") == 0)
X				break;
X			dos.read = nvrdevs[i].read;
X			dos.seek = nvrdevs[i].seek;
X			dos.write = nvrdevs[i].write;
X			break;
X		}
X	}
X	if(dos.read == 0)
X		panic("no device for nvram\n");
X	if(dosinit(&dos) < 0)
X		panic("can't init dos dosfs on %s\n", p);
X	splx(s);
X}

void
touser(void)
X{
X	int i;

X	settime(rtctime());
X	boottime = time();

X	print("sysinit\n");
X	sysinit();

X	userinit(floppyproc, 0, "floppyproc");
X	/*
X	 * Ethernet i/o processes
X	 */
X	etherstart();


X	/*
X	 * read ahead processes
X	 */
X	userinit(rahead, 0, "rah");

X	/*
X	 * server processes
X	 */
X	for(i=0; i<conf.nserve; i++)
X		userinit(serve, 0, "srv");

X	/*
X	 * worm "dump" copy process
X	 */
X	userinit(wormcopy, 0, "wcp");

X	/*
X	 * processes to read the console
X	 */
X	consserve();

X	/*
X	 * "sync" copy process
X	 * this doesn't return.
X	 */
X	u->text = "scp";
X	synccopy();
X}

void
localconfinit(void)
X{
X	/* conf.nfile = 60000; */	/* from emelie */
X	conf.nodump = 0;
X	conf.dumpreread = 1;
X	conf.firstsb = 0;	/* time- & jukebox-dependent optimisation */
X	conf.recovsb = 0;
X	conf.ripoff = 1;
X	conf.nlgmsg = 100;
X	conf.nsmmsg = 500;

X}

int (*fsprotocol[])(Msgbuf*) = {
X	serve9p1,
X	serve9p2,
X	nil,
X};
!
echo fs/dat.h
sed 's/^X//' >fs/dat.h <<'!'
X/*
X * The most fundamental constant.
X * Will the code compile with RBUFSIZE a variable?
X * Nope, for one thing, RBUFSIZE determines FEPERBUF, which determines
X * the number of elements in a free-list-block array.
X */
X#define RBUFSIZE	(4*1024)	/* raw buffer size */

X#include "../port/portdat.h"

extern	Mach	mach0;

typedef struct Segdesc	Segdesc;
struct Segdesc
X{
X	ulong	d0;
X	ulong	d1;
X};

typedef struct Mbank {
X	ulong	base;
X	ulong	limit;
X} Mbank;

X#define MAXBANK		8

typedef struct Mconf {
X	Lock;
X	Mbank	bank[MAXBANK];
X	int	nbank;
X	ulong	topofmem;
X} Mconf;
extern Mconf mconf;

extern char nvrfile[128];
!
echo port/chk.c
sed 's/^X//' >port/chk.c <<'!'
X#include	"all.h"

static	char*	abits;
static	long	sizabits;
static	char*	qbits;
static	long	sizqbits;
static	char*	name;
static	long	sizname;
static	long	fstart;
static	long	fsize;
static	long	nfiles;
static	long	maxq;
static	char*	calloc;
static	Device*	dev;
static	long	ndup;
static	long	nused;
static	long	nfdup;
static	long	nqbad;
static	long	nfree;
static	long	nbad;
static	int	mod;
static	int	flags;
static	int	ronly;
static	int	cwflag;
static	long	sbaddr;
static	long	oldblock;
static	int	depth;
static	int	maxdepth;

X/* local prototypes */
static	int	fsck(Dentry*);
static	void	ckfreelist(Superb*);
static	void	mkfreelist(Superb*);
static	void	trfreelist(Superb*);
static	void	xaddfree(Device*, long, Superb*, Iobuf*);
static	void	xflush(Device*, Superb*, Iobuf*);
static	Dentry*	maked(long, int, long);
static	void	modd(long, int, Dentry*);
static	void	xread(long, long);
static	int	amark(long);
static	int	fmark(long);
static	int	ftest(long);
static	void	missing(void);
static	void	qmark(long);
static	void*	malloc(ulong);
static	Iobuf*	xtag(long, int, long);

static
void*
malloc(ulong n)
X{
X	char *p, *q;

X	p = calloc;
X	while((ulong)p & 3)
X		p++;
X	q = p+n;
X	if(((ulong)q&0x0fffffffL) >= conf.mem)
X		panic("check: mem size");
X	calloc = q;
X	memset(p, 0, n);
X	return p;
X}

X/*
X * check flags
X */
enum
X{
X	Crdall	= (1<<0),	/* read all files */
X	Ctag	= (1<<1),	/* rebuild tags */
X	Cpfile	= (1<<2),	/* print files */
X	Cpdir	= (1<<3),	/* print directories */
X	Cfree	= (1<<4),	/* rebuild free list */
X	Csetqid	= (1<<5),	/* resequence qids */
X	Cream	= (1<<6),	/* clear all bad tags */
X	Cbad	= (1<<7),	/* clear all bad blocks */
X	Ctouch	= (1<<8),	/* touch old dir and indir */
X};

static
struct
X{
X	char*	option;
X	long	flag;
X} ckoption[] =
X{
X	"rdall",	Crdall,
X	"tag",		Ctag,
X	"pfile",	Cpfile,
X	"pdir",		Cpdir,
X	"free",		Cfree,
X	"setqid",	Csetqid,
X	"ream",		Cream,
X	"bad",		Cbad,
X	"touch",	Ctouch,
X	0,
X};

void
cmd_check(int argc, char *argv[])
X{
X	long f, i;
X	Filsys *fs;
X	Iobuf *p;
X	Superb *sb;
X	Dentry *d;
X	long raddr, flag;

X	flag = 0;
X	for(i=1; i<argc; i++) {
X		for(f=0; ckoption[f].option; f++)
X			if(strcmp(argv[i], ckoption[f].option) == 0)
X				goto found;
X		print("unknown check option %s\n", argv[i]);
X		for(f=0; ckoption[f].option; f++)
X			print("	%s\n", ckoption[f].option);
X		return;
X	found:
X		flag |= ckoption[f].flag;
X	}
X	fs = cons.curfs;

X	dev = fs->dev;
X	ronly = (dev->type == Devro);
X	cwflag = (dev->type == Devcw) | (dev->type == Devro);
X	if(!ronly)
X		wlock(&mainlock);		/* check */
X	calloc = (char*)ialloc(0, 1) + 100000;
X	flags = flag;

X	sizqbits = ((1<<22) + 7) / 8;		/* botch */
X	qbits = malloc(sizqbits);

X	sbaddr = superaddr(dev);
X	raddr = getraddr(dev);
X	p = xtag(sbaddr, Tsuper, QPSUPER);
X	if(!p)
X		goto out;
X	sb = (Superb*)p->iobuf;
X	fstart = 2;
X	cons.noage = 1;

X	fsize = sb->fsize;
X	sizabits = (fsize-fstart + 7)/8;
X	abits = malloc(sizabits);

X	sizname = 4000;
X	name = malloc(sizname);
X	sizname -= NAMELEN+10;	/* for safety */

X	mod = 0;
X	nfree = 0;
X	nfdup = 0;
X	nused = 0;
X	nbad = 0;
X	ndup = 0;
X	nqbad = 0;
X	depth = 0;
X	maxdepth = 0;

X	if(flags & Ctouch) {
X		/* round fsize down to start of current side */
X		int s;
X		long dsize;

X		oldblock = 0;
X		for (s = 0; dsize = wormsizeside(dev, s),
X		     dsize > 0 && oldblock + dsize < fsize; s++)
X			oldblock += dsize;
X		print("oldblock = %ld\n", oldblock);
X	}
X	amark(sbaddr);
X	if(cwflag) {
X		amark(sb->roraddr);
X		amark(sb->next);
X	}

X	print("checking filsys: %s\n", fs->name);
X	nfiles = 0;
X	maxq = 0;

X	d = maked(raddr, 0, QPROOT);
X	if(d) {
X		amark(raddr);
X		if(fsck(d))
X			modd(raddr, 0, d);
X		depth--;
X		calloc -= sizeof(Dentry);
X		if(depth)
X			print("depth not zero on return\n");
X	}

X	if(flags & Cfree) {
X		if(cwflag)
X			trfreelist(sb);
X		else
X			mkfreelist(sb);
X	}

X	if(sb->qidgen < maxq)
X		print("qid generator low path=%ld maxq=%ld\n",
X			sb->qidgen, maxq);
X	if(!(flags & Cfree))
X		ckfreelist(sb);
X	if(mod) {
X		sb->qidgen = maxq;
X		print("file system was modified\n");
X		settag(p, Tsuper, QPNONE);
X	}

X	print("nfiles = %ld\n", nfiles);
X	print("fsize  = %ld\n", fsize);
X	print("nused  = %ld\n", nused);
X	print("ndup   = %ld\n", ndup);
X	print("nfree  = %ld\n", nfree);
X	print("tfree  = %ld\n", sb->tfree);
X	print("nfdup  = %ld\n", nfdup);
X	print("nmiss  = %ld\n", fsize-fstart-nused-nfree);
X	print("nbad   = %ld\n", nbad);
X	print("nqbad  = %ld\n", nqbad);
X	print("maxq   = %ld\n", maxq);
X	if(!cwflag)
X		missing();

out:
X	cons.noage = 0;
X	putbuf(p);
X	if(!ronly)
X		wunlock(&mainlock);
X}

static
int
fsck(Dentry *d)
X{
X	Dentry *nd;
X	Iobuf *p1, *p2, *pd;
X	int i, j, k, ns, dmod;
X	long a, qpath;

X	depth++;
X	if(depth >= maxdepth) {
X		maxdepth = depth;
X		/*
X		 * On a 386 each recursion costs 72 bytes or thereabouts,
X		 * for some slop bump it up to 100.
X		 * Alternatives here might be to give the check process
X		 * a much bigger stack or rewrite it without recursion.
X		 */
X		if(maxdepth >= MAXSTACK/100) {
X			print("max depth exceeded: %s\n", name);
X			return 0;
X		}
X	}
X	dmod = 0;
X	if(!(d->mode & DALLOC))
X		goto out;
X	nfiles++;

X	ns = strlen(name);
X	i = strlen(d->name);
X	if(i >= NAMELEN) {
X		d->name[NAMELEN-1] = 0;
X		print("%s->name (%s) not terminated\n", name, d->name);
X		return 0;
X	}
X	ns += i;
X	if(ns >= sizname) {
X		print("%s->name (%s) name too large\n", name, d->name);
X		return 0;
X	}
X	strcat(name, d->name);

X	if(d->mode & DDIR) {
X		if(ns > 1) {
X			strcat(name, "/");
X			ns++;
X		}
X		if(flags & Cpdir) {
X			print("%s\n", name);
X			prflush();
X		}
X	} else
X	if(flags & Cpfile) {
X		print("%s\n", name);
X		prflush();
X	}

X	qpath = d->qid.path & ~QPDIR;
X	qmark(qpath);
X	if(qpath > maxq)
X		maxq = qpath;
X	for(i=0; i<NDBLOCK; i++) {
X		a = d->dblock[i];
X		if(amark(a)) {
X			if(flags & Cbad) {
X				d->dblock[i] = 0;
X				dmod++;
X			}
X			a = 0;
X		}
X		if(!a)
X			continue;
X		if(d->mode & DDIR) {
X			if((flags&Ctouch) && a < oldblock) {
X				pd = getbuf(dev, a, Bread|Bmod);
X				if(pd)
X					putbuf(pd);
X				dmod++;
X			}
X			for(k=0; k<DIRPERBUF; k++) {
X				nd = maked(a, k, qpath);
X				if(!nd)
X					break;
X				if(fsck(nd)) {
X					modd(a, k, nd);
X					dmod++;
X				}
X				depth--;
X				calloc -= sizeof(Dentry);
X				name[ns] = 0;
X			}
X			continue;
X		}
X		if(flags & Crdall)
X			xread(a, qpath);
X	}
X	a = d->iblock;
X	if(amark(a)) {
X		if(flags & Cbad) {
X			d->iblock = 0;
X			dmod++;
X		}
X		a = 0;
X	}
X	if(a) {
X		if((flags&Ctouch) && a < oldblock) {
X			pd = getbuf(dev, a, Bread|Bmod);
X			if(pd)
X				putbuf(pd);
X			dmod++;
X		}
X		if(p1 = xtag(a, Tind1, qpath))
X		for(i=0; i<INDPERBUF; i++) {
X			a = ((long*)p1->iobuf)[i];
X			if(amark(a)) {
X				if(flags & Cbad) {
X					((long*)p1->iobuf)[i] = 0;
X					p1->flags |= Bmod;
X				}
X				a = 0;
X			}
X			if(!a)
X				continue;
X			if(d->mode & DDIR) {
X				if((flags&Ctouch) && a < oldblock) {
X					pd = getbuf(dev, a, Bread|Bmod);
X					if(pd)
X						putbuf(pd);
X					dmod++;
X				}
X				for(k=0; k<DIRPERBUF; k++) {
X					nd = maked(a, k, qpath);
X					if(!nd)
X						break;
X					if(fsck(nd)) {
X						modd(a, k, nd);
X						dmod++;
X					}
X					depth--;
X					calloc -= sizeof(Dentry);
X					name[ns] = 0;
X				}
X				continue;
X			}
X			if(flags & Crdall)
X				xread(a, qpath);
X		}
X		if(p1)
X			putbuf(p1);
X	}
X	a = d->diblock;
X	if(amark(a)) {
X		if(flags & Cbad) {
X			d->diblock = 0;
X			dmod++;
X		}
X		a = 0;
X	}
X	if((flags&Ctouch) && a && a < oldblock) {
X		pd = getbuf(dev, a, Bread|Bmod);
X		if(pd)
X			putbuf(pd);
X		dmod++;
X	}
X	if(p2 = xtag(a, Tind2, qpath))
X	for(i=0; i<INDPERBUF; i++) {
X		a = ((long*)p2->iobuf)[i];
X		if(amark(a)) {
X			if(flags & Cbad) {
X				((long*)p2->iobuf)[i] = 0;
X				p2->flags |= Bmod;
X			}
X			continue;
X		}
X		if((flags&Ctouch) && a && a < oldblock) {
X			pd = getbuf(dev, a, Bread|Bmod);
X			if(pd)
X				putbuf(pd);
X			dmod++;
X		}
X		if(p1 = xtag(a, Tind1, qpath))
X		for(j=0; j<INDPERBUF; j++) {
X			a = ((long*)p1->iobuf)[j];
X			if(amark(a)) {
X				if(flags & Cbad) {
X					((long*)p1->iobuf)[j] = 0;
X					p1->flags |= Bmod;
X				}
X				continue;
X			}
X			if(!a)
X				continue;
X			if(d->mode & DDIR) {
X				if((flags&Ctouch) && a < oldblock) {
X					pd = getbuf(dev, a, Bread|Bmod);
X					if(pd)
X						putbuf(pd);
X					dmod++;
X				}
X				for(k=0; k<DIRPERBUF; k++) {
X					nd = maked(a, k, qpath);
X					if(!nd)
X						break;
X					if(fsck(nd)) {
X						modd(a, k, nd);
X						dmod++;
X					}
X					depth--;
X					calloc -= sizeof(Dentry);
X					name[ns] = 0;
X				}
X				continue;
X			}
X			if(flags & Crdall)
X				xread(a, qpath);
X		}
X		if(p1)
X			putbuf(p1);
X	}
X	if(p2)
X		putbuf(p2);
out:
X	return dmod;
X}

X#define	XFEN	(FEPERBUF+6)
typedef
struct
X{
X	int	flag;
X	int	count;
X	int	next;
X	long	addr[XFEN];
X} Xfree;

static
void
xaddfree(Device *dev, long a, Superb *sb, Iobuf *p)
X{
X	Xfree *x;

X	x = (Xfree*)p->iobuf;
X	if(x->count < XFEN) {
X		x->addr[x->count] = a;
X		x->count++;
X		return;
X	}
X	if(!x->flag) {
X		memset(&sb->fbuf, 0, sizeof(sb->fbuf));
X		sb->fbuf.free[0] = 0L;
X		sb->fbuf.nfree = 1;
X		sb->tfree = 0;
X		x->flag = 1;
X	}
X	addfree(dev, a, sb);
X}

static
void
xflush(Device *dev, Superb *sb, Iobuf *p)
X{
X	int i;
X	Xfree *x;

X	x = (Xfree*)p->iobuf;
X	if(!x->flag) {
X		memset(&sb->fbuf, 0, sizeof(sb->fbuf));
X		sb->fbuf.free[0] = 0L;
X		sb->fbuf.nfree = 1;
X		sb->tfree = 0;
X	}
X	for(i=0; i<x->count; i++)
X		addfree(dev, x->addr[i], sb);
X}

X/*
X * make freelist
X * from existing freelist
X * (cw devices)
X */
static
void
trfreelist(Superb *sb)
X{
X	long a;
X	int n, i;
X	Iobuf *p, *xp;
X	Fbuf *fb;


X	xp = getbuf(devnone, Cckbuf, 0);
X	memset(xp->iobuf, 0, BUFSIZE);
X	fb = &sb->fbuf;
X	p = 0;
X	for(;;) {
X		n = fb->nfree;
X		if(n < 0 || n > FEPERBUF)
X			break;
X		for(i=1; i<n; i++) {
X			a = fb->free[i];
X			if(a && !ftest(a))
X				xaddfree(dev, a, sb, xp);
X		}
X		a = fb->free[0];
X		if(!a)
X			break;
X		if(ftest(a))
X			break;
X		xaddfree(dev, a, sb, xp);
X		if(p)
X			putbuf(p);
X		p = xtag(a, Tfree, QPNONE);
X		if(!p)
X			break;
X		fb = (Fbuf*)p->iobuf;
X	}
X	if(p)
X		putbuf(p);
X	xflush(dev, sb, xp);
X	putbuf(xp);
X	mod++;
X	print("%ld blocks free\n", sb->tfree);
X}

static
void
ckfreelist(Superb *sb)
X{
X	long a, lo, hi;
X	int n, i;
X	Iobuf *p;
X	Fbuf *fb;


X	strcpy(name, "free list");
X	print("check %s\n", name);
X	fb = &sb->fbuf;
X	a = sbaddr;
X	p = 0;
X	lo = 0;
X	hi = 0;
X	for(;;) {
X		n = fb->nfree;
X		if(n < 0 || n > FEPERBUF) {
X			print("check: nfree bad %ld\n", a);
X			break;
X		}
X		for(i=1; i<n; i++) {
X			a = fb->free[i];
X			if(a && !fmark(a)) {
X				if(!lo || lo > a)
X					lo = a;
X				if(!hi || hi < a)
X					hi = a;
X			}
X		}
X		a = fb->free[0];
X		if(!a)
X			break;
X		if(fmark(a))
X			break;
X		if(!lo || lo > a)
X			lo = a;
X		if(!hi || hi < a)
X			hi = a;
X		if(p)
X			putbuf(p);
X		p = xtag(a, Tfree, QPNONE);
X		if(!p)
X			break;
X		fb = (Fbuf*)p->iobuf;
X	}
X	if(p)
X		putbuf(p);
X	print("lo = %ld; hi = %ld\n", lo, hi);
X}

X/*
X * make freelist from scratch
X */
static
void
mkfreelist(Superb *sb)
X{
X	long a;
X	int i, b;

X	if(ronly) {
X		print("cant make freelist on ronly device\n");
X		return;
X	}
X	strcpy(name, "free list");
X	memset(&sb->fbuf, 0, sizeof(sb->fbuf));
X	sb->fbuf.free[0] = 0L;
X	sb->fbuf.nfree = 1;
X	sb->tfree = 0;
X	for(a=fsize-fstart-1; a >= 0; a--) {
X		i = a/8;
X		if(i < 0 || i >= sizabits)
X			continue;
X		b = 1 << (a&7);
X		if(abits[i] & b)
X			continue;
X		addfree(dev, fstart+a, sb);
X	}
X	print("%ld blocks free\n", sb->tfree);
X	mod++;
X}

static
Dentry*
maked(long a, int s, long qpath)
X{
X	Iobuf *p;
X	Dentry *d, *d1;

X	p = xtag(a, Tdir, qpath);
X	if(!p)
X		return 0;
X	d = getdir(p, s);
X	d1 = malloc(sizeof(Dentry));
X	memmove(d1, d, sizeof(Dentry));
X	putbuf(p);
X	return d1;
X}

static
void
modd(long a, int s, Dentry *d1)
X{
X	Iobuf *p;
X	Dentry *d;

X	if(!(flags & Cbad))
X		return;
X	p = getbuf(dev, a, Bread);
X	d = getdir(p, s);
X	if(!d) {
X		if(p)
X			putbuf(p);
X		return;
X	}
X	memmove(d, d1, sizeof(Dentry));
X	p->flags |= Bmod;
X	putbuf(p);
X}

static
void
xread(long a, long qpath)
X{
X	Iobuf *p;

X	p = xtag(a, Tfile, qpath);
X	if(p)
X		putbuf(p);
X}

static
Iobuf*
xtag(long a, int tag, long qpath)
X{
X	Iobuf *p;

X	if(a == 0)
X		return 0;
X	p = getbuf(dev, a, Bread);
X	if(!p) {
X		print("check: \"%s\": xtag: p null\n", name);
X		if(flags & (Cream|Ctag)) {
X			p = getbuf(dev, a, Bmod);
X			if(p) {
X				memset(p->iobuf, 0, RBUFSIZE);
X				settag(p, tag, qpath);
X				mod++;
X				return p;
X			}
X		}
X		return 0;
X	}
X	if(checktag(p, tag, qpath)) {
X		print("check: \"%s\": xtag: checktag\n", name);
X		if(flags & (Cream|Ctag)) {
X			if(flags & Cream)
X				memset(p->iobuf, 0, RBUFSIZE);
X			settag(p, tag, qpath);
X			mod++;
X			return p;
X		}
X		return p;
X	}
X	return p;
X}

static
int
amark(long a)
X{
X	long i;
X	int b;

X	if(a < fstart || a >= fsize) {
X		if(a == 0)
X			return 0;
X		print("check: \"%s\": range %ld\n",
X			name, a);
X		nbad++;
X		return 1;
X	}
X	a -= fstart;
X	i = a/8;
X	b = 1 << (a&7);
X	if(abits[i] & b) {
X		if(!ronly) {
X			if(ndup < 10)
X				print("check: \"%s\": address dup %ld\n",
X					name, fstart+a);
X			else
X			if(ndup == 10)
X				print("...");
X		}
X		ndup++;
X		return 1;
X	}
X	abits[i] |= b;
X	nused++;
X	return 0;
X}

static
int
fmark(long a)
X{
X	long i;
X	int b;

X	if(a < fstart || a >= fsize) {
X		print("check: \"%s\": range %ld\n",
X			name, a);
X		nbad++;
X		return 1;
X	}
X	a -= fstart;
X	i = a/8;
X	b = 1 << (a&7);
X	if(abits[i] & b) {
X		print("check: \"%s\": address dup %ld\n",
X			name, fstart+a);
X		nfdup++;
X		return 1;
X	}
X	abits[i] |= b;
X	nfree++;
X	return 0;
X}

static
int
ftest(long a)
X{
X	long i;
X	int b;

X	if(a < fstart || a >= fsize)
X		return 1;
X	a -= fstart;
X	i = a/8;
X	b = 1 << (a&7);
X	if(abits[i] & b)
X		return 1;
X	abits[i] |= b;
X	return 0;
X}

static
void
missing(void)
X{
X	long a, i;
X	int b, n;

X	n = 0;
X	for(a=fsize-fstart-1; a>=0; a--) {
X		i = a/8;
X		b = 1 << (a&7);
X		if(!(abits[i] & b)) {
X			print("missing: %ld\n", fstart+a);
X			n++;
X		}
X		if(n > 10) {
X			print(" ...\n");
X			break;
X		}
X	}
X}

static
void
qmark(long qpath)
X{
X	int i, b;

X	i = qpath/8;
X	b = 1 << (qpath&7);
X	if(i < 0 || i >= sizqbits) {
X		nqbad++;
X		if(nqbad < 20)
X			print("check: \"%s\": qid out of range %lux\n",
X				name, qpath);
X		return;
X	}
X	if((qbits[i] & b) && !ronly) {
X		nqbad++;
X		if(nqbad < 20)
X			print("check: \"%s\": qid dup %lux\n",
X				name, qpath);
X	}
X	qbits[i] |= b;
X}
!
echo port/portdat.h
sed 's/^X//' >port/portdat.h <<'!'
X/*
X * fundamental constants
X */
X#define SUPER_ADDR	2		/* address of superblock */
X#define ROOT_ADDR	3		/* address of root directory */
X#define	NAMELEN		28		/* size of names */
X#define	NDBLOCK		6		/* number of direct blocks in Dentry */
X#define	MAXDAT		8192		/* max allowable data message */
X#define	MAXMSG		128		/* max size protocol message sans data */
X#define	OFFMSG		60		/* offset of msg in buffer */
X#define NDRIVE		16		/* size of drive structure */
X#define NTLOCK		200		/* number of active file Tlocks */
X#define	LRES		3		/* profiling resolution */
X#define NATTID		10		/* the last 10 ID's in attaches */
X#define	C0a		59		/* time constants for filters */
X#define	C0b		60
X#define	C1a		599
X#define	C1b		600
X#define	C2a		5999
X#define	C2b		6000

X/*
X * derived constants
X */
X#define	BUFSIZE		(RBUFSIZE-sizeof(Tag))
X#define DIRPERBUF	(BUFSIZE/sizeof(Dentry))
X#define INDPERBUF	(BUFSIZE/sizeof(long))
X#define INDPERBUF2	(INDPERBUF*INDPERBUF)
X#define FEPERBUF	((BUFSIZE-sizeof(Super1)-sizeof(long))/sizeof(long))
X#define	SMALLBUF	(MAXMSG)
X#define	LARGEBUF	(MAXMSG+MAXDAT+256)
X#define	RAGAP		(300*1024)/BUFSIZE		/* readahead parameter */
X#define CEPERBK		((BUFSIZE-BKPERBLK*sizeof(long))/\
X				(sizeof(Centry)*BKPERBLK))
X#define	BKPERBLK	10

typedef struct	Alarm	Alarm;
typedef struct	Auth	Auth;
typedef	struct	Conf	Conf;
typedef	struct	Label	Label;
typedef	struct	Lock	Lock;
typedef	struct	Mach	Mach;
typedef	struct	QLock	QLock;
typedef	struct	Ureg	Ureg;
typedef	struct	User	User;
typedef	struct	Fbuf	Fbuf;
typedef	struct	Super1	Super1;
typedef	struct	Superb	Superb;
typedef	struct	Filsys	Filsys;
typedef	struct	Startsb	Startsb;
typedef	struct	Dentry	Dentry;
typedef	struct	Tag	Tag;
typedef struct  Talarm	Talarm;
typedef	struct	Uid	Uid;
typedef struct	Device	Device;
typedef struct	Qid9p1	Qid9p1;
typedef	struct	Iobuf	Iobuf;
typedef	struct	Wpath	Wpath;
typedef	struct	File	File;
typedef	struct	Chan	Chan;
typedef	struct	Cons	Cons;
typedef	struct	Time	Time;
typedef	struct	Tm	Tm;
typedef	struct	Rtc	Rtc;
typedef	struct	Hiob	Hiob;
typedef	struct	RWlock	RWlock;
typedef	struct	Msgbuf	Msgbuf;
typedef	struct	Queue	Queue;
typedef	struct	Command	Command;
typedef	struct	Flag	Flag;
typedef	struct	Bp	Bp;
typedef	struct	Rabuf	Rabuf;
typedef	struct	Rendez	Rendez;
typedef	struct	Filter	Filter;
typedef		ulong	Float;
typedef	struct	Tlock	Tlock;
typedef	struct	Cache	Cache;
typedef	struct	Centry	Centry;
typedef	struct	Bucket	Bucket;

struct	Lock
X{
X	ulong*	sbsem;		/* addr of sync bus semaphore */
X	ulong	pc;
X	ulong	sr;
X};

struct	Rendez
X{
X	Lock;
X	User*	p;
X};

struct	Filter
X{
X	ulong	count;			/* count and old count kept separate */
X	ulong	oldcount;		/*	so interrput can read them */
X	int	c1;			/* time const multiplier */
X	int	c2;			/* time const divider */
X	int	c3;			/* scale for printing */
X	Float	filter;			/* filter */
X};

struct	QLock
X{
X	Lock;			/* to use object */
X	User*	head;		/* next process waiting for object */
X	User*	tail;		/* last process waiting for object */
X	char*	name;		/* for diagnostics */
X	int	locked;		/* flag, is locked */
X};

struct	RWlock
X{
X	int	nread;
X	QLock	wr;
X	QLock	rd;
X};

X/*
X * send/recv queue structure
X */
struct	Queue
X{
X	Lock;			/* to manipulate values */
X	int	size;		/* size of queue */
X	int	loc;		/* circular pointer */
X	int	count;		/* how many in queue */
X	User*	rhead;		/* process's waiting for send */
X	User*	rtail;
X	User*	whead;		/* process's waiting for recv */
X	User*	wtail;
X	void*	args[1];	/* list of saved pointers, [->size] */
X};

struct	Tag
X{
X	short	pad;
X	short	tag;
X	long	path;
X};

struct	Device
X{
X	uchar	type;
X	uchar	init;
X	Device*	link;			/* link for mcat/mlev/mirror */
X	Device*	dlink;			/* link all devices */
X	void*	private;
X	long	size;
X	union
X	{
X		struct			/* worm wren, (l)worm in targ */
X		{
X			int	ctrl;
X			int	targ;
X			int	lun;
X		} wren;
X		struct			/* ata/ide */
X		{
X			int	ctrl;
X			int	unit;
X		} ata;
X		struct			/* mcat mlev mirror */
X		{
X			Device*	first;
X			Device*	last;
X			int	ndev;
X		} cat;
X		struct			/* cw */
X		{
X			Device*	c;
X			Device*	w;
X			Device*	ro;
X		} cw;
X		struct			/* juke */
X		{
X			Device*	j;	/* (robotics, worm drives) - wrens */
X			Device*	m;	/* (sides) - r or l devices */
X		} j;
X		struct			/* ro */
X		{
X			Device*	parent;
X		} ro;
X		struct			/* fworm */
X		{
X			Device*	fw;
X		} fw;
X		struct			/* part */
X		{
X			Device*	d;
X			long	base;
X			long	size;
X		} part;
X		struct			/* byte-swapped */
X		{
X			Device*	d;
X		} swab;
X	};
X};

typedef struct Sidestarts {
X	long	sstart;			/* blocks before start of side */
X	long	s1start;		/* blocks before start of next side */
X} Sidestarts;

struct	Rabuf
X{
X	union
X	{
X		struct
X		{
X			Device*	dev;
X			long	addr;
X		};
X		Rabuf*	link;
X	};
X};

typedef
struct Qid
X{
X	uvlong	path;
X	ulong	vers;
X	uchar	type;
X} Qid;

X/* bits in Qid.type */
X#define QTDIR		0x80		/* type bit for directories */
X#define QTAPPEND	0x40		/* type bit for append only files */
X#define QTEXCL		0x20		/* type bit for exclusive use files */
X#define QTMOUNT		0x10		/* type bit for mounted channel */
X#define QTAUTH		0x08		/* type bit for authentication file */
X#define QTFILE		0x00		/* plain file */

X/* DONT TOUCH, this is the disk structure */
struct	Qid9p1
X{
X	long	path;
X	long	version;
X};

struct	Hiob
X{
X	Iobuf*	link;
X	Lock;
X};

struct	Chan
X{
X	char	type;			/* major driver type i.e. Dev* */
X	int	(*protocol)(Msgbuf*);	/* version */
X	int	msize;			/* version */
X	char	whochan[50];
X	char	whoname[NAMELEN];
X	void	(*whoprint)(Chan*);
X	ulong	flags;
X	int	chan;			/* overall channel number, mostly for printing */
X	int	nmsgs;			/* outstanding messages, set under flock -- for flush */
X	ulong	whotime;
X	Filter	work;
X	Filter	rate;
X	int	nfile;			/* used by cmd_files */
X	RWlock	reflock;
X	Chan*	next;			/* link list of chans */
X	Queue*	send;
X	Queue*	reply;

X	uchar	authinfo[64];

X	void*	ifc;
X	void*	pdata;
X};

struct	Filsys
X{
X	char*	name;			/* name of filsys */
X	char*	conf;			/* symbolic configuration */
X	Device*	dev;			/* device that filsys is on */
X	int	flags;
X		#define	FREAM		(1<<0)	/* mkfs */
X		#define	FRECOVER	(1<<1)	/* install last dump */
X		#define	FEDIT		(1<<2)	/* modified */
X};

struct	Startsb
X{
X	char*	name;
X	long	startsb;
X};

struct	Time
X{
X	ulong	lasttoy;
X	long	bias;
X	long	offset;
X};

X/*
X * array of qids that are locked
X */
struct	Tlock
X{
X	Device*	dev;
X	ulong	time;
X	long	qpath;
X	File*	file;
X};

struct	Cons
X{
X	ulong	flags;		/* overall flags for all channels */
X	QLock;			/* generic qlock for mutex */
X	int	uid;		/* botch -- used to get uid on cons_create */
X	int	gid;		/* botch -- used to get gid on cons_create */
X	int	nuid;		/* number of uids */
X	int	ngid;		/* number of gids */
X	long	offset;		/* used to read files, c.f. fchar */
X	int	chano;		/* generator for channel numbers */
X	Chan*	chan;		/* console channel */
X	Filsys*	curfs;		/* current filesystem */

X	int	profile;	/* are we profiling? */
X	long*	profbuf;
X	ulong	minpc;
X	ulong	maxpc;
X	ulong	nprofbuf;

X	long	nlarge;		/* number of large message buffers */
X	long	nsmall;		/* ... small ... */
X	long	nwormre;	/* worm read errors */
X	long	nwormwe;	/* worm write errors */
X	long	nwormhit;	/* worm read cache hits */
X	long	nwormmiss;	/* worm read cache non-hits */
X	int	noage;		/* dont update cache age, dump and check */
X	long	nwrenre;	/* disk read errors */
X	long	nwrenwe;	/* disk write errors */
X	long	nreseq;		/* cache bucket resequence */

X	Filter	work[3];	/* thruput in messages */
X	Filter	rate[3];	/* thruput in bytes */
X	Filter	bhit[3];	/* getbufs that hit */
X	Filter	bread[3];	/* getbufs that miss and read */
X	Filter	brahead[3];	/* messages to readahead */
X	Filter	binit[3];	/* getbufs that miss and dont read */
X};

struct	File
X{
X	QLock;
X	Qid	qid;
X	Wpath*	wpath;
X	Chan*	cp;		/* null means a free slot */
X	Tlock*	tlock;		/* if file is locked */
X	File*	next;		/* in cp->flist */
X	Filsys*	fs;
X	long	addr;
X	long	slot;
X	long	lastra;		/* read ahead address */
X	ulong	fid;
X	short	uid;
X	Auth	*auth;
X	char	open;
X		#define	FREAD	1
X		#define	FWRITE	2
X		#define	FREMOV	4

X	long	doffset;	/* directory reading */
X	ulong	dvers;
X	long	dslot;
X};

struct	Wpath
X{
X	Wpath*	up;		/* pointer upwards in path */
X	long	addr;		/* directory entry addr */
X	long	slot;		/* directory entry slot */
X	short	refs;		/* number of files using this structure */
X};

struct	Iobuf
X{
X	QLock;
X	Device*	dev;
X	Iobuf*	fore;		/* for lru */
X	Iobuf*	back;		/* for lru */
X	char*	iobuf;		/* only active while locked */
X	char*	xiobuf;		/* "real" buffer pointer */
X	long	addr;
X	int	flags;
X};

struct	Uid
X{
X	short	uid;		/* user id */
X	short	lead;		/* leader of group */
X	short	*gtab;		/* group table */
X	int	ngrp;		/* number of group entries */
X	char	name[NAMELEN];	/* user name */
X};

X/* DONT TOUCH, this is the disk structure */
struct	Dentry
X{
X	char	name[NAMELEN];
X	short	uid;
X	short	gid;
X	ushort	mode;
X		#define	DALLOC	0x8000
X		#define	DDIR	0x4000
X		#define	DAPND	0x2000
X		#define	DLOCK	0x1000
X		#define	DREAD	0x4
X		#define	DWRITE	0x2
X		#define	DEXEC	0x1
X	short	muid;
X	Qid9p1	qid;
X	long	size;
X	long	dblock[NDBLOCK];
X	long	iblock;
X	long	diblock;
X	long	atime;
X	long	mtime;
X};

X/* DONT TOUCH, this is the disk structure */
struct	Super1
X{
X	long	fstart;
X	long	fsize;
X	long	tfree;
X	long	qidgen;		/* generator for unique ids */
X	/*
X	 * Stuff for WWC device
X	 */
X	long	cwraddr;	/* cfs root addr */
X	long	roraddr;	/* dump root addr */
X	long	last;		/* last super block addr */
X	long	next;		/* next super block addr */
X};

X/* DONT TOUCH, this is the disk structure */
struct	Fbuf
X{
X	long	nfree;
X	long	free[FEPERBUF];
X};

X/* DONT TOUCH, this is the disk structure */
struct	Superb
X{
X	Fbuf	fbuf;
X	Super1;
X};

struct	Label
X{
X	ulong	pc;
X	ulong	sp;
X};

struct	Alarm
X{
X	Lock;
X	Alarm*	next;
X	int	busy;
X	int	dt;		/* in ticks */
X	void	(*f)(Alarm*, void*);
X	void*	arg;
X};

struct Talarm
X{
X	Lock;
X	User	*list;
X};

struct	Conf
X{
X	ulong	nmach;		/* processors */
X	ulong	nproc;		/* processes */
X	ulong	mem;		/* total physical bytes of memory */
X	ulong	sparemem;	/* memory left for check/dump and chans */
X	ulong	nalarm;		/* alarms */
X	ulong	nuid;		/* distinct uids */
X	ulong	nserve;		/* server processes */
X	ulong	nfile;		/* number of fid -- system wide */
X	ulong	nwpath;		/* number of active paths, derrived from nfile */
X	ulong	gidspace;	/* space for gid names -- derrived from nuid */
X	ulong	nlgmsg;		/* number of large message buffers */
X	ulong	nsmmsg;		/* number of small message buffers */
X	ulong	wcpsize;	/* memory for worm copies; unused in 4e */
X	ulong	recovcw;	/* recover addresses */
X	ulong	recovro;
X	ulong	firstsb;
X	ulong	recovsb;
X	ulong	nauth;		/* number of Auth structs */
X	uchar	nodump;		/* no periodic dumps */
X	uchar	ripoff;
X	uchar	dumpreread;	/* read and compare in dump copy */

X	ulong	npage0;		/* total physical pages of memory */
X	ulong	npage1;		/* total physical pages of memory */
X	ulong	base0;		/* base of bank 0 */
X	ulong	base1;		/* base of bank 1 */
X};

X/*
X * message buffers
X * 2 types, large and small
X */
struct	Msgbuf
X{
X	short	count;
X	short	flags;
X		#define	LARGE	(1<<0)
X		#define	FREE	(1<<1)
X		#define BFREE	(1<<2)
X		#define BTRACE	(1<<7)
X		#define Mbrcvbuf (1<<15) /* to free, call (*free)(this) */
X	Chan*	chan;
X	Msgbuf*	next;
X	ulong	param;
X	int	category;
X	uchar*	data;		/* rp or wp: current processing point */
X	uchar*	xdata;		/* base of allocation */
X	/* added for cpu kernel compatibility - geoff */
X	void	(*free)(Msgbuf *);
X};

X/*
X * message buffer categories
X */
enum
X{
X	Mxxx		= 0,
X	Mbreply1,
X	Mbreply2,
X	Mbreply3,
X	Mbreply4,
X	Mbarp1,
X	Mbarp2,
X	Mbip1,
X	Mbip2,
X	Mbip3,
X	Mbil1,
X	Mbil2,
X	Mbil3,
X	Mbil4,
X	Mbilauth,
X	Maeth1,
X	Maeth2,
X	Maeth3,
X	Mbeth1,
X	Mbeth2,
X	Mbeth3,
X	Mbeth4,
X	Mbsntp,
X	MAXCAT,
X};

struct	Mach
X{
X	int	machno;		/* physical id of processor */
X	int	mmask;		/* 1<<m->machno */
X	ulong	ticks;		/* of the clock since boot time */
X	int	lights;		/* light lights, this processor */
X	User*	proc;		/* current process on this processor */
X	Label	sched;		/* scheduler wakeup */
X	Lock	alarmlock;	/* access to alarm list */
X	void*	alarm;		/* alarms bound to this clock */
X	void	(*intr)(Ureg*, ulong);	/* pending interrupt */
X	User*	intrp;		/* process that was interrupted */
X	ulong	cause;		/* arg to intr */
X	Ureg*	ureg;		/* arg to intr */
X	uchar	stack[1];
X};

X#define	MAXSTACK 4000
X#define	NHAS	100
struct	User
X{
X	Label	sched;
X	Mach*	mach;		/* machine running this proc */
X	User*	rnext;		/* next process in run queue */
X	User*	qnext;		/* next process on queue for a QLock */
X	void	(*start)(void);	/* startup function */
X	char*	text;		/* name of this process */
X	void*	arg;
X	Filter	time[3];	/* cpu time used */
X	int	exiting;
X	int	pid;
X	int	state;
X	Rendez	tsleep;

X	ulong	twhen;
X	Rendez	*trend;
X	User	*tlink;
X	int	(*tfn)(void*);

X	struct
X	{
X		QLock*	q[NHAS];/* list of locks this process has */
X		QLock*	want;	/* lock waiting */
X	} has;
X	uchar	stack[MAXSTACK];
X};

X#define	PRINTSIZE	256
struct
X{
X	Lock;
X	int	machs;
X	int	exiting;
X} active;

struct	Command
X{
X	char*	arg0;
X	char*	help;
X	void	(*func)(int, char*[]);
X};

struct	Flag
X{
X	char*	arg0;
X	char*	help;
X	ulong	flag;
X};

struct	Tm
X{
X	/* see ctime(3) */
X	int	sec;
X	int	min;
X	int	hour;
X	int	mday;
X	int	mon;
X	int	year;
X	int	wday;
X	int	yday;
X	int	isdst;
X};

struct	Rtc
X{
X	int	sec;
X	int	min;
X	int	hour;
X	int	mday;
X	int	mon;
X	int	year;
X};

X/*
X * cw device
X */

X/* DONT TOUCH, this is the disk structure */
struct	Cache
X{
X	long	maddr;		/* cache map addr */
X	long	msize;		/* cache map size in buckets */
X	long	caddr;		/* cache addr */
X	long	csize;		/* cache size */
X	long	fsize;		/* current size of worm */
X	long	wsize;		/* max size of the worm */
X	long	wmax;		/* highwater write */

X	long	sbaddr;		/* super block addr */
X	long	cwraddr;	/* cw root addr */
X	long	roraddr;	/* dump root addr */

X	long	toytime;	/* somewhere convienent */
X	long	time;
X};

X/* DONT TOUCH, this is the disk structure */
struct	Centry
X{
X	ushort	age;
X	short	state;
X	long	waddr;		/* worm addr */
X};

X/* DONT TOUCH, this is the disk structure */
struct	Bucket
X{
X	long	agegen;		/* generator for ages in this bkt */
X	Centry	entry[CEPERBK];
X};

X/*
X * scsi i/o
X */
enum
X{
X	SCSIread = 0,
X	SCSIwrite = 1,
X};

X/*
X * Process states
X */
enum
X{
X	Dead = 0,
X	Moribund,
X	Zombie,
X	Ready,
X	Scheding,
X	Running,
X	Queueing,
X	Sending,
X	Recving,
X	MMUing,
X	Exiting,
X	Inwait,
X	Wakeme,
X	Broken,
X};

X/*
X * Lights
X */
enum
X{
X	Lreal	= 0,	/* blink in clock interrupt */
X	Lintr,		/* on while in interrupt */
X	Lpanic,		/* in panic */
X	Lcwmap,		/* in cw lookup */
X};

X/*
X * devnone block numbers
X */
enum
X{
X	Cwio1 	= 1,
X	Cwio2,
X	Cwxx1,
X	Cwxx2,
X	Cwxx3,
X	Cwxx4,
X	Cwdump1,
X	Cwdump2,
X	Cuidbuf,
X	Cckbuf,
X};

X/*
X * error codes generated from the file server
X */
enum
X{
X	Ebadspc = 1,
X	Efid,
X	Echar,
X	Eopen,
X	Ecount,
X	Ealloc,
X	Eqid,
X	Eaccess,
X	Eentry,
X	Emode,
X	Edir1,
X	Edir2,
X	Ephase,
X	Eexist,
X	Edot,
X	Eempty,
X	Ebadu,
X	Enoattach,
X	Ewstatb,
X	Ewstatd,
X	Ewstatg,
X	Ewstatl,
X	Ewstatm,
X	Ewstato,
X	Ewstatp,
X	Ewstatq,
X	Ewstatu,
X	Ewstatv,
X	Ename,
X	Ewalk,
X	Eronly,
X	Efull,
X	Eoffset,
X	Elocked,
X	Ebroken,
X	Eauth,
X	Eauth2,
X	Efidinuse,
X	Etoolong,
X	Econvert,
X	Eversion,
X	Eauthdisabled,
X	Eauthnone,
X	Eedge,
X	MAXERR
X};

X/*
X * device types
X */
enum
X{
X	Devnone 	= 0,
X	Devcon,			/* console */
X	Devwren,		/* scsi disk drive */
X	Devworm,		/* scsi video drive */
X	Devlworm,		/* scsi video drive (labeled) */
X	Devfworm,		/* fake read-only device */
X	Devjuke,		/* jukebox */
X	Devcw,			/* cache with worm */
X	Devro,			/* readonly worm */
X	Devmcat,		/* multiple cat devices */
X	Devmlev,		/* multiple interleave devices */
X	Devil,			/* internet link */
X	Devpart,		/* partition */
X	Devfloppy,		/* floppy drive */
X	Devide,			/* IDE drive */
X	Devswab,		/* swab data between mem and device */
X	Devdup,			/* Dup drive */
X	Devmirr,		/* mirror devices */
X	MAXDEV
X};

X/*
X * tags on block
X */
X/* DONT TOUCH, this is in disk structures */
enum
X{
X	Tnone		= 0,
X	Tsuper,			/* the super block */
X	Tdir,			/* directory contents */
X	Tind1,			/* points to blocks */
X	Tind2,			/* points to Tind1 */
X	Tfile,			/* file contents */
X	Tfree,			/* in free list */
X	Tbuck,			/* cache fs bucket */
X	Tvirgo,			/* fake worm virgin bits */
X	Tcache,			/* cw cache things */
X	Tconfig,		/* configuration block */
X	MAXTAG
X};

X/*
X * flags to getbuf
X */
enum
X{
X	Bread	= (1<<0),	/* read the block if miss */
X	Bprobe	= (1<<1),	/* return null if miss */
X	Bmod	= (1<<2),	/* buffer is dirty, needs writing */
X	Bimm	= (1<<3),	/* write immediately on putbuf */
X	Bres	= (1<<4),	/* reserved, never renammed */
X};

extern	register	Mach*	m;
extern	register	User*	u;
extern  Talarm		talarm;

Conf	conf;
Cons	cons;
X#define	MACHP(n)	((Mach*)(MACHADDR+n*BY2PG))

X#pragma	varargck	type	"Z"	Device*
X#pragma	varargck	type	"T"	ulong
X#pragma	varargck	type	"I"	uchar*
X#pragma	varargck	type	"E"	uchar*
X#pragma	varargck	type	"W"	Filter*
X#pragma	varargck	type	"G"	int

extern int (*fsprotocol[])(Msgbuf*);
!
echo port/portfns.h
sed 's/^X//' >port/portfns.h <<'!'
void	accessdir(Iobuf*, Dentry*, int, int);
void	addfree(Device*, long, Superb*);
Alarm*	alarm(int, void(*)(Alarm*, void*), void*);
void	alarminit(void);
void	arpstart(void);
void	arginit(void);
char*	authaname(Auth*);
void	authinit(void);
void	authfree(Auth*);
Auth*	authnew(char*, char*);
int	authread(File*, uchar*, int);
int	authuid(Auth*);
char*	authuname(Auth*);
int	authwrite(File*, uchar*, int);
void	cdiag(char*, int);
int	cnumb(void);
Device*	config(void);
int	rawchar(int);
long	bufalloc(Device*, int, long, int);
void	buffree(Device*, long, int);
int	byuid(void*, void*);
void	cancel(Alarm*);
int	canlock(Lock*);
int	canqlock(QLock*);
void	cfsdump(Filsys*);
Chan*	chaninit(int, int, int);
void	cmd_check(int, char*[]);
void	cmd_users(int, char*[]);
void	cmd_newuser(int, char*[]);
void	cmd_netdb(int, char*[]);
void	cmd_passwd(int, char*[]);
void	cmd_printconf(int, char*[]);
int	checkname(char*);
int	checktag(Iobuf*, int, long);
int	cksum(void*, int, int);
int	cksum0(int, int);
void	clock(ulong, ulong);
void	clockinit(void);
void	clockreload(ulong);
void	cyclstart(void);
void	dotrace(int);
int	conschar(void);
void	consinit(void (*)(char*, int));
void	consreset(void);
void	consstart(int);
int	(*consgetc)(void);
void	(*consputc)(int);
void	(*consputs)(char*, int);
void	consserve(void);
int	conslock(void);
int	con_attach(int, char*, char*);
int	con_clone(int, int);
int	con_create(int, char*, int, int, long, int);
int	con_clri(int);
int	con_fstat(int);
int	con_open(int, int);
int	con_read(int, char*, long, int);
int	con_remove(int);
void	con_rintr(int);
int	con_session(void);
int	con_walk(int, char*);
int	con_write(int, char*, long, int);
int	cwgrow(Device*, Superb*, int);
int	cwfree(Device*, long);
void	cwinit(Device*);
long	cwraddr(Device*);
int	cwread(Device*, long, void*);
void	cwream(Device*);
void	cwrecover(Device*);
long	cwsaddr(Device*);
long	cwsize(Device*);
long	dbufread(Iobuf*, Dentry*, long, long, int);
int	cwwrite(Device*, long, void*);
void	datestr(char*, ulong);
void	delay(int);
int	devcmpr(Device*, Device*);
void	devream(Device*, int);
void	devrecover(Device*);
void	devinit(Device*);
int	devread(Device*, long, void*);
long	devsize(Device*);
int	devwrite(Device*, long, void*);
Iobuf*	dnodebuf(Iobuf*, Dentry*, long, int, int);
Iobuf*	dnodebuf1(Iobuf*, Dentry*, long, int, int);
void	dofilter(Filter*, int, int, int);
int	doremove(File*, int);
void	dtrunc(Iobuf*, Dentry*, int);
int	dumpblock(Device*);
void	dumpregs(Ureg*);
void	dumpstack(User*);
void	exit(void);
XFloat	famd(Float, int, int, int);
ulong	fdf(Float, int);
void	fileinit(Chan*);
XFile*	filep(Chan*, ulong, int);
void	firmware(void);
int	fname(char*);
int	fpair(char*, char*);
void	formatinit(void);
int	fread(void*, int);
void	freealarm(Alarm*);
void	freefp(File*);
void	freewp(Wpath*);
XFilsys*	fsstr(char*);
long	fwormsize(Device*);
void	fwormream(Device*);
void	fworminit(Device*);
int	fwormread(Device*, long, void*);
int	fwormwrite(Device*, long, void*);
char*	getauthlist(void);
Iobuf*	getbuf(Device*, long, int);
void*	getarg(void);
char*	getwd(char*, char*);
int	getc(void);
ulong	getcallerpc(void*);
Dentry*	getdir(Iobuf*, int);
Chan*	getlcp(uchar*, long);
long	getraddr(Device*);
void	getstring(char*, int, int);
void	gotolabel(Label*);
void	hexdump(void*, int);
int	iaccess(File*, Dentry*, int);
void*	ialloc(ulong, int);
void	ilock(Lock*);
void	iunlock(Lock*);
long	indfetch(Device*, long, long, long , int, int, int);
int	ingroup(int, int);
int	inh(int, uchar*);
void	init0(void);
void	iobufinit(void);
void*	iobufmap(Iobuf*);
void	iobufunmap(Iobuf*);
int	iobufql(QLock*);
int	jukeread(Device*, long, void*);
int	jukewrite(Device*, long, void*);
void	jukeinit(Device*);
void	jukeream(Device*);
void	jukerecover(Device*);
long	jukesaddr(Device*);
long	jukesize(Device*);
void	kbdchar(int);
void	lights(int, int);
void	launchinit(void);
void	localconfinit(void);
int	leadgroup(int, int);
void	lock(Lock*);
void	lockinit(void);
void	machinit(void);
Msgbuf*	mballoc(int, Chan*, int);
void	mbinit(void);
void	mbfree(Msgbuf*);
ulong	meminit(void);
Iobuf*	movebuf(Iobuf*);
void	mcatinit(Device*);
int	mcatread(Device*, long, void*);
long	mcatsize(Device*);
int	mcatwrite(Device*, long, void*);
void	mirrinit(Device*);
int	mirrread(Device*, long, void*);
long	mirrsize(Device*);
int	mirrwrite(Device*, long, void*);
void	mkqid(Qid*, Dentry*, int);
int	mkqidcmp(Qid*, Dentry*);
void	mkqid9p1(Qid9p1*, Qid*);
void	mkqid9p2(Qid*, Qid9p1*, int);
void	mlevinit(Device*);
int	mlevread(Device*, long, void*);
long	mlevsize(Device*);
int	mlevwrite(Device*, long, void*);
int	nametokey(char*, char*);
Alarm*	newalarm(void);
XFile*	newfp(void);
User*	newproc(void);
Queue*	newqueue(int);
void	newstart(void);
Wpath*	newwp(void);
Auth*	newauth(void);
int	nvrcheck(void);
int	nvread(int, void*, int);
char*	nvrgetconfig(void);
int	nvrsetconfig(char*);
int	nvwrite(int, void*, int);
int	walkto(char*);
int	no(void*);
long	number(char*, int, int);
void	online(void);
void	otherinit(void);
void	panic(char*, ...);
void	partinit(Device*);
int	partread(Device*, long, void*);
long	partsize(Device*);
int	partwrite(Device*, long, void*);
void	prdate(void);
void	preread(Device*, long);
void	prflush(void);
int	prime(long);
void	printinit(void);
void	procinit(void);
void	putbuf(Iobuf*);
long	qidpathgen(Device*);
void	qlock(QLock*);
void	qunlock(QLock*);
void	rahead(void);
void	ready(User*);
void	ream(Filsys*);
void*	recv(Queue*, int);
void	restartprint(Alarm*);
void	rlock(RWlock*);
void	rootream(Device*, long);
int	roread(Device*, long, void*);
void	rstate(Chan*, int);
ulong	rtc2sec(Rtc *);
void	runlock(RWlock*);
User*	runproc(void);
void	sched(void);
void	schedinit(void);
int	scsiio(Device*, int, uchar*, int, void*, int);
void	sec2rtc(ulong, Rtc *);
void	send(Queue*, void*);
void	serve(void);
int	serve9p1(Msgbuf*);
int	serve9p2(Msgbuf*);
int	setlabel(Label*);
void	settag(Iobuf*, int, long);
void	settime(ulong);
void	sleep(Rendez*, int(*)(void*), void*);
void	sntpinit(void);
int	splhi(void);
int	spllo(void);
void	splx(int);
void	startprint(void);
int	strtouid(char*);
long	superaddr(Device*);
void	superream(Device*, long);
void	swab(void*, int);
void	sync(char*);
int	syncblock(void);
long	syscall(Ureg*);
void	sysinit(void);
int	Tfmt(Fmt*);
ulong	time(void);
ulong	nextime(ulong, int, int);
Tlock*	tlocked(Iobuf*, Dentry*);
void	tsleep(Rendez*, int(*)(void*), void*, int);
void	touser(void);
ulong	toytime(void);
ulong	rtctime(void);
void	setrtc(ulong);
void	uidtostr(char*, int, int);
Uid*	uidpstr(char*);
void	unlock(Lock*);
void	userinit(void(*)(void), void*, char*);
void	vecinit(void);
void	wakeup(Rendez*);
void	wbflush(void);
void	wlock(RWlock*);
void	wormcopy(void);
void	wormprobe(void);
int	dowcp(void);
int	dowcmp(void);
void	synccopy(void);
void	waitsec(int);
long	wormsearch(Device*, int, long, long);
int	wormread(Device*, long, void*);
long	wormsize(Device*);
long	wormsizeside(Device *, int side);
void	wormsidestarts(Device *dev, int side, Sidestarts *stp);
int	wormwrite(Device*, long, void*);
void	wreninit(Device*);
int	wrenread(Device*, long, void*);
long	wrensize(Device*);
int	wrenwrite(Device*, long, void*);
void	wunlock(RWlock*);
void 	cmd_exec(char*);
void	cmd_install(char*, char*, void (*)(int, char*[]));
ulong	flag_install(char*, char*);

int	chartoip(uchar *, char *);
int	isvalidip(uchar*);

int	nhgets(uchar*);
long	nhgetl(uchar*);
void	hnputs(uchar*, int);
void	hnputl(uchar*, long);

void	pokewcp(void);
!

^ permalink raw reply	[flat|nested] 2+ messages in thread

* [9fans] jukebox configuration changes
@ 2002-11-01  9:43 Geoff Collyer
  0 siblings, 0 replies; 2+ messages in thread
From: Geoff Collyer @ 2002-11-01  9:43 UTC (permalink / raw)
  To: 9fans

I've just modified the file server kernel to cope with different
DSIZEs (optical disc sizes) per jukebox and even per disc.  It's now
also possible to change FIXEDSIZES in /sys/src/fs/*/9*.c rather than
having to edit dev/juke.c before compiling each distinct kernel.

The way it used to work was that DSIZE was the size in RBUFSIZE-byte
blocks of each optical disc *side*.  However, it is possible to use
discs of different sizes within a single jukebox: you set FIXEDSIZES
to 0 and pay with a delay at each boot while the file server pulls
each disc side in and asks the drive how big it is.  It's probably
wiser to use discs of the same size and set FIXEDSIZES to 1, which
makes the file server just look at one disc and assume that all the
sides are the size of its.  So DSIZE was only used for computing the
statistics that "statw" prints on the console, for working out sizes
in the cache save and restore commands, and in the implementation of
"check touch".  Those operations could be led astray if DSIZE wasn't
the size in Plan 9 blocks of all the optical discs in all jukeboxes on
a given file server.  If one wanted to put a jukebox with 5.2GB discs
and one with 2.6GB discs on the same file server, it wouldn't work
perfectly.

DSIZE is gone.  It's now possible to mix jukeboxes with discs of
different sizes, and even, if you're willing to put up with the
delays, to use discs of different sizes within a single jukebox, and
everything should now work right.

The whole thing compiles but I want to test it tomorrow (after the
third jukebox arrives) before I offer the code to anyone else.


^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2002-11-02  9:37 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2002-11-02  9:37 [9fans] jukebox configuration changes Geoff Collyer
  -- strict thread matches above, loose matches on Subject: below --
2002-11-01  9:43 Geoff Collyer

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).