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

enum {
	Slop = 256,	/* room at the start of a message buf for proto hdrs */
};

Filsys*
fsstr(char *p)
{
	Filsys *fs;

	for(fs=filsys; fs->name; fs++)
		if(strcmp(fs->name, p) == 0)
			return fs;
	return 0;
}

Filsys*
dev2fs(Device *dev)
{
	Filsys *fs;

	for(fs=filsys; fs->name; fs++)
		if(fs->dev == dev)
			return fs;
	return 0;
}

/*
 * allocate 'count' contiguous channels
 * of type 'type' and return pointer to base
 */
Chan*
fs_chaninit(int count, int data)
{
	uchar *p;
	Chan *cp, *icp;
	int i;

	p = ialloc((uintptr)count * (sizeof(Chan)+data), 0);
	icp = (Chan*)p;
	for(i = 0; i < count; i++) {
		cp = (Chan*)p;
		cp->next = chans;
		chans = cp;
		cp->chan = cons.chano;
		cons.chano++;
		snprint(cp->whoname, sizeof(cp->whoname), "<none>");
		wlock(&cp->reflock);
		wunlock(&cp->reflock);
		rlock(&cp->reflock);
		runlock(&cp->reflock);

		p += sizeof(Chan);
		if(data){
			cp->pdata = p;
			p += data;
		}
	}
	return icp;
}

void
fileinit(Chan *cp)
{
	File *f, *prev;
	Tlock *t;
	int h;

loop:
	lock(&flock);
	for (h=0; h < nelem(flist); h++)
		for (prev=0, f = flist[h]; f; prev=f, f=f->next) {
			if(f->cp != cp)
				continue;
			if(prev) {
				prev->next = f->next;
				f->next = flist[h];
				flist[h] = f;
			}
			flist[h] = f->next;
			unlock(&flock);

			qlock(f);
			if(t = f->tlock) {
				if(t->file == f)
					t->time = 0;	/* free the lock */
				f->tlock = 0;
			}
			if(f->open & FREMOV)
				doremove(f);
			freewp(f->wpath);
			f->open = 0;
			authfree(f->auth);
			f->auth = 0;
			f->cp = 0;
			qunlock(f);
			goto loop;
		}
	unlock(&flock);
}

/*
 * returns a locked file structure
 */
File*
filep(Chan *cp, ulong fid, int flag)
{
	File *f;
	int h;

	if(fid == NOFID)
		return 0;

	h = (long)(uintptr)cp + fid;
	if(h < 0)
		h = ~h;
	h %= nelem(flist);

loop:
	lock(&flock);
	for(f=flist[h]; f; f=f->next)
		if(f->fid == fid && f->cp == cp){
			/*
			 * Already in use is an error
			 * when called from attach or clone (walk
			 * in 9P2000). The console uses FID[12] and
			 * never clunks them so catch that case.
			 */
			if(flag == 0 || cp == cons.chan)
				goto out;
			unlock(&flock);
			return 0;
		}

	if(flag) {
		f = newfp();
		if(f) {
			f->fid = fid;
			f->cp = cp;
			f->wpath = 0;
			f->tlock = 0;
			f->doffset = 0;
			f->dslot = 0;
			f->auth = 0;
			f->next = flist[h];
			flist[h] = f;
			goto out;
		}
	}
	unlock(&flock);
	return 0;

out:
	unlock(&flock);
	qlock(f);
	if(f->fid == fid && f->cp == cp)
		return f;
	qunlock(f);
	goto loop;
}

/*
 * always called with flock locked
 */
File*
newfp(void)
{
	static int first;
	File *f;
	int start, i;

	i = first;
	start = i;
	do {
		f = &files[i];
		i++;
		if(i >= conf.nfile)
			i = 0;
		if(f->cp)
			continue;
		first = i;
		return f;
	} while(i != start);

	fprint(2, "out of files\n");
	return 0;
}

void
freefp(File *fp)
{
	Chan *cp;
	File *f, *prev;
	int h;

	if(!fp || !(cp = fp->cp))
		return;

	h = (long)(uintptr)cp + fp->fid;
	if(h < 0)
		h = ~h;
	h %= nelem(flist);

	lock(&flock);
	for(prev=0,f=flist[h]; f; prev=f,f=f->next)
		if(f == fp) {
			if(prev)
				prev->next = f->next;
			else
				flist[h] = f->next;
			break;
		}
	fp->cp = 0;
	unlock(&flock);
}

