code: mafs

ref: 5c21eca829380c28769f79beabb7cc51f1787f97
dir: /9p.c/

View raw version
#include "all.h"

int mpstarted = 0;

s32 readfile(u64 dblkno, u64 qpath, char *rbuf, s32 rbufsize, u64 offset);
s32 writefile(u64 dblkno, u64 qpath, s16 uid, char *wbuf, s32 wbufsize, u64 offset);

Aux*
newaux(u64 addr, u16 uid)
{
	Aux *a;

	a = emalloc9p(sizeof(Aux));
	a->dblkno = addr;
	a->uid = uid;
	return a;
}

void
freeaux(Aux *a)
{
	if(a == nil)
		return;
	free(a);
}

static void
fsattach(Req *req)
{
	short uid;
	Iobuf *dbuf;
	Dentry *d;

	if(authattach(req) < 0){
		return;
	}

	if((uid = lookupid(req->ifcall.uname, 0)) <= 0){
		respond(req, "no such user");
		return;
	}

	dbuf = getbufchk(Broot, 1, Breadonly, Tdentry, Qproot);
	if(dbuf == nil){
		respond(req, "phase error");
		return;
	}
	d = &dbuf->io->d;
	req->fid->aux = newaux(Broot, uid);
	req->fid->qid = (Qid){Qproot, d->qid.version, QTDIR};
	putbuf(dbuf);
	req->ofcall.qid = req->fid->qid;
	respond(req, nil);
}

static void
fsdestroyfid(Fid *fid)
{
	if((fid->qid.type & QTAUTH) != 0){
		authdestroy(fid);
		return;
	}
	freeaux(fid->aux);
}

static void
fsmkdir(Dentry *d, Dir *dir, char *buf)
{
	memset(dir, 0, sizeof(*dir));
	dir->qid = (Qid){d->qid.path, d->qid.version, (d->mode&DMDIR)? QTDIR : QTFILE};
	dir->mode = (d->mode & 0777) | (dir->qid.type << 24);
	dir->atime = time(nil);
	dir->mtime = d->mtime/Nsec; /* ns to seconds */
	dir->length = d->size;
	if(dir->qid.type & QTDIR)
		dir->length = 0;
	if(buf == nil){
		dir->name = estrdup9p(d->name);
		dir->uid = username(d->uid, 0, dir->uid);
		dir->gid = username(d->gid, 0, dir->gid);
		dir->muid = username(d->muid, 0, dir->muid);
	}else{
		memset(buf, 0, Namelen + 3 * Userlen);
		strncpy(buf, d->name, Namelen - 1);
		dir->name = buf;
		dir->uid = username(d->uid, 0, buf + Namelen);
		dir->gid = username(d->gid, 0, buf + Namelen + Userlen);
		dir->muid = username(d->muid, 0, buf + Namelen + 2 * Userlen);
	}
}

static void
fsstat(Req *req)
{
	Dentry *d;
	Iobuf *dbuf;

	dbuf = getbufchk(((Aux*)req->fid->aux)->dblkno, 1, Breadonly,
						Tdentry, req->fid->qid.path);
	if(dbuf == nil){
		dprint("fsstat dbuf == nil dblkno %llud qpath %llud\n",
				((Aux*)req->fid->aux)->dblkno, req->fid->qid.path);
		respond(req, errstring[Ephase]);
		return;
	}
	d = &dbuf->io->d;
	fsmkdir(d, &req->d, nil);
	putbuf(dbuf);
	respond(req, nil);
}

int
emptystr(char *s)
{
	if(s == nil)
		return 1;
	if(s[0] == '\0')
		return 1;
	return 0;
}

static void
fswstat(Req *req)
{
	Dentry *d;
	Iobuf *dbuf;
	s16 gid;

	if(shuttingdown){
		respond(req, errstring[Eshutdown]);
		shutdown();
		return;
	}
	if(readonly){
		respond(req, errstring[Eronly]);
		return;
	}
	if(req->fid->qid.path == Qpctl){
		respond(req, errstring[Einval]);
		return;
	}
	if((req->d.type & QTDIR) > 0 && req->d.length != ~0){
		respond(req, errstring[Einval]);
		return;
	}
	if(((Aux*)req->fid->aux)->uid == None)
		goto noperm;

	/* TODO allow changing the length as per stat(5) */
	if(req->d.atime != ~0 ||  req->d.length  != ~0)
		goto noperm;

	/* stat(5) says that this is illegal */
	if(emptystr(req->d.uid) == 0){
		respond(req, errstring[Einval]);
		return;
	}

	/* TODO cwfs/9p2.c, hjfs/fs2.c and kfs64.b do more here. Get that stuff in. */

	dbuf = getbufchk(((Aux*)req->fid->aux)->dblkno, 1, Bmod,
						Tdentry, req->fid->qid.path);
	if(dbuf == nil){
		respond(req, errstring[Eperm]);
		return;
	}
	d = &dbuf->io->d;
	if(canaccess(((Aux*)req->fid->aux)->uid, d, DMWRITE) == 0){
		putbuf(dbuf);
		respond(req, errstring[Eaccess]);
		return;
	}
	/* invalid to change the directory bit stat(5) */
	if(req->d.mode != ~0 &&
			(req->d.mode&DMDIR) != (d->mode&DMDIR)){
		putbuf(dbuf);
		respond(req, errstring[Einval]);
		return;
	}
	if(emptystr(req->d.name) == 0){
		strncpy(d->name, req->d.name, Namelen);
		d->mtime = nsec();
		d->muid = ((Aux*)req->fid->aux)->uid;
	}
	if(d->uid == ((Aux*)req->fid->aux)->uid ||
		leadgroup(((Aux*)req->fid->aux)->uid, d->gid) == 1){
		if(req->d.mtime != ~0){
			d->mtime = req->d.mtime*Nsec;
			d->muid = ((Aux*)req->fid->aux)->uid;
		}
		if(req->d.mode != ~0 &&
			(req->d.mode&DMDIR) == (d->mode&DMDIR) &&
			req->d.mode != d->mode)
			d->mode = req->d.mode;

		/* TODO gid setting needs more fine tuning to align with stat(5) */
		if(emptystr(req->d.gid) == 0 &&
			(gid = lookupid(req->d.gid, 0)) != 0){
			d->gid = gid;
		}
	}

	putbuf(dbuf);
	respond(req, nil);
	return;
noperm:
	respond(req, errstring[Eperm]);
	return;	
}

