code: mafs

ref: 84d66beb90246495861436c041297528298ba74f
dir: /9p.c/

View raw version
#include "all.h"

Tlock	*tlockhead = nil, *tlocktail = nil;
QLock	tlock;
extern u32 mpsrvpid;
extern u8 synchronouswrites;

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);
s32 writeallappend(Iobuf *dbuf, Dentry *d, u64 dblkno);

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;

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

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

	dbuf = getmetachk(Bdroot, Breadonly, Tdentry, Qproot);
	if(dbuf == nil){
		respond(req, "phase error");
		return;
	}
	req->fid->aux = newaux(Bdroot, uid);
	req->fid->qid = (Qid){Qproot, dbuf->cur->version, QTDIR};
	putbuf(dbuf, 0);
	req->ofcall.qid = req->fid->qid;
	respond(req, nil);
}

static void
fsdestroyfid(Fid *fid)
{
	Tlock *t;

	if((fid->qid.type & QTAUTH) != 0){
		authdestroy(fid);
		return;
	}
	if(((Aux*)fid->aux)->tlocked == 1){
		qlock(&tlock);
		if(tlocktail == nil){
			qunlock(&tlock);
			panic("locked but tlock queue is empty\n");
			return;
		}else{
			for(t = tlockhead; t != nil; t = t->next){
				if(t->dblkno == ((Aux*)fid->aux)->dblkno &&
					t->qpath == fid->qid.path){
					if(t->prev != nil)
						t->prev->next = t->next;
					if(t->next != nil)
						t->next->prev = t->prev;
					if(tlocktail == t)
						tlocktail = t->prev;
					if(tlockhead == t)
						tlockhead = t->next;
					((Aux*)fid->aux)->tlocked = 0;
					free(t);
					break;
				}
			}
		}
		qunlock(&tlock);
	}
/*	dbuf = getmetachk(((Aux*)fid->aux)->dblkno,
						Bwritable, Tdentry, fid->qid.path);
	if(dbuf != nil){
		writeallappend(dbuf, (Dentry*)dbuf->new, ((Aux*)fid->aux)->dblkno);
		putbuf(dbuf, 1);
	} */
	freeaux(fid->aux);
}