int
iaccess(File *f, Dentry *d, int m)
{
	/* uid none gets only other permissions */
	if(f->uid != 0) {
		/*
		 * owner
		 */
		if(f->uid == d->uid)
			if((m<<6) & d->mode)
				return 0;
		/*
		 * group membership
		 */
		if(ingroup(f->uid, d->gid))
			if((m<<3) & d->mode)
				return 0;
	}

	/* other */
	if(m & d->mode) {
		/* 
		 *  walk directories regardless.
		 *  otherwise its impossible to get
		 *  from the root to noworld's directories.
		 */
		if((d->mode & DDIR) && (m == DEXEC))
			return 0;
		if(!ingroup(f->uid, 9999))
			return 0;
	}

	/* read access for du */
	if(duallow != 0 && duallow == f->uid)
		if((d->mode & DDIR) && (m == DREAD || m == DEXEC))
			return 0;

	/* allow god */
	return !isallowed(f);
}

int
isallowed(File *f)
{
	if(f->cp == cons.chan)
		return 1;
	switch(allowed){
	case 0:
		return 0;
	case -1:
		return 1;
	default:
		return f->uid == allowed;
	}
}

Tlock*
tlocked(Iobuf *p, Dentry *d)
{
	Tlock *t, *t1;
	Off qpath;
	Timet tim;
	Device *dev;

	tim = toytime();
	qpath = d->qid.path;
	dev = p->dev;

again:
	t1 = 0;
	for(t=tlocks+NTLOCK-1; t>=tlocks; t--) {
		if(t->qpath == qpath)
		if(t->time >= tim)
		if(t->dev == dev)
			return nil;		/* its locked */
		if(t1 != nil && t->time == 0)
			t1 = t;			/* remember free lock */
	}
	if(t1 == 0) {
		// reclaim old locks
		lock(&tlocklock);
		for(t=tlocks+NTLOCK-1; t>=tlocks; t--)
			if(t->time < tim) {
				t->time = 0;
				t1 = t;
			}
		unlock(&tlocklock);
	}
	if(t1) {
		lock(&tlocklock);
		if(t1->time != 0) {
			unlock(&tlocklock);
			goto again;
		}
		t1->dev = dev;
		t1->qpath = qpath;
		t1->time = tim + TLOCK;
		unlock(&tlocklock);
	}
	/* botch
	 * out of tlock nodes simulates
	 * a locked file
	 */
	return t1;
}

Wpath*
newwp(void)
{
	static int si = 0;
	int i;
	Wpath *w, *sw, *ew;

	i = si + 1;
	if(i < 0 || i >= conf.nwpath)
		i = 0;
	si = i;
	sw = &wpaths[i];
	ew = &wpaths[conf.nwpath];
	for(w=sw;;) {
		w++;
		if(w >= ew)
			w = &wpaths[0];
		if(w == sw) {
			fprint(2, "out of wpaths\n");
			return 0;
		}
		if(w->refs)
			continue;
		lock(&wpathlock);
		if(w->refs) {
			unlock(&wpathlock);
			continue;
		}
		w->refs = 1;
		w->up = 0;
		unlock(&wpathlock);
		return w;
	}

}

void
freewp(Wpath *w)
{
	lock(&wpathlock);
	for(; w; w=w->up)
		w->refs--;
	unlock(&wpathlock);
}

Off
qidpathgen(Device *dev)
{
	Iobuf *p;
	Superb *sb;
	Off path;

	p = getbuf(dev, superaddr(dev), Brd|Bmod);
	if(!p || checktag(p, Tsuper, QPSUPER))
		panic("newqid: super block");
	sb = (Superb*)p->iobuf;
	do {
		path = ++sb->qidgen;
	} while(path == QPDIR);
	putbuf(p);
	return path;
}

/* truncating to length > 0 */
static void
truncfree(Truncstate *ts, Device *dev, int d, Iobuf *p, int i)
{
	int pastlast;
	Off a;

	pastlast = ts->pastlast;
	a = ((Off *)p->iobuf)[i];
	if (d > 0 || pastlast)
		buffree(dev, a, d, ts);
	if (pastlast) {
		((Off *)p->iobuf)[i] = 0;
		p->flags |= Bmod|Bimm;
	} else if (d == 0 && ts->relblk == ts->lastblk)
		ts->pastlast = 1;
	if (d == 0)
		ts->relblk++;
}