static void
fsread(Req *req)
{
	s32 n;
	Iobuf *dbuf, *cbuf;
	Dentry *d;
	char nbuf[Namelen + 3 * Userlen];
	Dir dir;

	if(shuttingdown){
		respond(req, errstring[Eshutdown]);
		shutdown();
		return;
	}
	if(req->fid->qid.type == QTAUTH){
		authread(req);
		return;
	}else if(req->fid->qid.path == Qpctl){
		ctlread(req);
		return;
	}
	if((req->fid->qid.type & QTDIR) != 0){
		/* some directory, find the child at offset */
		d = searchdir(((Aux*)req->fid->aux)->dblkno, req->fid->qid.path,
						((Aux*)req->fid->aux)->uid, nil,
						((Aux*)req->fid->aux)->dri, &dbuf, &cbuf);
		if(d == nil){
			req->ofcall.offset = req->ifcall.offset;
			req->ofcall.count = 0;
			((Aux*)req->fid->aux)->dri = 0;
			respond(req, nil);
			return;
		}
		fsmkdir(d, &dir, nbuf);
		req->ofcall.count = n = convD2M(&dir, (u8*)req->ofcall.data, req->ifcall.count);
		req->ofcall.offset = req->ifcall.offset+n;
		if(n == 0)
			((Aux*)req->fid->aux)->dri = 0;
		else
			((Aux*)req->fid->aux)->dri++;
		freesearchstate(&dbuf, &cbuf);
		respond(req, nil);
		return;
	}

	n = readfile(((Aux*)req->fid->aux)->dblkno, req->fid->qid.path,
				 req->ofcall.data, req->ifcall.count, req->ifcall.offset);
	req->ofcall.count = n;
	req->ofcall.offset = req->ifcall.offset+n;
	respond(req, nil);
}


static void
fswrite(Req *req)
{
	int n;

	if(shuttingdown){
		respond(req, errstring[Eshutdown]);
		shutdown();
		return;
	}
	if(readonly){
		respond(req, errstring[Eronly]);
		return;
	}
	if(req->fid->qid.type == QTAUTH){
		authwrite(req);
		return;
	}else if(req->fid->qid.path == Qpctl){
		ctlwrite(req);
		return;
	}
	if((req->fid->qid.type & QTDIR) != 0){
		respond(req, errstring[Einval]);
		return;
	}

	n = writefile(((Aux*)req->fid->aux)->dblkno, req->fid->qid.path,
					((Aux*)req->fid->aux)->uid,
					req->ifcall.data, req->ifcall.count, req->ifcall.offset);
	if(n == -3)
		respond(req, errstring[Efull]);
	else if(n == -1 || n == -2)
		respond(req, errstring[Ephase]);
	else{
		req->ofcall.count = n;
		req->ofcall.offset = req->ifcall.offset+n;
		respond(req, nil);
	}
}

static void
fsremove(Req *req)
{
	Fid *fid;
	Aux *aux;

	if(shuttingdown){
		respond(req, errstring[Eshutdown]);
		shutdown();
		return;
	}
	if(readonly){
		respond(req, errstring[Eronly]);
		return;
	}

	fid = req->fid;
	aux = fid->aux;
	if(aux == nil ||
		aux->uid == None ||
		fid->qid.path < Qproot ||
		aux->dblkno == 0){
		respond(req, errstring[Eperm]);
		return;
	}

	if(fid->qid.type & QTDIR)
		rmdirectory(fid->qid.path, aux->dblkno);
	else
		rmfile(fid->qid.path, aux->dblkno);
	respond(req, nil);
}