static void
fsmkdir(Dentry *d, Dir *dir, char *buf, u64 appendsize)
{
	memset(dir, 0, sizeof(*dir));
	dir->qid = (Qid){d->path, d->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+appendsize;
	if(dir->qid.type & QTDIR)
		dir->length = 0;
	if(buf == nil){
		dir->name = estrdup9p(d->name);
		dir->uid = username(d->uid, dir->uid);
		dir->gid = username(d->gid, dir->gid);
		dir->muid = username(d->muid, dir->muid);
	}else{
		memset(buf, 0, Namelen + 3 * Userlen);
		strncpy(buf, d->name, Namelen - 1);
		dir->name = buf;
		dir->uid = username(d->uid, buf + Namelen);
		dir->gid = username(d->gid, buf + Namelen + Userlen);
		dir->muid = username(d->muid, buf + Namelen + 2 * Userlen);
	}
}

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

	dbuf = getmetachk(((Aux*)req->fid->aux)->dblkno, 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 = (Dentry*)dbuf->cur;
	/* nothing to do for already zero'ed out slots */
	if(d->path != Qpnone)
		fsmkdir(d, &req->d, nil, dbuf->appendsize);

	putbuf(dbuf, 0);
	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]);
		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 = getmetachk(((Aux*)req->fid->aux)->dblkno, Bwritable,
						Tdentry, req->fid->qid.path);
	if(dbuf == nil){
		respond(req, errstring[Eperm]);
		return;
	}
	d = (Dentry*)dbuf->new;
	if(canaccess(((Aux*)req->fid->aux)->uid, d, DMWRITE) == 0){
		putbuf(dbuf, 0);
		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, 0);
		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){
			d->gid = gid;
		}
	}

	respond(req, nil);
	putbuf(dbuf, 1);
	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]);
		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){
		/* using this loop to skip over zero'ed out blocks */
		dbuf = cbuf = nil;
		do{
			if(dbuf != nil)
				freesearchstate(&dbuf, &cbuf);
			/* 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;
			}
			((Aux*)req->fid->aux)->dri++;
		}while(d->path == 0);

		fsmkdir(d, &dir, nbuf, cbuf->appendsize);
		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;
		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)
{
	s32 rv;

	if(shuttingdown){
		respond(req, errstring[Eshutdown]);
		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;
	}

	rv = 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(rv == -3)
		respond(req, errstring[Efull]);
	else if(rv == -1 || rv == -2)
		respond(req, errstring[Ephase]);
	else{
		req->ofcall.count = rv;
		req->ofcall.offset = req->ifcall.offset+rv;
		respond(req, nil);
	}
}

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

	if(shuttingdown){
		respond(req, errstring[Eshutdown]);
		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){
		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 = getmetachk(aux->dblkno, Breadonly, Tdentry, fid->qid.path);
			if(dbuf == nil)
				return errstring[Ephase];
			d = (Dentry*)dbuf->cur;
			if(chatty9p > 1)
				dprint("fswalk1 .. d->name %s d->qid.path %llud d->pdblkno %llud\n",
						d->name, d->path, d->pdblkno);
			pbuf = getmetachk(d->pdblkno, Breadonly, Tdentry, d->pqpath);
			if(pbuf == nil)
				return errstring[Ephase];
			putbuf(dbuf, 0);
			p = (Dentry*)pbuf->cur;
			*qid = (Qid){p->path, p->version, (p->mode&DMDIR) ? QTDIR : QTFILE};
			if(aux != nil)
				aux->dblkno = pbuf->blkno;
			putbuf(pbuf, 0);
			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->path, d->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){
		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->newr.
 */
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;
	u64 zblkno;	/* zero'ed dentry that can be reused */
	Aux *aux;
	u32 perm;
	Fid *fid;
	Tlock *t;

	/* no create's when shutting down */
	if(shuttingdown){
		respond(req, errstring[Eshutdown]);
		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 = getmetachk(aux->dblkno, Bwritable, Tdentry, fid->qid.path);
	if(dbuf == nil){
		respond(req, errstring[Ephase]);
		return;
	}
	dparent = (Dentry*)dbuf->new;
	if(canaccess(((Aux*)req->fid->aux)->uid, dparent, DMWRITE) == 0){
		respond(req, errstring[Eperm]);
		return;
	}

	if(canaccess(aux->uid, dparent, DMWRITE) == 0){
		putbuf(dbuf, 0);
		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, 0);
			respond(req, errstring[Emode]);
			return;
	}
	for(reli = 0, blkno = 1; blkno > 0; reli++){
		blkno = rel2abs(dparent, reli);
		/* if(reli >= Max)
			error(Etoobig); */

		if(blkno == 0){
			/* end reached, nothing found, create */
			if(zblkno != 0){
				cbuf = getmetachk(zblkno, Bwritable, Tdentry, Qpnone);
				if(cbuf == nil){
					putbuf(dbuf, 0);
					respond(req, errstring[Ephase]);
					return;
				}
			}else{
				cbuf = allocmeta(Tdentry, fid->qid.path);
				if(cbuf == nil){
					putbuf(dbuf, 0);
					respond(req, errstring[Efull]);
					return;
				}
			}
			dchild = (Dentry*)cbuf->new;
			dchild->size = 0;
			dchild->pdblkno = dbuf->blkno;
			dchild->pqpath = dparent->path;
			dchild->mtime = nsec();
			dchild->uid = dchild->muid = aux->uid;
			dchild->gid = dparent->gid;
			/* TODO DMAPPEND attributes */
			if(perm&DMDIR){
				if((req->ifcall.mode & OTRUNC) || (perm & DMAPPEND) ||
					(fid->omode & OWRITE)){
					putbuf(cbuf, 1);
					putbuf(dbuf, 0);
					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)
				dchild->mode |= DMEXCL;
			if(canaccess(aux->uid, dchild, DMWRITE) == 0){
				putbuf(cbuf, 0);
				putbuf(dbuf, 0);
				respond(req, errstring[Eaccess]);
				return;
			}
			dchild->qpath = dchild->path = newqpath();
			dchild->version = 0;
			strncpy(dchild->name, req->ifcall.name, Namelen);
			fid->qid = (Qid){dchild->path, 0, (perm&DMDIR) ? QTDIR : QTFILE};
			aux->dblkno = cbuf->blkno;
			aux->dri = 0;
			if(perm&DMEXCL){
				t = emalloc9p(sizeof(Tlock));
				t->time = nsec();
				t->qpath = dchild->path;
				t->dblkno = aux->dblkno;
				qlock(&tlock);
				if(tlocktail == nil){
					tlocktail = tlockhead = t;
				}else{
					tlocktail->next = t;
					t->prev = tlocktail;
					tlocktail = t;
				}
				qunlock(&tlock);
				aux->tlocked = 1;
			}else
				aux->tlocked = 0;
			req->ofcall.qid = fid->qid;
			req->ofcall.iounit = Iounit;
			settag(cbuf, Tdentry, dchild->path);
			putbuf(cbuf, 1);	/* 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) == 0){
				putbuf(dbuf, 1);
				respond(req, errstring[Ephase]);
				return;
			}
			putbuf(dbuf, 1);
			respond(req, nil);
			return;
		}else{
			cbuf = getmeta(blkno, Breadonly, Bused);
			if(cbuf == nil){
				putbuf(dbuf, 0);
				respond(req, errstring[Ephase]);
				return;
			}
			dchild = (Dentry*)cbuf->cur;

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

			if(dchild->tag != Tdentry){
				putbuf(cbuf, 0);
				putbuf(dbuf, 0);
				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->qpath, dchild->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, 0);
				if(req->ofcall.qid.path >= Qpusers &&
					req->ofcall.qid.type == QTFILE)
					truncatefile(req->ofcall.qid.path, blkno, aux->uid);
				putbuf(dbuf, 0);
				respond(req, nil);
				return;
			}
			Nextdentry:
				putbuf(cbuf, 0);
		}
	}
}

/* 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;
	Tlock *t;

	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 = getmetachk(aux->dblkno, Breadonly, Tdentry, fid->qid.path);
	if(dbuf == nil){
		respond(req, errstring[Ephase]);
		return;
	}
	d = (Dentry*)dbuf->cur;

	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){
		qlock(&tlock);
		if(tlocktail != nil){
			for(t = tlockhead; t != nil; t = t->next){
				if(t->dblkno == ((Aux*)fid->aux)->dblkno &&
					t->qpath == fid->qid.path){
					qunlock(&tlock);
					respond(req, "DMEXCL locked");
					return;
				}
			}
		}
		t = emalloc9p(sizeof(Tlock));
		t->time = nsec();
		t->qpath = fid->qid.path;
		t->dblkno = aux->dblkno;
		if(tlocktail == nil){
			tlocktail = tlockhead = t;
		}else{
			tlocktail->next = t;
			t->prev = tlocktail;
			tlocktail = t;
		}
		qunlock(&tlock);
		aux->tlocked = 1;
	}
	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->path, d->version, (d->mode&DMDIR) ? QTDIR : QTFILE};
	fid->omode = omode;
	req->ofcall.iounit = Iounit;
	req->ofcall.qid = fid->qid;

	putbuf(dbuf, 0);
	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, 0);
	respond(req, errstring[Einval]);
	return;
perm:
	if(dbuf != nil)
		putbuf(dbuf, 0);
	respond(req, errstring[Eaccess]);
	return;
}

/* below is from nemo's Pg 252 */
typedef	struct	Buffer	Buffer;
typedef	struct	Work	Work;

struct Work
{
	void (*f)(Req *r);
	Req *r;
};

struct Buffer
{
	QLock lck;
	Work works[Nworks];
	u16 hd, tl, nworks;
	Rendez isfull;	/* throttling */
	Rendez isempty; /* workers do not have to keep polling to find work */
};

Buffer buf;

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);
			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;
}

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

	if(shuttingdown){
		respond(r, errstring[Eshutdown]);
		qlock(&b->lck);
		rwakeupall(&b->isempty);
		qunlock(&b->lck);
	}
	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();
}

struct
{
	u32 pid;
	Work w;
} worker[Nworkers];	/* 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<Nworkers; i++){
		if(worker[i].w.f == nil){
			if(worker[i].pid > 0 && worker[i].pid != getpid()){
				// 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;
}

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

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

/*
	there are 2 ways to shutdown:
	1. by unmounting and removing the /srv/mfs_service file (can be rm and unmount too)
	2. by writing halt to /adm/ctl file. unmount() the file system to keep it clean.

	In the first instance, the srv() process is driving the shutdown. It calls fsend().
		rm /srv/mfs_service file does not wait for fsend() to finish. Hence, there
		is no way to ensure that any pending writes have been flushed to the disk.
		If the system is shutdown or restarted immediately, there is a high possibility
		that the filesystem will be in an inconsistent state. This is more probable
		when disk/mafs (asynchronous writes) is used.
	In the second instance, fsend() is called by the worker process. It does not return
		until all the pending writes have been flushed to the disk. It also removes the
		/srv/mfs_service file and also stops the srv() process. Hence, this is the
		preferred approach to shutting down the file system.

	There is no way to unmount() automatically on shutdown. The mount() and unmount()
		calls are client driven and it is not the responsibility of the server to find
		all the clients that mounted it. Just shutdown and let the respective clients
		deal with their mess.
 */
/* TODO need to track open fids and close them */
void
shutdown(void)
{
	u64 n;
/*	User *u, *v; */

	if(chatty9p > 1)
		dprint("shutdown\n");
	shuttingdown = 1;
	while((n=sync()) > 0){
		// if(chatty9p > 1)
		dprint("shutdown: sync() finds %llud locked blocks\n", n);
		sleep(1000);
	}
	fsok(1);
//	showextents(&frees);
//	dprint("&buf.isempty %#p\n", &buf.isempty);
	if(mpsrvpid){
		qlock(&buf.lck);
		rwakeupall(&buf.isempty);
		qunlock(&buf.lck);
	}
/*	if(synchronouswrites == 0){
		while((n=pendingwrites())>0){
			if(chatty9p > 1)
			dprint("shutdown: pendingwrites %llud of write queue\n", n);
			sleep(1000);
		}
	}
*/
	savefrees(Bdfrees);
/*	if(synchronouswrites == 0){
		while((n=pendingwrites())>0){
			if(chatty9p > 1)
			dprint("shutdown: pendingwrites %llud of frees\n", n);
			sleep(1000);
		}
		stopwriter();
	}
*/
	/* free users, why bother? leave it alone */
/*	u = t->newrs;
	while(u != nil){
		v = u->next;
		free(u);
		u = v;
	}
	t->newrs = nil;*/

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

	close(devfd);
}

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;
}

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

	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;
	}
	snprint(name, 128, "%s worker %d", service, id);
	procsetname(name);
	work(b, id);
	if(chatty9p)
		dprint("%s process exited\n", name);
	exits(nil);
}