/*
 * free the block at `addr' on dev.
 * if it's an indirect block (d [depth] > 0),
 * first recursively free all the blocks it names.
 *
 * ts->relblk is the block number within the file of this
 * block (or the first data block eventually pointed to via
 * this indirect block).
 */
void
buffree(Device *dev, Off addr, int d, Truncstate *ts)
{
	Iobuf *p;
	Off a;
	int i, pastlast;

	if(!addr)
		return;
	pastlast = (ts == nil? 1: ts->pastlast);
	/*
	 * if this is an indirect block, recurse and free any
	 * suitable blocks within it (possibly via further indirect blocks).
	 */
	if(d > 0) {
		d--;
		p = getbuf(dev, addr, Brd);
		if(p) {
			if (ts == nil)		/* common case: create */
				for(i=INDPERBUF-1; i>=0; i--) {
					a = ((Off *)p->iobuf)[i];
					buffree(dev, a, d, nil);
				}
			else			/* wstat truncation */
				for (i = 0; i < INDPERBUF; i++)
					truncfree(ts, dev, d, p, i);
			putbuf(p);
		}
	}
	if (!pastlast)
		return;
	/*
	 * having zeroed the pointer to this block, add it to the free list.
	 * stop outstanding i/o
	 */
	p = getbuf(dev, addr, Bprobe);
	if(p) {
		p->flags &= ~(Bmod|Bimm);
		putbuf(p);
	}
	/*
	 * dont put written worm
	 * blocks into free list
	 */
	if(dev->type == Devcw) {
		i = cwfree(dev, addr);
		if(i)
			return;
	}
	p = getbuf(dev, superaddr(dev), Brd|Bmod);
	if(!p || checktag(p, Tsuper, QPSUPER))
		panic("buffree: super block");
	addfree(dev, addr, (Superb*)p->iobuf);
	putbuf(p);
}

Off
bufalloc(Device *dev, int tag, long qid, int uid)
{
	Iobuf *bp, *p;
	Superb *sb;
	Off a, n;

	p = getbuf(dev, superaddr(dev), Brd|Bmod);
	if(!p || checktag(p, Tsuper, QPSUPER)) {
		fprint(2, "bufalloc: super block\n");
		if(p)
			putbuf(p);
		return 0;
	}
	sb = (Superb*)p->iobuf;

loop:
	n = --sb->fbuf.nfree;
	sb->tfree--;
	if(n < 0 || n >= FEPERBUF) {
		fprint(2, "bufalloc: %Z: bad freelist\n", dev);
		n = 0;
		sb->fbuf.free[0] = 0;
	}
	a = sb->fbuf.free[n];
	if(n <= 0) {
		if(a == 0) {
			sb->tfree = 0;
			sb->fbuf.nfree = 1;
			if(dev->type == Devcw) {
				n = uid;
				if(n < 0 || n >= nelem(growacct))
					n = 0;
				growacct[n]++;
				if(cwgrow(dev, sb, uid))
					goto loop;
			}
			putbuf(p);
			fprint(2, "fs %Z full uid=%d\n", dev, uid);
			return 0;
		}
		bp = getbuf(dev, a, Brd);
		if(!bp || checktag(bp, Tfree, QPNONE)) {
			if(bp)
				putbuf(bp);
			putbuf(p);
			return 0;
		}
		sb->fbuf = *(Fbuf*)bp->iobuf;
		putbuf(bp);
	}

	bp = getbuf(dev, a, Bmod);
	memset(bp->iobuf, 0, RBUFSIZE);
	settag(bp, tag, qid);
	if(tag == Tind1 || tag == Tind2 ||
#ifndef COMPAT32
	    tag == Tind3 || tag == Tind4 ||  /* add more Tind tags here ... */
#endif
	    tag == Tdir)
		bp->flags |= Bimm;
	putbuf(bp);
	putbuf(p);
	return a;
}

/*
 * what are legal characters in a name?
 * only disallow control characters.
 * utf avoids control characters, so we
 * only need to inspect the ascii range.
 */
int
checkname(char *n)
{
	int i, c;

	if(n == 0 || *n == 0)
		return Ename;
	if(*n == '.' && (n[1] == 0 || (n[1] == '.' && n[2] == 0)))
		return Edot;
	for(i=1; i<NAMELEN; i++) {
		c = n[i] & 0xff;
		if(c == 0)
			return 0;
		if(c < 040 || c == '/')
			return Ename;
	}
	return Etoolong;
}