static char*
fswalk1(Fid *fid, char *name, Qid *qid)
{
	Aux *aux;
	Dentry *d, *p;
	Iobuf *dbuf, *pbuf, *cbuf;

	if(shuttingdown){
		shutdown();
		return nil;
	}
	if((fid->qid.type&QTDIR) == 0)
		return errstring[Enotdir];

	aux = fid->aux;
	if(strcmp(name, "..") == 0){
		switch(fid->qid.path){
		case Qproot:
			return nil;
		default:
			if(chatty9p > 1)
				dprint("fswalk1 .. fid->qid.path %llud aux->dblkno %llud\n",
						fid->qid.path, aux->dblkno);
			dbuf = getbufchk(aux->dblkno, 1, Breadonly, Tdentry, fid->qid.path);
			if(dbuf == nil)
				return errstring[Ephase];
			d = &dbuf->io->d;
			if(chatty9p > 1)
				dprint("fswalk1 .. d->name %s d->qid.path %llud d->pdblkno %llud\n",
						d->name, d->qid.path, d->pdblkno);
			pbuf = getbufchk(d->pdblkno, 1, Breadonly, Tdentry, d->pqpath);
			if(pbuf == nil)
				return errstring[Ephase];
			putbuf(dbuf);
			p = &pbuf->io->d;
			*qid = (Qid){p->qid.path, p->qid.version, (p->mode&DMDIR) ? QTDIR : QTFILE};
			if(aux != nil)
				aux->dblkno = pbuf->blkno;
			putbuf(pbuf);
			fid->qid = *qid;
			return nil;
		}
	}

	/* assuming that it will ever be here only for directories */
		/* some directory, find the child with name or idx */
		d = searchdir(aux->dblkno, fid->qid.path,
						aux->uid, name, 0, &dbuf, &cbuf);
		if(d == nil)
			return "directory entry not found";
		if((d->mode&DMDIR) && canaccess(d->uid, d, DMEXEC) == 0){
			freesearchstate(&dbuf, &cbuf);
			return errstring[Eperm];
		}
		*qid = (Qid){d->qid.path, d->qid.version, (d->mode&DMDIR) ? QTDIR : QTFILE};
		if(aux != nil)
			aux->dblkno = cbuf->blkno;
		freesearchstate(&dbuf, &cbuf);
		fid->qid = *qid;
		return nil;
}

static char*
fsclone(Fid *oldfid, Fid *newfid)
{
	Aux *o;

	if(shuttingdown){
		shutdown();
		return nil;
	}
	o = oldfid->aux;
	if(o == nil)
		return "bad fid";
	newfid->aux = newaux(o->dblkno, o->uid);
	return nil;
}

/*
 * error(Eperm) if open permission not granted for up->user.
 */
int
permcheck(u16 fileuid, u16 filegid, u16 uid, ulong perm, int omode)
{
	ulong t;
	static int access[] = { 0400, 0200, 0600, 0100 };

	if(uid == fileuid)
		perm <<= 0;
	else if(ingroup(uid, filegid, 0) == 0)
		perm <<= 3;
	else
		perm <<= 6;

	t = access[omode&3];
	if((t&perm) != t)
		return 0;
	return 1;
}

/* read the Req.ifcall's perm, name and mode
	and build the Fid.omode */