void
fsstart(Srv *)
{
	int i;

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

/* read the comment above shutdown() to understand */
void
fsend(Srv *)
{
	if(shuttingdown == 0)
		shutdown();
	/* no need to bother removing /srv/service
		as we do for cmdhalt in ctlwrite() of ctl.c
		as the srv() takes care of that cleanup */
}

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 = Iounit,

	.walk1 = fswalk1,
	.clone = fsclone,

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

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

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

	.walk1 = fswalk1,
	.clone = fsclone,

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

	.end = fsend,
};

/*
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(int stdio)
{
	int sfd;

	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 = getmetachk(dblkno, Breadonly, Tdentry, qpath);
	if(dbuf == nil)
		return 0;
	filesize = dbuf->cur->size;
	putbuf(dbuf, 0);
	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;
	u64 datablocksize;

	sent = 0;
	dbuf = getmetachk(dblkno, Breadonly, Tdentry, qpath);
	if(dbuf == nil)
		return 0;
	d = (Dentry*)dbuf->cur;
	filesize = d->size+dbuf->appendsize;

	if(offset >= filesize)
		goto readend;
	if(filesize < Ddatasize){
		n = min(filesize-offset, rbufsize);
		memcpy(rbuf, d->buf+offset, n);
		sent = n;
		goto readend;
	}
	if(filesize - offset > rbufsize)
		tosend = rbufsize;
	else
		tosend = filesize - offset;
	for(sent = 0; sent < tosend && offset+sent < d->size; ){
		buf = getdatablkat(d, (offset+sent)/Maxdatablocksize);
		if(buf == nil){
			putbuf(dbuf, 0);
			return -1;
		}
		if(buf->len == Maxdatablockunits)
			datablocksize = Maxdatablocksize;
		else
			datablocksize = buf->len*Blocksize -sizeof(Datahdr) -sizeof(u64 /* trailing path */);
		n = min(datablocksize-((offset+sent)%Maxdatablocksize), tosend-sent);
		memcpy(rbuf+sent, buf->io->buf+((offset+sent)%Maxdatablocksize), n);
		sent += n;
		putbuf(buf, 0);
	}
	if(dbuf->append && sent < tosend && offset+sent < d->size+dbuf->appendsize){
		/* assuming that rbufsize < Maxdatablocksize */
		n = min(d->size+dbuf->appendsize-(offset+sent), tosend-sent);
		memcpy(rbuf+sent, dbuf->append+(offset+sent-d->size), n);
		sent += n;
	}