void
addfree(Device *dev, Off addr, Superb *sb)
{
	int n;
	Iobuf *p;

	n = sb->fbuf.nfree;
	if(n < 0 || n > FEPERBUF)
		panic("addfree: bad freelist");
	if(n >= FEPERBUF) {
		p = getbuf(dev, addr, Bmod|Bimm);
		if(p == 0)
			panic("addfree: getbuf");
		*(Fbuf*)p->iobuf = sb->fbuf;
		settag(p, Tfree, QPNONE);
		putbuf(p);
		n = 0;
	}
	sb->fbuf.free[n++] = addr;
	sb->fbuf.nfree = n;
	sb->tfree++;
	if(addr >= sb->fsize)
		sb->fsize = addr+1;
}

/*
static int
Yfmt(Fmt* fmt)
{
	Chan *cp;
	char s[20];

	cp = va_arg(fmt->args, Chan*);
	sprint(s, "C%d.%.3d", cp->type, cp->chan);
	return fmtstrcpy(fmt, s);
}
 */

static int
Zfmt(Fmt* fmt)
{
	Device *d;
	int c, c1;
	char s[100];

	d = va_arg(fmt->args, Device*);
	if(d == nil) {
		sprint(s, "Z***");
		goto out;
	}
	c = c1 = '\0';
	switch(d->type) {
	default:
		sprint(s, "D%d", d->type);
		break;
	case Devwren:
		c = 'w';
		/* fallthrough */
	case Devworm:
		if (c == '\0')
			c = 'r';
		/* fallthrough */
	case Devlworm:
		if (c == '\0')
			c = 'l';
		if(d->wren.file)
			snprint(s, sizeof(s), "%c\"%s\"", c, d->wren.file);
		else if(d->wren.ctrl == 0 && d->wren.lun == 0)
			sprint(s, "%c%d", c, d->wren.targ);
		else
			sprint(s, "%c%d.%d.%d", c, d->wren.ctrl, d->wren.targ,
				d->wren.lun);
		break;
	case Devmcat:
		c = '(';
		c1 = ')';
		/* fallthrough */
	case Devmlev:
		if (c == '\0') {
			c = '[';
			c1 = ']';
		}
		/* fallthrough */
	case Devmirr:
		if (c == '\0') {
			c = '{';
			c1 = '}';
		}
		if(d->cat.first == d->cat.last)
			sprint(s, "%c%Z%c", c, d->cat.first, c1);
		else if(d->cat.first->link == d->cat.last)
			sprint(s, "%c%Z%Z%c", c, d->cat.first, d->cat.last, c1);
		else
			sprint(s, "%c%Z-%Z%c", c, d->cat.first, d->cat.last, c1);
		break;
	case Devro:
		sprint(s, "o%Z%Z", d->ro.parent->cw.c, d->ro.parent->cw.w);
		break;
	case Devcw:
		sprint(s, "c%Z%Z", d->cw.c, d->cw.w);
		break;
	case Devjuke:
		sprint(s, "j%Z%Z", d->j.j, d->j.m);
		break;
	case Devfworm:
		sprint(s, "f%Z", d->fw.fw);
		break;
	case Devpart:
		sprint(s, "p(%Z)%ld.%ld", d->part.d, d->part.base, d->part.size);
		break;
	case Devswab:
		sprint(s, "x%Z", d->swab.d);
		break;
	case Devnone:
		sprint(s, "n");
		break;
	}
out:
	return fmtstrcpy(fmt, s);
}

static int
Gfmt(Fmt* fmt)
{
	int t;
	char *s;

	t = va_arg(fmt->args, int);
	s = "<badtag>";
	if(t >= 0 && t < MAXTAG)
		s = tagnames[t];
	return fmtstrcpy(fmt, s);
}

void
formatinit(void)
{
	quotefmtinstall();
//	fmtinstall('Y', Yfmt);	/* print channels */
	fmtinstall('Z', Zfmt);	/* print devices */
	fmtinstall('G', Gfmt);	/* print tags */
	fmtinstall('T', Tfmt);	/* print times */
//	fmtinstall('E', eipfmt);	/* print ether addresses */
	fmtinstall('I', eipfmt);	/* print ip addresses */
}

void
rootream(Device *dev, Off addr)
{
	Iobuf *p;
	Dentry *d;

	p = getbuf(dev, addr, Bmod|Bimm);
	memset(p->iobuf, 0, RBUFSIZE);
	settag(p, Tdir, QPROOT);
	d = getdir(p, 0);
	strcpy(d->name, "/");
	d->uid = -1;
	d->gid = -1;
	d->mode = DALLOC | DDIR | 0775;
	d->qid = QID9P1(QPROOT|QPDIR,0);
	d->atime = time(nil);
	d->mtime = d->atime;
	d->muid = 0;
	putbuf(p);
}