static void
fscreate(Req *req)
{
	Iobuf *dbuf, *cbuf;
	u64 reli, blkno;
	Dentry *dchild, *dparent;
	Tag *ct;
	u64 zblkno;	/* zero'ed dentry that can be reused */
	Aux *aux;
	u32 perm;
	Fid *fid;
	Spanid s;

/*	if(chatty9p > 1)
		dprint("< %ld mafscreate c->path %s req->ifcall.name %s\n"
				"	c->mode 0x%ux mode 0x%ux perm 0x%lux\n"
				"	c->qid.path 0x%zux c->qid.vers %lud c->qid.type %ud 0x%ux\n"
				"	c->aux 0x%p t->idx %d dblkno %llud\n",
				time(nil),
				chanpath(c), req->ifcall.name, c->mode, omode, perm,
				c->qid.path,
				c->qid.vers, c->qid.type, c->qid.type,
				c->aux, aux->t->idx, aux->dblkno);*/

	/* no create's when shutting down */
	if(shuttingdown){
		respond(req, errstring[Eshutdown]);
		shutdown();
		return;
	}
	fid = req->fid;
	aux = fid->aux;
	if(chatty9p >2)
		dprint("fscreate aux 0x%p aux->uid %d fid->qid.path %d aux->dblkno %llud\n",
				aux, aux->uid, fid->qid.path, aux->dblkno);
	if(aux == nil ||
		aux->uid == None ||
		fid->qid.path < Qproot ||
		aux->dblkno == 0){
		respond(req, errstring[Eaccess]);
		return;
	}

	if(strlen(req->ifcall.name) > Namelen){
		respond(req, errstring[Etoolong]);
		return;
	}
	if((fid->qid.type & QTDIR) == 0){
		respond(req, errstring[Enotdir]);
		return;
	}
	if(req->ifcall.name == nil ||
		strlen(req->ifcall.name) == 0 ||
		req->ifcall.name[0] == '/' ||
		checkname9p2(req->ifcall.name) == 0){
		respond(req, errstring[Ebadname]);
		return;
	}
	if(readonly){
		respond(req, errstring[Eaccess]);
		return;
	}

	perm = req->ifcall.perm;
	cbuf = nil;
	USED(cbuf);
	zblkno = 0;
	dbuf = getbufchk(aux->dblkno, 1, Bmod, Tdentry, fid->qid.path);
	if(dbuf == nil){
		respond(req, errstring[Ephase]);
		return;
	}
	dparent = &dbuf->io->d;
	if(canaccess(((Aux*)req->fid->aux)->uid, dparent, DMWRITE) == 0){
		respond(req, errstring[Eperm]);
		return;
	}

	if(canaccess(aux->uid, dparent, DMWRITE) == 0){
		putbuf(dbuf);
		respond(req, errstring[Eaccess]);
		return;
	}

	fid->omode = OREAD;
	switch(req->ifcall.mode & 7) {
		case OREAD:
		case OEXEC:
			fid->omode = OREAD;
			break;
		case OWRITE:
			fid->omode = OWRITE;
			break;
		case ORDWR:	
			fid->omode = OREAD+OWRITE;
			break;
		default:
			putbuf(dbuf);
			respond(req, errstring[Emode]);
			return;
	}
	for(reli = 0, blkno = 1; blkno > 0; reli++){
		rel2abs(dparent, reli, &s);
		blkno = s.blkno;
		/* if(reli >= Max)
			error(Etoobig); */

		if(blkno == 0){
			/* end reached, nothing found, create */
			if(zblkno != 0){
				cbuf = getbufchk(zblkno, 1,Bmod, Tdentry, Qpnone);
				if(cbuf == nil){
					putbuf(dbuf);
					respond(req, errstring[Ephase]);
					return;
				}
			}else{
				cbuf = allocblocks(1, Bmod, Tdentry, fid->qid.path);
				if(cbuf == nil){
					putbuf(dbuf);
					respond(req, errstring[Efull]);
					return;
				}
			}
			dchild = &cbuf->io->d;
			dchild->size = 0;
			dchild->pdblkno = dbuf->blkno;
			dchild->pqpath = dparent->qid.path;
			dchild->mtime = nsec();
			dchild->uid = dchild->muid = aux->uid;
			dchild->gid = dparent->gid;
			/* TODO DMAPPEND and DMEXCL attributes */
			if(perm&DMDIR){
				if((req->ifcall.mode & OTRUNC) || (perm & DMAPPEND) ||
					(fid->omode & OWRITE)){
					putbuf(cbuf);
					putbuf(dbuf);
					respond(req, errstring[Eaccess]);
					return;
				}
			}
			if(perm&DMDIR)
				dchild->mode = DMDIR | (perm & (~0777 | (dparent->mode & 0777)));
			else
				dchild->mode = perm & (~0666 | (dparent->mode & 0666));
			if(perm&DMAPPEND)
				dchild->mode |= DMAPPEND;
			/* if(perm&DMEXCL) TODO create a list of locks
				dchild->mode |= DMEXCL; */
			if(canaccess(aux->uid, dchild, DMWRITE) == 0){
				putbuf(cbuf);
				putbuf(dbuf);
				respond(req, errstring[Eaccess]);
				return;
			}
			dchild->qid.path = newqpath();
			dchild->qid.version = 0;
			strncpy(dchild->name, req->ifcall.name, Namelen);
			fid->qid = (Qid){dchild->qid.path, 0, (perm&DMDIR) ? QTDIR : QTFILE};
			aux->dblkno = cbuf->blkno;
			aux->dri = 0;
			req->ofcall.qid = fid->qid;
			req->ofcall.iounit = Iounit;
			settag(cbuf, Tdentry, dchild->qid.path);
			putbuf(cbuf);	/* save Iobuf of the content */
			/*
				only add it to the directory dentry if we are adding a new dentry block
				if we are reusing a zero'ed out slot, it already exists in the
				directory dentry
			 */
			if(zblkno == 0 &&
				addrelative(dparent, dbuf->blkno, reli, aux->dblkno,1) == 0){
				putbuf(dbuf);
				respond(req, errstring[Ephase]);
				return;
			}
			putbuf(dbuf);
			/* DBG("> mafscreate c->path %s mode 0x%ux omode0 0x%ux\n"
				"	c->qid.path 0x%zux c->qid.vers %lud c->qid.type %d 0x%ux\n"
				"	c->aux 0x%p\n",
				chanpath(c), c->mode, omode, c->qid.path,
				c->qid.vers, c->qid.type, c->qid.type,
				c->aux); */
			respond(req, nil);
			return;
		}else{
			cbuf = getbuf(blkno, 1,Breadonly);
			if(cbuf == nil){
				putbuf(dbuf);
				respond(req, errstring[Ephase]);
				return;
			}
			dchild = &cbuf->io->d;
			ct = (Tag*)cbuf->io;

			/* nothing to do for already zero'ed out slots */
			if(ct->path == Qpnone){
				if(zblkno == 0)
					zblkno = cbuf->blkno;
				goto Nextdentry;
			}

			if(ct->type != Tdentry){
				putbuf(cbuf);
				putbuf(dbuf);
				respond(req, errstring[Ephase]);
				return;
			}

			if(strcmp(req->ifcall.name, dchild->name) == 0){
				/* req->ifcall.name matched, truncate file and use it */
				if(permcheck(dchild->uid, dchild->gid, aux->uid, perm, ORDWR) == 0){
					respond(req, errstring[Eperm]);
					return;
				}
				dchild->muid = aux->uid;
				fid->qid = (Qid){dchild->qid.path, dchild->qid.version,
									(req->ifcall.mode&DMDIR) ? QTDIR : QTFILE};
				aux->dblkno = blkno;
				aux->dri = 0;
				req->ofcall.qid = fid->qid;
				req->ofcall.iounit = Iounit;
				/*
					save Iobuf of the child so truncate can open
					it with a wlock(), if needed
				 */
				putbuf(cbuf);
				if(req->ofcall.qid.path >= Qpusers &&
					req->ofcall.qid.type == QTFILE)
					truncatefile(req->ofcall.qid.path, blkno, aux->uid);
				putbuf(dbuf);
		/*		DBG("> mafscreate c->path %s mode 0x%ux omode0 0x%ux\n"
					"	c->qid.path 0x%zux c->qid.vers %lud c->qid.type %ud 0x%ux\n"
					"	c->aux 0x%p\n",
					chanpath(c), c->mode, omode, c->qid.path,
					c->qid.vers, c->qid.type, c->qid.type,
					c->aux);*/
				respond(req, nil);
				return;
			}
			Nextdentry:
				putbuf(cbuf);
		}
	}
}