readend:
	putbuf(dbuf, 0);
	return sent;
}

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

	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
	 */
	blkno = rel2abs(d, offset/Maxdatablocksize);
	if(offset/Maxdatablocksize < d->size/Maxdatablocksize)
		nblocks = Maxdatablockunits;
	else
		nblocks = nlastdatablocks(d->size);
	if(chatty9p > 1)
	dprint("update d->name %s d->size %llud offset %llud"
				" rel2abs(offset/Datablocksize %llud) = blkno %llud\n",
				d->name, d->size, offset, offset/Maxdatablocksize, blkno);
	buf = getbufchk(blkno, nblocks, Bwritable, Tdata, d->path);
	if(buf == nil)
		return -1;

	/* overlay the new contents */
	to = offset%Maxdatablocksize;
	howmuch = min((nblocks*Blocksize)-to, wbufsize);
	if(chatty9p > 1){
		dprint("updating buf->blkno %llud offset %llud size %llud\n",
				buf->blkno, to, howmuch);
		showbuf(buf);
	}
	memcpy(buf->io->buf+to, wbuf, howmuch);
	if(chatty9p > 1){
		dprint("update after\n");
		showbuf(buf);
	}
	putbuf(buf, 1);
	return howmuch;
}

/*
	Scenarios:
	1. last block is full or no last block
	2. last block is partially full
 */