void
superream(Device *dev, Off addr)
{
	Iobuf *p;
	Superb *s;
	Off i;

	p = getbuf(dev, addr, Bmod|Bimm);
	memset(p->iobuf, 0, RBUFSIZE);
	settag(p, Tsuper, QPSUPER);

	s = (Superb*)p->iobuf;
	s->fstart = 2;
	s->fsize = devsize(dev);
	s->fbuf.nfree = 1;
	s->qidgen = 10;
	for(i = s->fsize-1; i >= addr+2; i--)
		addfree(dev, i, s);
	putbuf(p);
}

struct
{
	Lock;
	Msgbuf	*smsgbuf;
	Msgbuf	*lmsgbuf;
} msgalloc;

/*
 * pre-allocate some message buffers at boot time.
 * if this supply is exhausted, more will be allocated as needed.
 */
void
mbinit(void)
{
	Msgbuf *mb;
	Rabuf *rb;
	int i;

	lock(&msgalloc);
	unlock(&msgalloc);
	msgalloc.lmsgbuf = 0;
	msgalloc.smsgbuf = 0;
	for(i=0; i<conf.nlgmsg; i++) {
		mb = ialloc(sizeof(Msgbuf), 0);
		mb->magic = Mbmagic;
		mb->xdata = ialloc(LARGEBUF+Slop, 0);
		mb->flags = LARGE;
		mbfree(mb);
		cons.nlarge++;
	}
	for(i=0; i<conf.nsmmsg; i++) {
		mb = ialloc(sizeof(Msgbuf), 0);
		mb->magic = Mbmagic;
		mb->xdata = ialloc(SMALLBUF+Slop, 0);
		mb->flags = 0;
		mbfree(mb);
		cons.nsmall++;
	}
	memset(mballocs, 0, sizeof(mballocs));

	lock(&rabuflock);
	unlock(&rabuflock);
	rabuffree = 0;
	for(i=0; i<1000; i++) {
		rb = ialloc(sizeof(*rb), 0);
		rb->link = rabuffree;
		rabuffree = rb;
	}
}

Msgbuf*
mballoc(int count, Chan *cp, int category)
{
	Msgbuf *mb;

	lock(&msgalloc);
	if(count > SMALLBUF) {
		if(count > LARGEBUF)
			panic("msgbuf count");
		mb = msgalloc.lmsgbuf;
		if(mb == nil) {
			mb = ialloc(sizeof(Msgbuf), 0);
			mb->xdata = ialloc(LARGEBUF+Slop, 0);
			cons.nlarge++;
		} else
			msgalloc.lmsgbuf = mb->next;
		mb->flags = LARGE;
	} else {
		mb = msgalloc.smsgbuf;
		if(mb == nil) {
			mb = ialloc(sizeof(Msgbuf), 0);
			mb->xdata = ialloc(SMALLBUF+Slop, 0);
			cons.nsmall++;
		} else
			msgalloc.smsgbuf = mb->next;
		mb->flags = 0;
	}
	mballocs[category]++;
	unlock(&msgalloc);
	mb->magic = Mbmagic;
	mb->count = count;
	mb->chan = cp;
	mb->next = 0;
	mb->param = 0;
	mb->category = category;
	mb->data = mb->xdata+Slop;
	return mb;
}

void
mbfree(Msgbuf *mb)
{
	if(mb == nil)
		return;
	if(mb->magic != Mbmagic)
		panic("mbfree: bad magic 0x%lux", mb->magic);
	if(mb->flags & BTRACE)
		fprint(2, "mbfree: BTRACE cat=%d flags=%ux, caller %#p\n",
			mb->category, mb->flags, getcallerpc(&mb));

	if(mb->flags & FREE)
		panic("mbfree already free");

	lock(&msgalloc);
	mballocs[mb->category]--;
	mb->flags |= FREE;
	if(mb->flags & LARGE) {
		mb->next = msgalloc.lmsgbuf;
		msgalloc.lmsgbuf = mb;
	} else {
		mb->next = msgalloc.smsgbuf;
		msgalloc.smsgbuf = mb;
	}
	mb->data = 0;
	mb->magic = 0;
	unlock(&msgalloc);
}