/* read the Req.ifcall.mode and build the Fid.omode based on the dentry */
static void
fsopen(Req *req)
{
	u8 mode, omode;
	Fid *fid;
	Iobuf *dbuf;
	Aux *aux;
	Dentry *d;

	dbuf = nil;
	mode = req->ifcall.mode;
	fid = req->fid;
	aux = req->fid->aux;
	omode = 0;
	if(chatty9p > 1)
		dprint("fsopen fid->qid.path %d mode %d readonly %d\n",
				 fid->qid.path, mode, readonly);
	if(readonly && (mode & (ORCLOSE | OTRUNC | OWRITE | ORDWR)) != 0)
		goto inval;

	dbuf = getbufchk(aux->dblkno, 1, Breadonly, Tdentry, fid->qid.path);
	if(dbuf == nil){
		respond(req, errstring[Ephase]);
		return;
	}
	d = &dbuf->io->d;

	if((mode & OTRUNC) != 0 &&
		canaccess(aux->uid, d, DMWRITE) == 0)
		goto perm;
	if((mode & ORCLOSE) != 0)
		if(canaccess(aux->uid, d, DMWRITE) == 0)
			goto perm;
	if((d->mode & DMAPPEND) != 0)
		mode &= ~OTRUNC;
	if((d->mode & DMDIR) != 0){
		if((mode & (ORCLOSE | OTRUNC | OWRITE | ORDWR)) != 0)
			goto inval;
		if(canaccess(aux->uid, d, DMEXEC) == 0)
			goto perm;
	}
	if((d->mode & DMEXCL) != 0){
		respond(req, "DMEXCL read not implemented");
		return;
	}
	switch(mode & OEXEC){
	case ORDWR:
		omode |= ORDWR;
	case OWRITE:
		omode |= OWRITE;
		break;
	case OEXEC:
	case OREAD:
		omode |= OREAD;
		break;
	}

	if((mode & ORCLOSE) != 0)
		omode |= ORCLOSE;

	fid->qid = (Qid){d->qid.path, d->qid.version, (d->mode&DMDIR) ? QTDIR : QTFILE};
	fid->omode = omode;
	req->ofcall.iounit = Iounit;
	req->ofcall.qid = fid->qid;

	putbuf(dbuf);
	if((mode & OTRUNC) == 0){
		respond(req, nil);
		return;
	}
	/* truncate file */
	truncatefile(fid->qid.path, aux->dblkno, aux->uid);
	respond(req, nil);
	return;
inval:
	if(dbuf != nil)
		putbuf(dbuf);
	respond(req, errstring[Einval]);
	return;
perm:
	if(dbuf != nil)
		putbuf(dbuf);
	respond(req, errstring[Eaccess]);
	return;
}

void
fshalt(Srv *)
{
	shutdown();
}

/* below is from nemo's Pg 252 */
Work
get(Buffer *b)
{
	Work w;

	if(shuttingdown)
		return (Work){nil,nil};
	qlock(&b->lck);
	if(b->nworks == 0){
		rsleep(&b->isempty);
		if(shuttingdown){
			qunlock(&b->lck);
			dprint("shuttingdown\n");
			return (Work){nil,nil};
		}
	}
	w = b->works[b->hd];
	b->hd = ++b->hd %Nworks;
	b->nworks--;
	if(b->nworks == Nworks-1)
		rwakeup(&b->isfull);
	qunlock(&b->lck);
	return w;
}

void stats(void);
void
put(Buffer *b, void (*f)(Req *r), Req *r)
{
	Work  w;

	if(shuttingdown){
		respond(r, errstring[Eshutdown]);
	}
	w.f = f;
	w.r = r;
	qlock(&b->lck);
	if(b->nworks == Nworks){
		rsleep(&b->isfull);
		if(shuttingdown){
			qunlock(&b->lck);
			respond(r, errstring[Eshutdown]);
		}
	}
	b->works[b->tl] = w;
	b->tl = ++b->tl % Nworks;
	b->nworks++;
	if(b->nworks == 1)
		rwakeup(&b->isempty);
	qunlock(&b->lck);
	if(chatty9p > 1)
		stats();
}

Buffer buf;
struct
{
	u32 pid;
	Work w;
} worker[Nprocs];	/* keeps track of running procs to flush */

void
initworks(Buffer *b)
{
	// release all locks, set everything to null values
	memset(b, 0, sizeof(*b));
	// set the locks used by the Rendezes
	b->isempty.l = &b->lck;
	b->isfull.l = &b->lck;
}

int
stopworkers(void)
{
	int i, a;

	a = 0;
	for(i = 0; i<Nprocs; i++){
		if(worker[i].w.f == nil){
			if(worker[i].pid > 0){
				// rwakeup(&buf.isempty); TODO why can't I get this to work?
				postnote(PNPROC, worker[i].pid, "interrupt");
				worker[i].pid = 0;
			}
		}else
			a++;
	}
	return a;
}

void
stats(void)
{
	int n, w, inv, i;

	n = w = inv = 0;
	for(i = 0; i<Nprocs; i++){
		if(worker[i].pid == 0)
			inv++;
		else if(worker[i].w.f == nil)
			n++;
		else if(worker[i].w.f != nil)
			w++;
	}
	dprint("Nprocs %d inv %d idle %d working %d buf nworks %d hd %d tl %d\n",
			Nprocs, inv, n, w, buf.nworks, buf.hd, buf.tl);
}