s32
writefullappend(Iobuf *dbuf, Dentry *d, u64 dblkno)
{
	Iobuf *oldbuf, *newbuf;
	s32 howmuch;
	u64 newblkno, newbufsize, lastdatablksize, oldblkno;

	/* last data block is full or there is no last block.
		write out the append
	 */
	if(d->size%Maxdatablocksize == 0 &&
		dbuf->appendsize == Maxdatablocksize){

		/* allocate new blocks */
		newbuf = allocblocks(Maxdatablockunits,Tdata, d->path);
		if(newbuf == nil)
			return -1;
		newblkno = newbuf->blkno;
		newbuf->io->dblkno = dblkno;

		/* add the contents of append to those new blocks */
		memcpy(newbuf->io->buf, dbuf->append, dbuf->appendsize);

		putbuf(newbuf, 1);

		/* add this newly allocated blocks to the Dentry */
		if(addrelative(d, dblkno, d->size/Maxdatablocksize, newblkno) == 0){
			panic("could not write Tdata block\n");
			freeblocks(newblkno, Maxdatablockunits, Tdata, d->path);
			return -1;
		}
		d->size += dbuf->appendsize;
		dbuf->appendsize = 0;
		return 1;

	}else if(d->size%Maxdatablocksize > 0 &&
			(d->size%Maxdatablocksize)+dbuf->appendsize >= Maxdatablocksize){

		/* last data block is not full.
			write a full last data block and leave the rest in append
		 */

		/* allocate new blocks */
		newbuf = allocblocks(Maxdatablockunits, Tdata, d->path);
		if(newbuf == nil)
			return -1;
		newblkno = newbuf->blkno;
		newbuf->io->dblkno = dblkno;
		newbufsize = 0;

		/* read from the last block */
		/* copy that to the new allocated blocks */
		oldbuf = nil;
		if(lastdatablksize=d->size%Maxdatablocksize){
			/* partial block, above is = not == */
			oldblkno = rel2abs(d, d->size/Maxdatablocksize);
			oldbuf = getbufchk(oldblkno, nlastdatablocks(lastdatablksize),
								Bwritable, Tdata, d->path);
			if(oldbuf == nil){
				freeblocks(newblkno, Maxdatablockunits, Tdata, d->path);
				return -1;
			}
			memcpy(newbuf->io->buf, oldbuf->io->buf, lastdatablksize);
			newbufsize = lastdatablksize;
		}

		/* add the contents of append to those new blocks */
		howmuch = Maxdatablocksize-newbufsize;
		memcpy(newbuf->io->buf+newbufsize, dbuf->append, howmuch);

		/* move the left over append stuff to the front */
		memmove(dbuf->append, dbuf->append+howmuch, dbuf->appendsize-howmuch);

		dbuf->appendsize -= howmuch;
		putbuf(newbuf, 1);

		/* add this newly allocated blocks to the Dentry */
		if(addrelative(d, dblkno, d->size/Maxdatablocksize, newblkno) == 0){
			panic("could not write Tdata block\n");
			freeblocks(newblkno, Maxdatablockunits, Tdata, d->path);
			return -2;
		}

		/* free the old last blocks */
		if(oldbuf)
			putbuffree(oldbuf);
		d->size += howmuch;
		return 1;
	}
	return 0;
}