/*
 * returns 1 if n is prime
 * used for adjusting lengths
 * of hashing things.
 * there is no need to be clever
 */
int
prime(vlong n)
{
	long i;

	if((n%2) == 0)
		return 0;
	for(i=3;; i+=2) {
		if((n%i) == 0)
			return 0;
		if((vlong)i*i >= n)
			return 1;
	}
}

char*
getwrd(char *word, char *line)
{
	int c, n;

	while(isascii(*line) && isspace(*line) && *line != '\n')
		line++;
	for(n = 0; n < Maxword; n++) {
		c = *line;
		if(c == '\0' || isascii(c) && isspace(c))
			break;
		line++;
		*word++ = c;
	}
	*word = 0;
	return line;
}

void
hexdump(void *a, int n)
{
	char s1[30], s2[4];
	uchar *p;
	int i;

	p = a;
	s1[0] = 0;
	for(i = 0; i < n; i++) {
		sprint(s2, " %.2ux", p[i]);
		strcat(s1, s2);
		if((i&7) == 7) {
			fprint(2, "%s\n", s1);
			s1[0] = 0;
		}
	}
	if(s1[0])
		fprint(2, "%s\n", s1);
}

extern int cas(long *p, long ov, long nv);

void*
fs_recv(Queue *q, int)
{
	void *a;
	long v;

	v = q->count;
	if(v == 0 || cas(&q->count, v, v-1) == 0)
		semacquire(&q->count, 1);
	lock(&q->rl);
	a = *q->rp;
	if(++q->rp >= &q->args[q->size])
		q->rp = q->args;
	unlock(&q->rl);
	semrelease(&q->avail, 1);
	return a;
}

void
fs_send(Queue *q, void *a)
{
	long v;

	v = q->avail;
	if(v == 0 || cas(&q->avail, v, v-1) == 0)
		semacquire(&q->avail, 1);
	lock(&q->wl);
	*q->wp = a;
	if(++q->wp >= &q->args[q->size])
		q->wp = q->args;
	unlock(&q->wl);
	semrelease(&q->count, 1);
}

Queue*
newqueue(int size, char *name)
{
	Queue *q;

	q = ialloc(sizeof(Queue) + (size-1)*sizeof(void*), 0);
	q->size = size;
	q->avail = size;
	q->count = 0;
	q->rp = q->args;
	q->wp = q->args;
	q->name = name;
	return q;
}

int
devread(Device *d, Off b, void *c)
{
	int e;

	for (;;)
		switch(d->type) {
		case Devcw:
			return cwread(d, b, c);

		case Devjuke:
			d = d->j.m;
			break;

		case Devro:
			return roread(d, b, c);

		case Devwren:
			return wrenread(d, b, c);

		case Devworm:
		case Devlworm:
			return wormread(d, b, c);

		case Devfworm:
			return fwormread(d, b, c);

		case Devmcat:
			return mcatread(d, b, c);

		case Devmlev:
			return mlevread(d, b, c);

		case Devmirr:
			return mirrread(d, b, c);

		case Devpart:
			return partread(d, b, c);

		case Devswab:
			e = devread(d->swab.d, b, c);
			if(e == 0)
				swab(c, 0);
			return e;

		case Devnone:
			fprint(2, "read from device none(%lld)\n", (Wideoff)b);
			return 1;
		default:
			panic("illegal device in devread: %Z %lld",
				d, (Wideoff)b);
			return 1;
		}
}

int
devwrite(Device *d, Off b, void *c)
{
	int e;

	/*
	 * set readonly to non-0 to prevent all writes;
	 * mainly for trying dangerous experiments.
	 */
	if (readonly)
		return 0;
	for (;;)
		switch(d->type) {
		case Devcw:
			return cwwrite(d, b, c);

		case Devjuke:
			d = d->j.m;
			break;

		case Devro:
			fprint(2, "write to ro device %Z(%lld)\n", d, (Wideoff)b);
			return 1;

		case Devwren:
			return wrenwrite(d, b, c);

		case Devworm:
		case Devlworm:
			return wormwrite(d, b, c);

		case Devfworm:
			return fwormwrite(d, b, c);

		case Devmcat:
			return mcatwrite(d, b, c);

		case Devmlev:
			return mlevwrite(d, b, c);

		case Devmirr:
			return mirrwrite(d, b, c);

		case Devpart:
			return partwrite(d, b, c);

		case Devswab:
			swab(c, 1);
			e = devwrite(d->swab.d, b, c);
			swab(c, 0);
			return e;

		case Devnone:
			/* checktag() can generate blocks with type devnone */
			return 0;
		default:
			panic("illegal device in devwrite: %Z %lld",
				d, (Wideoff)b);
			return 1;
		}
}