void
shutdown(void)
{
	int i, a;
/*	User *u, *v; */
	char srvfilename[Namelen];

	if(chatty9p > 1)
	dprint("shutdown\n");
	a = 0;
	fsok(1);
//	showextents(&frees);
	shuttingdown = 1;
	savefrees(Bdfrees);
//	dprint("&buf.isempty %#p\n", &buf.isempty);
//	rwakeupall(&buf.isempty);
	for(i = 0; i< 10; i++){
		a = stopworkers();
		sleep(a*1000);
	}
	if(a > 1){ /* 1 for this running process */
		dprint("%d processes still running\n", a);
		for(i = 0; i<Nprocs; i++){
			if(worker[i].pid > 0)
				dprint("running %#p\n", worker[i].w.f);
		}
		sleep(1000);
		postnote(PNGROUP, getpid(), "interrupt");
	}

	/* free users, why bother? leave it alone */
/*	u = t->users;
	while(u != nil){
		v = u->next;
		free(u);
		u = v;
	}
	t->users = nil;*/

	/* /srv/mafs_service file will not exist when mounted with -s */
	if(mpstarted &&
		sprint(srvfilename, "/srv/%s", service) > 5)
			remove(srvfilename);

	if(chatty9p > 1)
		dprint("shutdown: exiting\n");
	exits(nil);
	/* chkqunlock(&superlock); exit while holding the lock */
}

void
work(Buffer *b, int id)
{
	Work w;

	worker[id].pid = getpid();
	w = get(b);
	while(w.f != nil){
		worker[id].w = w;
		w.f(w.r);
		worker[id].w = (Work){nil, nil};
		w = get(b);
	}
	worker[id].pid = 0;
	dprint("worker %d exiting\n", id);
	exits(nil);
}

void
startproc(Buffer *b, int id)
{
	char name[16];

	switch(rfork(RFPROC|RFMEM|RFFDG)){
	case -1:
		panic("can't fork");
	case 0:
		if(chatty9p > 1)
		dprint("child %d pid: %d\n", id, getpid());
		break;
	default:
		return;
	}
	procname = name;
	snprint(name, 16, "mafs worker %d", id);
	procsetname(name);
	work(b, id);
	dprint("%s process exited\n", name);
	_exits(nil);
}

void
fsstart(Srv *)
{
	int i;

	mpstarted=1;
	initworks(&buf);
	if(chatty9p > 1)
	dprint("parent pid: %d\n", getpid());
	for(i = 0; i < Nprocs; i++){
		startproc(&buf, i);
	}
}

void
bfsopen(Req *r)
{
	put(&buf, fsopen, r);
}

void
bfscreate(Req *r)
{
	put(&buf, fscreate, r);
}

void
bfsread(Req *r)
{
	if(r->fid->qid.type == QTAUTH){
		authread(r);
		return;
	}else
		put(&buf, fsread, r);
}

void
bfswrite(Req *r)
{
	if(r->fid->qid.type == QTAUTH){
		authwrite(r);
		return;
	}else
		put(&buf, fswrite, r);
}

void
bfsstat(Req *r)
{
	put(&buf, fsstat, r);
}

void
bfswstat(Req *r)
{
	put(&buf, fswstat, r);
}

void
bfsremove(Req *r)
{
	put(&buf, fsremove, r);
}

/* multi process server */
Srv mpsrv = {
	.auth = auth9p,
	.attach = fsattach,
	.destroyfid = fsdestroyfid,
	.msize = Maxspansize,

	.walk1 = fswalk1,
	.clone = fsclone,

	.open = bfsopen,
	.create = bfscreate,
	.read = bfsread,
	.write = bfswrite,
	.stat = bfsstat,
	.wstat = bfswstat,
	.remove = bfsremove,

	.start = fsstart,
	.end = fshalt,
};

/* usrv is a uni process server */
Srv usrv = {
	.auth = auth9p,
	.attach = fsattach,
	.destroyfid = fsdestroyfid,
	.msize = Maxspansize,

	.walk1 = fswalk1,
	.clone = fsclone,

	.open = fsopen,
	.create = fscreate,
	.read = fsread,
	.write = fswrite,
	.stat = fsstat,
	.wstat = fswstat,
	.remove = fsremove,

	.end = fshalt,
};

/*
08:08 < joe7> is there a way to interact with the stdin of a process using srv() from rc? I understand that it runs the srv() loop between the fd's 0 and 1.
08:13 < joe7> I wrote an userspace file server and I want to test it without using postmountsrv (avoiding the pipe interface).
08:45 < cinap_lenrek> {yourfileserver <[0=1] &} | echo 0 >/srv/service
08:46 < cinap_lenrek> then you should be able to mount /srv/service
08:47 < cinap_lenrek> the <>{} operator might also work
08:50 < cinap_lenrek> mount <{ramfs -i <[0=1]} /n/ram
08:50 < cinap_lenrek> works.
 */
void
start9p(char **nets, int stdio)
{
	int sfd;

	while(nets && *nets){
		mpsrv.end = nil;	/* disable shutdown */
		listensrv(&mpsrv, *nets++);
	}
	if(stdio){
		usrv.infd = 0;
		usrv.outfd = 1;
		srv(&usrv);
	}else{
		sfd = postsrv(&mpsrv, service);
		if(sfd < 0)
			sysfatal("postsrv: %r");
		close(sfd);
	}
}