/*
	Scenarios:
	1. last block is full or no last block
	2. last block is partially full
 */
s32
writeallappend(Iobuf *dbuf, Dentry *d, u64 dblkno)
{
	Iobuf *oldbuf, *newbuf;
	u64 newblkno, newbufsize, lastdatablksize, oldblkno, newblocks;
	s32 rv;

	if(dbuf->appendsize == 0)
		return 1;

	rv = 0;
	if((d->size%Maxdatablocksize)+dbuf->appendsize >= Maxdatablocksize){
		rv = writefullappend(dbuf, d, dblkno);
		if(rv < 0)
			return rv;
	}

	if(rv < 0)
		return rv;
	if(dbuf->appendsize == 0)
		return 1;

	/* last data block is full or there is no last block. */
	if(d->size%Maxdatablocksize == 0){

		/* allocate new blocks */
		newbuf = allocblocks(nlastdatablocks(dbuf->appendsize),Tdata, d->path);
		if(newbuf == nil)
			return -1;
		newblkno = newbuf->blkno;
		newbuf->io->dblkno = dblkno;

		/* add the contents of append to those new blocks */
		memcpy(newbuf->io->buf, dbuf->append, dbuf->appendsize);

		putbuf(newbuf, 1);

		/* add this newly allocated blocks to the Dentry */
		if(addrelative(d, dblkno, d->size/Maxdatablocksize, newblkno) == 0){
			panic("could not write Tdata block\n");
			freeblocks(newblkno, nlastdatablocks(dbuf->appendsize), Tdata, d->path);
			return -1;
		}
		d->size += dbuf->appendsize;
		dbuf->appendsize = 0;
		return 1;
	}

	/* allocate new blocks */
	newblocks = nlastdatablocks((d->size%Maxdatablocksize) +dbuf->appendsize);
	newbuf = allocblocks(newblocks, Tdata, d->path);
	if(newbuf == nil)
		return -1;
	newblkno = newbuf->blkno;
	newbufsize = 0;
	newbuf->io->dblkno = dblkno;

	/* read from the last block */
	/* copy that to the new allocated blocks */
	oldbuf = nil;
	if(lastdatablksize=d->size%Maxdatablocksize){
		/* partial block, above is = not == */
		oldblkno = rel2abs(d, d->size/Maxdatablocksize);
		oldbuf = getbufchk(oldblkno, nlastdatablocks(lastdatablksize),
							Bwritable, Tdata, d->path);
		if(oldbuf == nil){
			freeblocks(newblkno, newblocks, Tdata, d->path);
			return -1;
		}
		memcpy(newbuf->io->buf, oldbuf->io->buf, lastdatablksize);
		newbufsize = lastdatablksize;
	}

	/* add the contents of append to those new blocks */
	/* writefullappend() takes care of bigger sizes */
	memcpy(newbuf->io->buf+newbufsize, dbuf->append, dbuf->appendsize);
	newbuf->io->len = newblocks;

	/* nothing left in append to move to the front */

	putbuf(newbuf, 1);

	/* add this newly allocated blocks to the Dentry */
	if(addrelative(d, dblkno, d->size/Maxdatablocksize, newblkno) == 0){
		panic("could not write Tdata block\n");
		freeblocks(newblkno, newblocks, Tdata, d->path);
		return -2;
	}

	/* free the old last blocks */
	if(oldbuf)
		putbuffree(oldbuf);
	d->size += dbuf->appendsize;
	dbuf->appendsize = 0;
	return 1;
}