Devsize
devsize(Device *d)
{
	for (;;)
		switch(d->type) {
		case Devcw:
		case Devro:
			return cwsize(d);

		case Devjuke:
			d = d->j.m;
			break;

		case Devwren:
			return wrensize(d);

		case Devworm:
		case Devlworm:
			return wormsize(d);

		case Devfworm:
			return fwormsize(d);

		case Devmcat:
			return mcatsize(d);

		case Devmlev:
			return mlevsize(d);

		case Devmirr:
			return mirrsize(d);

		case Devpart:
			return partsize(d);

		case Devswab:
			d = d->swab.d;
			break;
		default:
			panic("illegal device in devsize: %Z", d);
			return 0;
		}
}

/* result is malloced */
char *
sdof(Device *d)
{
	static char name[256];

	for (;;)
		switch(d->type) {
		case Devjuke:
			d = d->j.j;		/* robotics */
			break;
		case Devwren:
			snprint(name, sizeof name, "/dev/sd%d%d", d->wren.ctrl,
				d->wren.targ);
			return strdup(name);
		case Devswab:
			d = d->swab.d;
			break;
		default:
			panic("illegal device in sdof: %Z", d);
			return nil;
		}
}

Off
superaddr(Device *d)
{
	for (;;)
		switch(d->type) {
		default:
			return SUPER_ADDR;
		case Devcw:
		case Devro:
			return cwsaddr(d);
		case Devswab:
			d = d->swab.d;
			break;
		}
}

Off
getraddr(Device *d)
{
	for (;;)
		switch(d->type) {
		default:
			return ROOT_ADDR;
		case Devcw:
		case Devro:
			return cwraddr(d);
		case Devswab:
			d = d->swab.d;
			break;
		}
}

void
devream(Device *d, int top)
{
	Device *l;

loop:
	if(chatty)
		print("\tdevream %Z %d\n", d, top);
	switch(d->type) {
	default:
		fprint(2, "devream: unknown dev type %Z\n", d);
		return;

	case Devcw:
		devream(d->cw.w, 0);
		devream(d->cw.c, 0);
		if(top) {
			wlock(&mainlock);
			cwream(d);
			wunlock(&mainlock);
		}
		devinit(d);
		return;

	case Devfworm:
		devream(d->fw.fw, 0);
		fwormream(d);
		break;

	case Devpart:
		devream(d->part.d, 0);
		break;

	case Devmlev:
	case Devmcat:
	case Devmirr:
		for(l=d->cat.first; l; l=l->link)
			devream(l, 0);
		break;

	case Devjuke:
	case Devworm:
	case Devlworm:
	case Devwren:
		break;

	case Devswab:
		d = d->swab.d;
		goto loop;
	}
	devinit(d);
	if(top) {
		wlock(&mainlock);
		rootream(d, ROOT_ADDR);
		superream(d, SUPER_ADDR);
		wunlock(&mainlock);
	}
}

void
devrecover(Device *d)
{
	for (;;) {
		if(chatty)
			print("recover %Z\n", d);
		switch(d->type) {
		default:
			fprint(2, "devrecover: unknown dev type %Z\n", d);
			return;

		case Devcw:
			wlock(&mainlock);	/* recover */
			cwrecover(d);
			wunlock(&mainlock);
			return;

		case Devswab:
			d = d->swab.d;
			break;
		}
	}
}

void
devinit(Device *d)
{
	for (;;) {
		if(d->init)
			return;
		d->init = 1;
		if(chatty)
			print("\tdevinit %Z\n", d);
		switch(d->type) {
		default:
			fprint(2, "devinit: unknown device %Z\n", d);
			return;

		case Devro:
			cwinit(d->ro.parent);
			return;

		case Devcw:
			cwinit(d);
			return;

		case Devjuke:
			jukeinit(d);
			return;

		case Devwren:
			wreninit(d);
			return;

		case Devworm:
		case Devlworm:
			return;

		case Devfworm:
			fworminit(d);
			return;

		case Devmcat:
			mcatinit(d);
			return;

		case Devmlev:
			mlevinit(d);
			return;

		case Devmirr:
			mirrinit(d);
			return;

		case Devpart:
			partinit(d);
			return;

		case Devswab:
			d = d->swab.d;
			break;

		case Devnone:
			return;
		}
	}
}