/*
static int
dir9p2(Dir* dir, Dentry* dentry, void* strs)
{
	char *op, *p;

	memset(dir, 0, sizeof(Dir));
	mkqid(dir->qid, dentry, 1);
	dir->mode = (dir->qid.type<<24)|(dentry->mode & 0777);
	dir->mtime = dentry->mtime;
	dir->length = dentry->size;

	op = p = strs;
	dir->name = p;
	p += sprint(p, "%s", dentry->name)+1;

	dir->uid = p;
	uidtostr(p, dentry->uid);
	p += strlen(p)+1;

	dir->gid = p;
	uidtostr(p, dentry->gid);
	p += strlen(p)+1;

	dir->muid = p;
	strcpy(p, "");
	p += strlen(p)+1;

	return p-op;
} */

s32
readfilesize(u64 dblkno, u64 qpath)
{
	u64 filesize;
	Iobuf *dbuf;

	dbuf = getbufchk(dblkno, 1, Breadonly, Tdentry, qpath);
	if(dbuf == nil)
		return 0;
	filesize = dbuf->io->d.size;
	putbuf(dbuf);
	return filesize;
}
s32
readfile(u64 dblkno, u64 qpath, char *rbuf, s32 rbufsize, u64 offset)
{
	Dentry *d;
	s64 tosend, sent, filesize;
	s32 n;
	Iobuf *dbuf, *buf;

	dbuf = getbufchk(dblkno, 1, Breadonly, Tdentry, qpath);
	if(dbuf == nil)
		return 0;
	d = &dbuf->io->d;
	filesize = d->size;

	if(offset >= filesize){
		putbuf(dbuf);
		return 0;
	}
	if(filesize - offset > rbufsize)
		tosend = rbufsize;
	else
		tosend = filesize - offset;

	for(sent = 0; sent < tosend && offset+sent < d->size; ){
		buf = getdataspanat(d, (offset+sent)/Maxspansize, Breadonly);
		if(buf == nil){
			putbuf(dbuf);
			return -1;
		}
		n = min(Maxspansize-((offset+sent)%Maxspansize), tosend-sent);
		memcpy(rbuf+sent, buf->io->buf+((offset+sent)%Maxspansize), n);
		sent += n;
		putbuf(buf);
	}
	putbuf(dbuf);
	return sent;
}

/* obsolete */
s32
updatefullspan(Dentry *d, char *wbuf, s32 wbufsize, u64 offset)
{
	Iobuf *buf;
	s32 n;

	if(d == nil || wbuf == nil || wbufsize == 0)
		return 0;
	buf = getdataspanat(d, offset/Maxspansize, Bmod);
	if(buf == nil)
		return -1;
	n = min(Maxspansize, wbufsize);
	memcpy(buf->io->buf, wbuf, n);
	putbuf(buf);
	return n;
}

/* only for updating existing data */
s32
update(Dentry *d, u64 /* dblkno */, char *wbuf, s32 wbufsize, u64 offset)
{
	Iobuf *buf;
	s32 howmuch, nblocks;
	u64 blkno, to;
	Spanid s;

	if(d == nil || wbuf == nil || wbufsize == 0)
		return 0;
	if(offset+wbufsize > d->size)
		panic("update(): should not be here\n"
				"	offset %llud wbufsize %d d->size %llud",
				offset, wbufsize, d->size);

	/*	get the extent
		overlay data
	 */

	rel2abs(d, offset/Maxspansize, &s);
	if(chatty9p > 1)
	dprint("update rel2abs d->name %s d->size %llud reli offset %llud\n"
				"	offset/Maxspansize %llud s.blkno %llud s.len %d\n",
				d->name, d->size, offset, offset/Maxspansize, s.blkno, s.len);
	blkno = s.blkno;
	nblocks = s.len;
	buf = getbufchk(blkno, nblocks, Bmod, Tdata, d->qid.path);
	if(buf == nil)
		return -1;

	/* overlay the new contents */
	to = offset%Maxspansize;
	howmuch = min(Maxspansize-to, wbufsize);
	memcpy(buf->io->buf+to, wbuf, howmuch);
	putbuf(buf);
	return howmuch;
}