/*
	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, rv;
	Iobuf *dbuf;
	u8 dowrite;

	dbuf = getmetachk(dblkno, Bwritable, Tdentry, qpath);
	if(dbuf == nil)
		return 0;
	d = dbuf->new;
	d->muid = uid;
	rv = dowrite = written = 0;
	// odentry(d);

	// little data, stuff it in the Dentry
	if(d->size+dbuf->appendsize<=Ddatasize && offset+wbufsize <= Ddatasize){
		memcpy(d->buf+offset, wbuf, wbufsize);
		if(offset+wbufsize > d->size)
			d->size = offset+wbufsize;
		written = wbufsize;
		dowrite = 1;
		goto writeend;
	}
	// more data, relocate it to a proper data block
	if(d->size+dbuf->appendsize <= Ddatasize && offset+wbufsize > Ddatasize){
		if(dbuf->append == nil){
			dbuf->append = allocmemunits(Maxdatablockunits);
			if(dbuf->append == nil){
				rv = -1;
				goto writeend;
			}
			dbuf->appendsize = 0;
		}
		memcpy(dbuf->append, d->buf, d->size);
		dbuf->appendsize = d->size;
		d->size = 0;
		dowrite = 1;
	}

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

		if(chatty9p > 1)
			dprint("writefile(): d->name %s d->size %llud offset %llud"
					" written %d offset+written %llud wbufsize %ld\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+dbuf->appendsize){
			/* new blank blocks until offset
				fill blank data upto offset */

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

			if(dbuf->append == nil){
				dbuf->append = allocmemunits(Maxdatablockunits);
				if(dbuf->append == nil){
					rv = -1;
					goto writeend;
				}
				dbuf->appendsize = 0;
			}
			n = min(Maxdatablocksize, offset-d->size);
			dbuf->appendsize = n;

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

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

			rv = 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 &&
					offset+written < d->size+dbuf->appendsize){
			/* changing append contents */
			n = min(d->size+dbuf->appendsize-(offset+written),wbufsize-written);
			memcpy(dbuf->append+(offset+written-d->size), wbuf+written, n);
			written += n;

		}else if(offset+written >= d->size+dbuf->appendsize){
			/* assuming that wbufsize <= append */
			/* append data, changes file size
				data blocks from offset until offset+wbufsize */
			if(dbuf->append == nil){
				dbuf->append = allocmemunits(Maxdatablockunits);
				if(dbuf->append == nil){
					rv = -1;
					goto writeend;
				}
				dbuf->appendsize = 0;
			}
			n = min(Maxdatablocksize-dbuf->appendsize, wbufsize-written);
			memcpy(dbuf->append+dbuf->appendsize, wbuf+written, n);
			dbuf->appendsize += n;
			written += n;
		}
		else
			panic("writefile: should not be here");

		if(dbuf->appendsize == Maxdatablocksize){
			dowrite = 1;
			if((rv=writefullappend(dbuf, d, dblkno)) < 0)
				goto writeend;
		}
	}

writeend:
	d->mtime = nsec();
	putbuf(dbuf, dowrite);

	/* How will this work for partial writes? */
	if(rv < 0)
		return rv;
	return written;
}