void
swab2(void *c)
{
	uchar *p;
	int t;

	p = c;

	t = p[0];
	p[0] = p[1];
	p[1] = t;
}

void
swab4(void *c)
{
	uchar *p;
	int t;

	p = c;

	t = p[0];
	p[0] = p[3];
	p[3] = t;

	t = p[1];
	p[1] = p[2];
	p[2] = t;
}

void
swab8(void *c)
{
	uchar *p;
	int t;

	p = c;

	t = p[0];
	p[0] = p[7];
	p[7] = t;

	t = p[1];
	p[1] = p[6];
	p[6] = t;

	t = p[2];
	p[2] = p[5];
	p[5] = t;

	t = p[3];
	p[3] = p[4];
	p[4] = t;
}

/*
 * swab a block
 *	flag = 0 -- convert from foreign to native
 *	flag = 1 -- convert from native to foreign
 */
void
swab(void *c, int flag)
{
	uchar *p;
	Tag *t;
	int i, j;
	Dentry *d;
	Cache *h;
	Bucket *b;
	Superb *s;
	Fbuf *f;
	Off *l;

	/* swab the tag */
	p = (uchar*)c;
	t = (Tag*)(p + BUFSIZE);
	if(!flag) {
		swab2(&t->pad);
		swab2(&t->tag);
		swaboff(&t->path);
	}

	/* swab each block type */
	switch(t->tag) {
	default:
		fprint(2, "no swab for tag=%G rw=%d\n", t->tag, flag);
		hexdump(p, 256);
		panic("swab");
		break;

	case Tsuper:
		s = (Superb*)p;
		swaboff(&s->fbuf.nfree);
		for(i=0; i<FEPERBUF; i++)
			swaboff(&s->fbuf.free[i]);
		swaboff(&s->fstart);
		swaboff(&s->fsize);
		swaboff(&s->tfree);
		swaboff(&s->qidgen);
		swaboff(&s->cwraddr);
		swaboff(&s->roraddr);
		swaboff(&s->last);
		swaboff(&s->next);
		break;

	case Tdir:
		for(i=0; i<DIRPERBUF; i++) {
			d = (Dentry*)p + i;
			swab2(&d->uid);
			swab2(&d->gid);
			swab2(&d->mode);
			swab2(&d->muid);
			swaboff(&d->qid.path);
			swab4(&d->qid.version);
			swaboff(&d->size);
			for(j=0; j<NDBLOCK; j++)
				swaboff(&d->dblock[j]);
			for (j = 0; j < NIBLOCK; j++)
				swaboff(&d->iblocks[j]);
			swab4(&d->atime);
			swab4(&d->mtime);
		}
		break;

	case Tind1:
	case Tind2:
#ifndef COMPAT32
	case Tind3:
	case Tind4:
	/* add more Tind tags here ... */
#endif
		l = (Off *)p;
		for(i=0; i<INDPERBUF; i++) {
			swaboff(l);
			l++;
		}
		break;

	case Tfree:
		f = (Fbuf*)p;
		swaboff(&f->nfree);
		for(i=0; i<FEPERBUF; i++)
			swaboff(&f->free[i]);
		break;

	case Tbuck:
		for(i=0; i<BKPERBLK; i++) {
			b = (Bucket*)p + i;
			swab4(&b->agegen);
			for(j=0; j<CEPERBK; j++) {
				swab2(&b->entry[j].age);
				swab2(&b->entry[j].state);
				swaboff(&b->entry[j].waddr);
			}
		}
		break;

	case Tcache:
		h = (Cache*)p;
		swaboff(&h->maddr);
		swaboff(&h->msize);
		swaboff(&h->caddr);
		swaboff(&h->csize);
		swaboff(&h->fsize);
		swaboff(&h->wsize);
		swaboff(&h->wmax);
		swaboff(&h->sbaddr);
		swaboff(&h->cwraddr);
		swaboff(&h->roraddr);
		swab4(&h->toytime);
		swab4(&h->time);
		break;

	case Tnone:	// unitialized
	case Tfile:	// someone elses problem
	case Tvirgo:	// bit map -- all bytes
	case Tconfig:	// configuration string -- all bytes
		break;
	}

	/* swab the tag */
	if(flag) {
		swab2(&t->pad);
		swab2(&t->tag);
		swaboff(&t->path);
	}
}