s32
append(Dentry *d, u64 dblkno, char *wbuf, s32 wbufsize)
{
	Iobuf *buf, *nbuf;
	s32 howmuch, oldnblocks, newnblocks, nblocks;
	u64 blkno, nblkno, lastspansize;
	Spanid s;

	if(d == nil || wbuf == nil || wbufsize == 0)
		return 0;
	if(chatty9p > 1)
		dprint("append wbufsize %d\n", wbufsize);

	if((lastspansize=d->size%Maxspansize) == 0){
		/* last extent is full, use a new extent */
		if(chatty9p > 1)
		dprint("append new extent rel2abs d->name %s reli d->size %llud"
				" d->size/Maxspansize %llud\n",
				d->name, d->size, d->size/Maxspansize);
		nblocks = min(Maxspanlen, (wbufsize+sizeof(Tag))/Rawblocksize);
		if((wbufsize+sizeof(Tag))%Rawblocksize > 0)
			nblocks++;

		buf = allocblocks(nblocks, Bmod, Tdata, d->qid.path);
		if(buf == nil)
			return -1;
		blkno = buf->blkno;
		howmuch = min((nblocks*Rawblocksize)-sizeof(Tag), wbufsize);
		memcpy(buf->io->buf, wbuf, howmuch);
		putbuf(buf);

		if(addrelative(d, dblkno, d->size/Maxspansize, blkno,nblocks) == 0){
			freeblocks(blkno, nblocks, Tdata, d->qid.path);
			return -2;
		}
		return howmuch;
	}else{
		/* last extent is partially full */
		/*
		get the old extent and the new extent
		copy from the old extent to the new
		addrelative the new extent
		free the old extent */

		rel2abs(d, d->size/Maxspansize, &s);
		blkno = s.blkno;
		oldnblocks = s.len;
		if(s.len == 0)
			panic("append update rel2abs s.len == 0"
					" d->name %s reli d->size %llud"
					" d->size/Maxspansize %llud s.blkno %llud s.len %d\n",
					d->name, d->size, d->size/Maxspansize, s.blkno, s.len);
		if(chatty9p > 1)
		dprint("append update rel2abs d->name %s reli d->size %llud"
				" d->size/Maxspansize %llud s.blkno %llud s.len %d\n",
				d->name, d->size, d->size/Maxspansize, s.blkno, s.len);
		buf = getbufchk(blkno, oldnblocks, Bmod, Tdata, d->qid.path);
		if(buf == nil)
			return -1;

		if(lastspansize+wbufsize >= Maxspansize)
			newnblocks = Maxspanlen;
		else{
			newnblocks = (lastspansize+wbufsize+sizeof(Tag))/Rawblocksize;
			if((lastspansize+wbufsize+sizeof(Tag))%Rawblocksize > 0)
				newnblocks++;
		}
		nbuf = allocblocks(newnblocks, Bmod, Tdata, d->qid.path);
		if(nbuf == nil){
			putbuf(buf);
			return -1;
		}
		nblkno = nbuf->blkno;
		memcpy(nbuf->io->buf, buf->io->buf, lastspansize);
		howmuch = min(Maxspansize-lastspansize, wbufsize);
		if(chatty9p > 1)
		dprint("update lastspansize %llud newnblocks %llud"
				" nblkno %llud howmuch %llud\n",
				lastspansize, newnblocks, nblkno, howmuch);
		memcpy(nbuf->io->buf+lastspansize, wbuf, howmuch);
		// if(chatty9p > 1)
		//	dprint("append contents: %s\n", nbuf->io->buf);
		putbuf(nbuf);
		putbuf(buf);

		/* rel2abs() rlock's while reading */
		if(addrelative(d, dblkno, d->size/Maxspansize, nblkno,newnblocks) == 0){
			freeblocks(nblkno, newnblocks, Tdata, d->qid.path);
			return -2;
		}
		freeblocks(blkno, oldnblocks, Tdata, d->qid.path);
		return howmuch;
	}
}

/* pad blanks one span at a time */
static s32
padblanks(Dentry *d, u64 dblkno, s32 size)
{
	s8 *buf;
	int n;

	if(d == nil || size <= 0)
		return 0;
	n = min(size, Maxspansize - d->size%Maxspansize);
	buf = emalloc9p(n);
	n = append(d, dblkno, buf, n);
	free(buf);
	return n;
}

/*
	3 scenarios
		offset < filesize && offset+wbufsize <= filesize
			replacing the data in existing blocks
		offset <= filesize && offset+wbufsize > filesize
			replacing the data in existing blocks
			add data to new blocks
		offset > filesize
			new blank blocks until offset
			data blocks from offset until offset+wbufsize
 */
s32
writefile(u64 dblkno, u64 qpath, s16 uid, char *wbuf, s32 wbufsize, u64 offset)
{
	Dentry *d;
	s64 written;
	s32 n;
	Iobuf *dbuf;

	dbuf = getbufchk(dblkno, 1, Bmod, Tdentry, qpath);
	if(dbuf == nil)
		return 0;
	d = &dbuf->io->d;
	d->muid = uid;
	// odentry(d);

	for(written = 0; written < wbufsize; ){

		if(chatty9p > 1)
			dprint("writefile(): d->name %s d->size %llud offset %llud\n"
				"	written %d offset+written %llud wbufsize %llud\n",
				d->name, d->size, offset, written, offset+written, wbufsize);
		/* all the below functions only write upto the end of an extent.
		   Hence, the need for a loop to keep repeating.
		 */
		if(offset > d->size){
			/* new blank blocks until offset
				fill blank data upto offset */

			if(chatty9p > 1)
				dprint("writefile(): blank blocks until offset\n");

			n = padblanks(d, dblkno, offset+wbufsize-d->size);
			if(chatty9p > 1)
				dprint("writefile(): padblanks returned %d\n", n);
			if(n<0){
				dprint("padblanks has an issue %d\n", n);
				goto writeend;
			}else
				d->size += n;

		}else if(offset+written < d->size){
			/* replacing existing data
				no change to file size */

			if(chatty9p > 1)
				dprint("writefile(): replace existing data\n");

			n = update(d, dblkno,
							wbuf+written, /* from where */
							/* how much */
							min(wbufsize-written, d->size-(offset+written)),
							offset+written);	/* to where */
			if(chatty9p > 1)
				dprint("writefile(): update returned %d\n", n);
			if(n<0){
				dprint("update has an issue %d\n", n);
				goto writeend;
			}else
				written += n;

		}else if(offset+written >= d->size){
			/* append data, changes file size
				data blocks from offset until offset+wbufsize */
			if(chatty9p > 1)
				dprint("writefile(): append\n");
			n = append(d, dblkno, wbuf+written, /* from where */
							wbufsize-written		/* how much */);
			if(chatty9p > 1)
				dprint("writefile(): append returned %d\n", n);
			if(n<0){
				dprint("append has an issue %d\n", n);
				goto writeend;
			}
			written += n;
			d->size += n;
		}
		else
			panic("writefile: should not be here");
	}

writeend:
	d->mtime = nsec();
	putbuf(dbuf);
	return written;
}