| /* Copyright © 1994-1999 Lucent Technologies Inc.  All rights reserved. | 
 |  * Portions Copyright © 1997-1999 Vita Nuova Limited | 
 |  * Portions Copyright © 2000-2007 Vita Nuova Holdings Limited | 
 |  *                                (www.vitanuova.com) | 
 |  * Revisions Copyright © 2000-2007 Lucent Technologies Inc. and others | 
 |  * | 
 |  * Modified for the Akaros operating system: | 
 |  * Copyright (c) 2013-2014 The Regents of the University of California | 
 |  * Copyright (c) 2013-2015 Google Inc. | 
 |  * | 
 |  * Permission is hereby granted, free of charge, to any person obtaining a copy | 
 |  * of this software and associated documentation files (the "Software"), to deal | 
 |  * in the Software without restriction, including without limitation the rights | 
 |  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | 
 |  * copies of the Software, and to permit persons to whom the Software is | 
 |  * furnished to do so, subject to the following conditions: | 
 |  * | 
 |  * The above copyright notice and this permission notice shall be included in | 
 |  * all copies or substantial portions of the Software. | 
 |  * | 
 |  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | 
 |  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | 
 |  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE | 
 |  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | 
 |  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | 
 |  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | 
 |  * SOFTWARE. */ | 
 |  | 
 | #include <slab.h> | 
 | #include <kmalloc.h> | 
 | #include <kref.h> | 
 | #include <string.h> | 
 | #include <stdio.h> | 
 | #include <assert.h> | 
 | #include <error.h> | 
 | #include <cpio.h> | 
 | #include <pmap.h> | 
 | #include <smp.h> | 
 | #include <net/ip.h> | 
 | #include <smallidpool.h> | 
 |  | 
 | struct dev mntdevtab; | 
 |  | 
 | static char *devname(void) | 
 | { | 
 | 	return mntdevtab.name; | 
 | } | 
 |  | 
 | /* | 
 |  * References are managed as follows: | 
 |  * The channel to the server - a network connection or pipe - has one | 
 |  * reference for every Chan open on the server.  The server channel has | 
 |  * c->mux set to the Mnt used for muxing control to that server.  Mnts | 
 |  * have no reference count; they go away when c goes away. | 
 |  * Each channel derived from the mount point has mchan set to c, | 
 |  * and increfs/decrefs mchan to manage references on the server | 
 |  * connection. | 
 |  */ | 
 |  | 
 | #define MAXRPC (IOHDRSZ+8192) | 
 | #define MAXTAG MAX_U16_POOL_SZ | 
 |  | 
 | static __inline int isxdigit(int c) | 
 | { | 
 | 	if ((c >= '0') && (c <= '9')) | 
 | 		return 1; | 
 | 	if ((c >= 'a') && (c <= 'f')) | 
 | 		return 1; | 
 | 	if ((c >= 'A') && (c <= 'F')) | 
 | 		return 1; | 
 | 	return 0; | 
 | } | 
 |  | 
 | struct mntrpc { | 
 | 	struct chan *c;		/* Channel for whom we are working */ | 
 | 	struct mntrpc *list;	/* Free/pending list */ | 
 | 	struct fcall request;	/* Outgoing file system protocol message */ | 
 | 	struct fcall reply;	/* Incoming reply */ | 
 | 	struct mnt *m;		/* Mount device during rpc */ | 
 | 	struct rendez r;	/* Place to hang out */ | 
 | 	uint8_t *rpc;		/* I/O Data buffer */ | 
 | 	unsigned int rpclen;	/* len of buffer */ | 
 | 	struct block *b;	/* reply blocks */ | 
 | 	char done;		/* Rpc completed */ | 
 | 	uint64_t stime;		/* start time for mnt statistics */ | 
 | 	uint32_t reqlen;	/* request length for mnt statistics */ | 
 | 	uint32_t replen;	/* reply length for mnt statistics */ | 
 | 	struct mntrpc *flushed;	/* message this one flushes */ | 
 | }; | 
 |  | 
 | /* Our TRUNC and remove on close differ from 9ps, so we'll need to translate. | 
 |  * I got these flags from http://man.cat-v.org/plan_9/5/open */ | 
 | #define MNT_9P_OPEN_OTRUNC		0x10 | 
 | #define MNT_9P_OPEN_ORCLOSE		0x40 | 
 |  | 
 | struct Mntalloc { | 
 | 	spinlock_t l; | 
 | 	struct mnt *list;		/* Mount devices in use */ | 
 | 	struct mnt *mntfree;		/* Free list */ | 
 | 	struct mntrpc *rpcfree; | 
 | 	int nrpcfree; | 
 | 	int nrpcused; | 
 | 	uint32_t id; | 
 | 	struct u16_pool *tags; | 
 | } mntalloc; | 
 |  | 
 | void mattach(struct mnt *, struct chan *, char *unused_char_p_t); | 
 | struct mnt *mntchk(struct chan *); | 
 | void mntdirfix(uint8_t * unused_uint8_p_t, struct chan *); | 
 | struct mntrpc *mntflushalloc(struct mntrpc *, uint32_t); | 
 | void mntflushfree(struct mnt *, struct mntrpc *); | 
 | void mntfree(struct mntrpc *); | 
 | void mntgate(struct mnt *); | 
 | void mntpntfree(struct mnt *); | 
 | void mntqrm(struct mnt *, struct mntrpc *); | 
 | struct mntrpc *mntralloc(struct chan *, uint32_t); | 
 | size_t mntrdwr(int unused_int, struct chan *, void *, size_t, off64_t); | 
 | int mntrpcread(struct mnt *, struct mntrpc *); | 
 | void mountio(struct mnt *, struct mntrpc *); | 
 | void mountmux(struct mnt *, struct mntrpc *); | 
 | void mountrpc(struct mnt *, struct mntrpc *); | 
 | int rpcattn(void *); | 
 | struct chan *mntchan(void); | 
 |  | 
 | void (*mntstats) (int unused_int, struct chan *, uint64_t, uint32_t); | 
 |  | 
 | static void mntinit(void) | 
 | { | 
 | 	mntalloc.id = 1; | 
 | 	mntalloc.tags = create_u16_pool(MAXTAG); | 
 | 	(void) get_u16(mntalloc.tags);	/* don't allow 0 as a tag */ | 
 | 	//fmtinstall('F', fcallfmt); | 
 | /*	fmtinstall('D', dirfmt); */ | 
 | /*	fmtinstall('M', dirmodefmt);  */ | 
 | } | 
 |  | 
 | /* | 
 |  * Version is not multiplexed: message sent only once per connection. | 
 |  */ | 
 | long mntversion(struct chan *c, char *version, int msize, int returnlen) | 
 | { | 
 | 	ERRSTACK(2); | 
 | 	struct fcall f; | 
 | 	uint8_t *msg; | 
 | 	struct mnt *m; | 
 | 	char *v; | 
 | 	long k, l; | 
 | 	uint64_t oo; | 
 | 	char buf[128]; | 
 |  | 
 | 	/* make sure no one else does this until we've established ourselves */ | 
 | 	qlock(&c->umqlock); | 
 | 	if (waserror()) { | 
 | 		qunlock(&c->umqlock); | 
 | 		nexterror(); | 
 | 	} | 
 |  | 
 | 	/* defaults */ | 
 | 	if (msize == 0) | 
 | 		msize = MAXRPC; | 
 | 	if (msize > c->iounit && c->iounit != 0) | 
 | 		msize = c->iounit; | 
 | 	v = version; | 
 | 	if (v == NULL || v[0] == '\0') | 
 | 		v = VERSION9P; | 
 |  | 
 | 	/* validity */ | 
 | 	if (msize < 0) | 
 | 		error(EFAIL, "bad iounit in version call"); | 
 | 	if (strncmp(v, VERSION9P, strlen(VERSION9P)) != 0) | 
 | 		error(EFAIL, "bad 9P version specification"); | 
 |  | 
 | 	m = c->mux; | 
 |  | 
 | 	if (m != NULL) { | 
 | 		qunlock(&c->umqlock); | 
 | 		poperror(); | 
 |  | 
 | 		strlcpy(buf, m->version, sizeof(buf)); | 
 | 		k = strlen(buf); | 
 | 		if (strncmp(buf, v, k) != 0) { | 
 | 			snprintf(buf, sizeof buf, | 
 | 				 "incompatible 9P versions %s %s", m->version, | 
 | 				 v); | 
 | 			error(EFAIL, buf); | 
 | 		} | 
 | 		if (returnlen > 0) { | 
 | 			if (returnlen < k) | 
 | 				error(ENAMETOOLONG, ERROR_FIXME); | 
 | 			memmove(version, buf, k); | 
 | 		} | 
 | 		return k; | 
 | 	} | 
 |  | 
 | 	f.type = Tversion; | 
 | 	f.tag = NOTAG; | 
 | 	f.msize = msize; | 
 | 	f.version = v; | 
 | 	msg = kzmalloc(8192 + IOHDRSZ, 0); | 
 | 	if (msg == NULL) | 
 | 		exhausted("version memory"); | 
 | 	if (waserror()) { | 
 | 		kfree(msg); | 
 | 		nexterror(); | 
 | 	} | 
 | 	k = convS2M(&f, msg, 8192 + IOHDRSZ); | 
 | 	if (k == 0) | 
 | 		error(EFAIL, "bad fversion conversion on send"); | 
 |  | 
 | 	spin_lock(&c->lock); | 
 | 	oo = c->offset; | 
 | 	c->offset += k; | 
 | 	spin_unlock(&c->lock); | 
 |  | 
 | 	l = devtab[c->type].write(c, msg, k, oo); | 
 |  | 
 | 	if (l < k) { | 
 | 		spin_lock(&c->lock); | 
 | 		c->offset -= k - l; | 
 | 		spin_unlock(&c->lock); | 
 | 		error(EFAIL, "short write in fversion"); | 
 | 	} | 
 |  | 
 | 	/* message sent; receive and decode reply */ | 
 | 	k = devtab[c->type].read(c, msg, 8192 + IOHDRSZ, c->offset); | 
 | 	if (k <= 0) | 
 | 		error(EFAIL, "EOF receiving fversion reply"); | 
 |  | 
 | 	spin_lock(&c->lock); | 
 | 	c->offset += k; | 
 | 	spin_unlock(&c->lock); | 
 |  | 
 | 	l = convM2S(msg, k, &f); | 
 | 	if (l != k) | 
 | 		error(EFAIL, "bad fversion conversion on reply"); | 
 | 	if (f.type != Rversion) { | 
 | 		if (f.type == Rerror) | 
 | 			error(EFAIL, f.ename); | 
 | 		error(EFAIL, "unexpected reply type in fversion"); | 
 | 	} | 
 | 	if (f.msize > msize) | 
 | 		error(EFAIL, "server tries to increase msize in fversion"); | 
 | 	if (f.msize < 256 || f.msize > 1024 * 1024) | 
 | 		error(EFAIL, "nonsense value of msize in fversion"); | 
 | 	if (strncmp(f.version, v, strlen(f.version)) != 0) | 
 | 		error(EFAIL, "bad 9P version returned from server"); | 
 |  | 
 | 	/* now build Mnt associated with this connection */ | 
 | 	spin_lock(&mntalloc.l); | 
 | 	m = mntalloc.mntfree; | 
 | 	if (m != 0) | 
 | 		mntalloc.mntfree = m->list; | 
 | 	else { | 
 | 		m = kzmalloc(sizeof(struct mnt), 0); | 
 | 		if (m == 0) { | 
 | 			spin_unlock(&mntalloc.l); | 
 | 			exhausted("mount devices"); | 
 | 		} | 
 | 	} | 
 | 	m->list = mntalloc.list; | 
 | 	mntalloc.list = m; | 
 | 	m->version = NULL; | 
 | 	kstrdup(&m->version, f.version); | 
 | 	m->id = mntalloc.id++; | 
 | 	m->q = qopen(10 * MAXRPC, 0, NULL, NULL); | 
 | 	m->msize = f.msize; | 
 | 	spin_unlock(&mntalloc.l); | 
 |  | 
 | 	poperror();	/* msg */ | 
 | 	kfree(msg); | 
 |  | 
 | 	spin_lock(&m->lock); | 
 | 	m->queue = 0; | 
 | 	m->rip = 0; | 
 |  | 
 | 	c->flag |= CMSG; | 
 | 	c->mux = m; | 
 | 	m->c = c; | 
 | 	spin_unlock(&m->lock); | 
 |  | 
 | 	poperror();	/* c */ | 
 | 	qunlock(&c->umqlock); | 
 | 	k = strlen(f.version); | 
 | 	if (returnlen > 0) { | 
 | 		if (returnlen < k) | 
 | 			error(ENAMETOOLONG, ERROR_FIXME); | 
 | 		memmove(version, f.version, k); | 
 | 	} | 
 |  | 
 | 	return k; | 
 | } | 
 |  | 
 | struct chan *mntauth(struct chan *c, char *spec) | 
 | { | 
 | 	ERRSTACK(2); | 
 | 	struct mnt *m; | 
 | 	struct mntrpc *r; | 
 |  | 
 | 	m = c->mux; | 
 |  | 
 | 	if (m == NULL) { | 
 | 		mntversion(c, VERSION9P, MAXRPC, 0); | 
 | 		m = c->mux; | 
 | 		if (m == NULL) | 
 | 			error(EINVAL, ERROR_FIXME); | 
 | 	} | 
 |  | 
 | 	c = mntchan(); | 
 | 	if (waserror()) { | 
 | 		/* Close must not be called since it will | 
 | 		 * call mnt recursively | 
 | 		 */ | 
 | 		chanfree(c); | 
 | 		nexterror(); | 
 | 	} | 
 |  | 
 | 	r = mntralloc(0, m->msize); | 
 |  | 
 | 	if (waserror()) { | 
 | 		mntfree(r); | 
 | 		nexterror(); | 
 | 	} | 
 |  | 
 | 	r->request.type = Tauth; | 
 | 	r->request.afid = c->fid; | 
 | 	/* This assumes we're called from a syscall, which should always be | 
 | 	 * true. */ | 
 | 	if (!current_kthread->sysc) | 
 | 		warn("Kthread %s didn't have a syscall, current is %s", | 
 | 		     current_kthread->name, current ? current->progname : NULL); | 
 | 	r->request.uname = current->user.name; | 
 | 	r->request.aname = spec; | 
 | 	mountrpc(m, r); | 
 |  | 
 | 	c->qid = r->reply.aqid; | 
 | 	c->mchan = m->c; | 
 | 	chan_incref(m->c); | 
 | 	c->mqid = c->qid; | 
 | 	c->mode = O_RDWR; | 
 |  | 
 | 	poperror();	/* r */ | 
 | 	mntfree(r); | 
 |  | 
 | 	poperror();	/* c */ | 
 |  | 
 | 	return c; | 
 |  | 
 | } | 
 |  | 
 | static struct chan *mntattach(char *muxattach) | 
 | { | 
 | 	ERRSTACK(2); | 
 | 	struct mnt *m; | 
 | 	struct chan *c; | 
 | 	struct mntrpc *r; | 
 | 	struct mntparam *params = (struct mntparam *)muxattach; | 
 |  | 
 | 	c = params->chan; | 
 |  | 
 | 	m = c->mux; | 
 |  | 
 | 	if (m == NULL) { | 
 | 		mntversion(c, NULL, 0, 0); | 
 | 		m = c->mux; | 
 | 		if (m == NULL) | 
 | 			error(EINVAL, ERROR_FIXME); | 
 | 	} | 
 |  | 
 | 	c = mntchan(); | 
 | 	if (waserror()) { | 
 | 		/* Close must not be called since it will | 
 | 		 * call mnt recursively | 
 | 		 */ | 
 | 		chanfree(c); | 
 | 		nexterror(); | 
 | 	} | 
 |  | 
 | 	r = mntralloc(0, m->msize); | 
 |  | 
 | 	if (waserror()) { | 
 | 		mntfree(r); | 
 | 		nexterror(); | 
 | 	} | 
 |  | 
 | 	r->request.type = Tattach; | 
 | 	r->request.fid = c->fid; | 
 | 	if (params->authchan == NULL) | 
 | 		r->request.afid = NOFID; | 
 | 	else | 
 | 		r->request.afid = params->authchan->fid; | 
 | 	/* This assumes we're called from a syscall, which should always be | 
 | 	 * true. */ | 
 | 	if (!current_kthread->sysc) | 
 | 		warn("Kthread %s didn't have a syscall, current is %s", | 
 | 		     current_kthread->name, current ? current->progname : NULL); | 
 | 	r->request.uname = current->user.name; | 
 | 	r->request.aname = params->spec; | 
 | 	mountrpc(m, r); | 
 |  | 
 | 	c->qid = r->reply.qid; | 
 | 	c->mchan = m->c; | 
 | 	chan_incref(m->c); | 
 | 	c->mqid = c->qid; | 
 |  | 
 | 	poperror();	/* r */ | 
 | 	mntfree(r); | 
 |  | 
 | 	poperror();	/* c */ | 
 | 	return c; | 
 | } | 
 |  | 
 | struct chan *mntchan(void) | 
 | { | 
 | 	struct chan *c; | 
 |  | 
 | 	c = devattach(devname(), 0); | 
 | 	spin_lock(&mntalloc.l); | 
 | 	c->dev = mntalloc.id++; | 
 | 	spin_unlock(&mntalloc.l); | 
 |  | 
 | 	if (c->mchan) | 
 | 		panic("mntchan non-zero %p", c->mchan); | 
 | 	return c; | 
 | } | 
 |  | 
 | static struct walkqid *mntwalk(struct chan *c, struct chan *nc, char **name, | 
 | 							   unsigned int nname) | 
 | { | 
 | 	ERRSTACK(2); | 
 | 	volatile int alloc; | 
 | 	int i; | 
 | 	struct mnt *m; | 
 | 	struct mntrpc *r; | 
 | 	struct walkqid *wq; | 
 |  | 
 | 	if (nc != NULL) | 
 | 		printd("mntwalk: nc != NULL\n"); | 
 | 	if (nname > MAXWELEM) | 
 | 		error(EFAIL, "devmnt: too many name elements"); | 
 | 	alloc = 0; | 
 | 	wq = kzmalloc(sizeof(struct walkqid) + nname * sizeof(struct qid), | 
 | 				  MEM_WAIT); | 
 | 	if (waserror()) { | 
 | 		if (alloc && wq->clone != NULL) | 
 | 			cclose(wq->clone); | 
 | 		kfree(wq); | 
 | 		poperror(); | 
 | 		return NULL; | 
 | 	} | 
 |  | 
 | 	alloc = 0; | 
 | 	m = mntchk(c); | 
 | 	r = mntralloc(c, m->msize); | 
 | 	if (nc == NULL) { | 
 | 		nc = devclone(c); | 
 | 		/* Until the other side accepts this fid, we can't mntclose it. | 
 | 		 * Therefore set type to -1 for now.  inferno was setting this | 
 | 		 * to 0, assuming it was devroot.  lining up with chanrelease | 
 | 		 * and newchan */ | 
 | 		nc->type = -1; | 
 | 		alloc = 1; | 
 | 	} | 
 | 	wq->clone = nc; | 
 |  | 
 | 	if (waserror()) { | 
 | 		mntfree(r); | 
 | 		nexterror(); | 
 | 	} | 
 | 	r->request.type = Twalk; | 
 | 	r->request.fid = c->fid; | 
 | 	r->request.newfid = nc->fid; | 
 | 	r->request.nwname = nname; | 
 | 	memmove(r->request.wname, name, nname * sizeof(char *)); | 
 |  | 
 | 	mountrpc(m, r); | 
 |  | 
 | 	if (r->reply.nwqid > nname) | 
 | 		error(EFAIL, "too many QIDs returned by walk"); | 
 | 	if (r->reply.nwqid < nname) { | 
 | 		if (alloc) | 
 | 			cclose(nc); | 
 | 		wq->clone = NULL; | 
 | 		if (r->reply.nwqid == 0) { | 
 | 			kfree(wq); | 
 | 			wq = NULL; | 
 | 			goto Return; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	/* move new fid onto mnt device and update its qid */ | 
 | 	if (wq->clone != NULL) { | 
 | 		if (wq->clone != c) { | 
 | 			wq->clone->type = c->type; | 
 | 			wq->clone->mchan = c->mchan; | 
 | 			chan_incref(c->mchan); | 
 | 		} | 
 | 		if (r->reply.nwqid > 0) | 
 | 			wq->clone->qid = r->reply.wqid[r->reply.nwqid - 1]; | 
 | 	} | 
 | 	wq->nqid = r->reply.nwqid; | 
 | 	for (i = 0; i < wq->nqid; i++) | 
 | 		wq->qid[i] = r->reply.wqid[i]; | 
 |  | 
 | Return: | 
 | 	poperror(); | 
 | 	mntfree(r); | 
 | 	poperror(); | 
 | 	return wq; | 
 | } | 
 |  | 
 | static size_t mntstat(struct chan *c, uint8_t *dp, size_t n) | 
 | { | 
 | 	ERRSTACK(1); | 
 | 	struct mnt *m; | 
 | 	struct mntrpc *r; | 
 |  | 
 | 	if (n < BIT16SZ) | 
 | 		error(EINVAL, ERROR_FIXME); | 
 | 	m = mntchk(c); | 
 | 	r = mntralloc(c, m->msize); | 
 | 	if (waserror()) { | 
 | 		mntfree(r); | 
 | 		nexterror(); | 
 | 	} | 
 | 	r->request.type = Tstat; | 
 | 	r->request.fid = c->fid; | 
 | 	mountrpc(m, r); | 
 |  | 
 | 	if (r->reply.nstat > n) { | 
 | 		/* doesn't fit; just patch the count and return */ | 
 | 		PBIT16((uint8_t *) dp, r->reply.nstat); | 
 | 		n = BIT16SZ; | 
 | 	} else { | 
 | 		n = r->reply.nstat; | 
 | 		memmove(dp, r->reply.stat, n); | 
 | 		validstat(dp, n, 0); | 
 | 		mntdirfix(dp, c); | 
 | 	} | 
 | 	poperror(); | 
 | 	mntfree(r); | 
 | 	return n; | 
 | } | 
 |  | 
 | static struct chan *mntopencreate(int type, struct chan *c, char *name, | 
 | 				  int omode, uint32_t perm) | 
 | { | 
 | 	ERRSTACK(1); | 
 | 	struct mnt *m; | 
 | 	struct mntrpc *r; | 
 |  | 
 | 	m = mntchk(c); | 
 | 	r = mntralloc(c, m->msize); | 
 | 	if (waserror()) { | 
 | 		mntfree(r); | 
 | 		nexterror(); | 
 | 	} | 
 | 	r->request.type = type; | 
 | 	r->request.fid = c->fid; | 
 | 	r->request.mode = omode_to_9p_accmode(omode); | 
 | 	if (omode & O_TRUNC) | 
 | 		r->request.mode |= MNT_9P_OPEN_OTRUNC; | 
 | 	if (omode & O_REMCLO) | 
 | 		r->request.mode |= MNT_9P_OPEN_ORCLOSE; | 
 | 	if (type == Tcreate) { | 
 | 		r->request.perm = perm; | 
 | 		r->request.name = name; | 
 | 	} | 
 | 	mountrpc(m, r); | 
 |  | 
 | 	c->qid = r->reply.qid; | 
 | 	c->offset = 0; | 
 | 	c->mode = openmode(omode); | 
 | 	c->iounit = r->reply.iounit; | 
 | 	if (c->iounit == 0 || c->iounit > m->msize - IOHDRSZ) | 
 | 		c->iounit = m->msize - IOHDRSZ; | 
 | 	c->flag |= COPEN; | 
 | 	poperror(); | 
 | 	mntfree(r); | 
 |  | 
 | 	return c; | 
 | } | 
 |  | 
 | static struct chan *mntopen(struct chan *c, int omode) | 
 | { | 
 | 	return mntopencreate(Topen, c, NULL, omode, 0); | 
 | } | 
 |  | 
 | static void mntcreate(struct chan *c, char *name, int omode, uint32_t perm, | 
 |                       char *ext) | 
 | { | 
 | 	/* TODO: support extensions for e.g. symlinks */ | 
 | 	if (perm & DMSYMLINK) | 
 | 		error(EINVAL, "#%s doesn't support symlinks", devname()); | 
 | 	mntopencreate(Tcreate, c, name, omode, perm); | 
 | } | 
 |  | 
 | static void mntclunk(struct chan *c, int t) | 
 | { | 
 | 	ERRSTACK(1); | 
 | 	struct mnt *m; | 
 | 	struct mntrpc *r; | 
 |  | 
 | 	m = mntchk(c); | 
 | 	r = mntralloc(c, m->msize); | 
 | 	if (waserror()) { | 
 | 		mntfree(r); | 
 | 		nexterror(); | 
 | 	} | 
 |  | 
 | 	r->request.type = t; | 
 | 	r->request.fid = c->fid; | 
 | 	mountrpc(m, r); | 
 | 	mntfree(r); | 
 | 	poperror(); | 
 | } | 
 |  | 
 | void muxclose(struct mnt *m) | 
 | { | 
 | 	struct mntrpc *q, *r; | 
 |  | 
 | 	for (q = m->queue; q; q = r) { | 
 | 		r = q->list; | 
 | 		mntfree(q); | 
 | 	} | 
 | 	m->id = 0; | 
 | 	kfree(m->version); | 
 | 	m->version = NULL; | 
 | 	mntpntfree(m); | 
 | } | 
 |  | 
 | void mntpntfree(struct mnt *m) | 
 | { | 
 | 	struct mnt *f, **l; | 
 | 	struct queue *q; | 
 |  | 
 | 	spin_lock(&mntalloc.l); | 
 | 	l = &mntalloc.list; | 
 | 	for (f = *l; f; f = f->list) { | 
 | 		if (f == m) { | 
 | 			*l = m->list; | 
 | 			break; | 
 | 		} | 
 | 		l = &f->list; | 
 | 	} | 
 | 	m->list = mntalloc.mntfree; | 
 | 	mntalloc.mntfree = m; | 
 | 	q = m->q; | 
 | 	spin_unlock(&mntalloc.l); | 
 |  | 
 | 	qfree(q); | 
 | } | 
 |  | 
 | static void mntclose(struct chan *c) | 
 | { | 
 | 	mntclunk(c, Tclunk); | 
 | } | 
 |  | 
 | static void mntremove(struct chan *c) | 
 | { | 
 | 	mntclunk(c, Tremove); | 
 | } | 
 |  | 
 | static size_t mntwstat(struct chan *c, uint8_t *dp, size_t n) | 
 | { | 
 | 	ERRSTACK(1); | 
 | 	struct mnt *m; | 
 | 	struct mntrpc *r; | 
 |  | 
 | 	m = mntchk(c); | 
 | 	r = mntralloc(c, m->msize); | 
 | 	if (waserror()) { | 
 | 		mntfree(r); | 
 | 		nexterror(); | 
 | 	} | 
 | 	r->request.type = Twstat; | 
 | 	r->request.fid = c->fid; | 
 | 	r->request.nstat = n; | 
 | 	r->request.stat = dp; | 
 | 	mountrpc(m, r); | 
 | 	poperror(); | 
 | 	mntfree(r); | 
 | 	return n; | 
 | } | 
 |  | 
 | /* the servers should either return units of whole directory entries | 
 |  * OR support seeking to an arbitrary place. One or other. | 
 |  * Both are fine, but at least one is a minimum. | 
 |  * If the return a partial result, but more than one result, | 
 |  * we'll return a shorter read and the next offset will be aligned | 
 |  */ | 
 | static size_t mntread(struct chan *c, void *buf, size_t n, off64_t off) | 
 | { | 
 | 	uint8_t *p, *e; | 
 | 	int nc, dirlen; | 
 | 	int numdirent = 0; | 
 |  | 
 |  | 
 | 	p = buf; | 
 |  | 
 | 	n = mntrdwr(Tread, c, buf, n, off); | 
 |  | 
 | 	if (c->qid.type & QTDIR) { | 
 | 		for (e = &p[n]; p + BIT16SZ < e; p += dirlen) { | 
 | 			dirlen = BIT16SZ + GBIT16(p); | 
 | 			if (p + dirlen > e){ | 
 | 				break; | 
 | 			} | 
 | 			validstat(p, dirlen, 0); | 
 | 			mntdirfix(p, c); | 
 | 			numdirent += dirlen; | 
 | 		} | 
 | 		if (p != e) { | 
 | 			//error(Esbadstat); | 
 | 			/* not really. Maybe the server supports | 
 | 			 * arbitrary seek like go9p now does. | 
 | 			 */ | 
 | 			n = numdirent; | 
 | 		} | 
 | 	} | 
 | 	return n; | 
 | } | 
 |  | 
 | static size_t mntwrite(struct chan *c, void *buf, size_t n, off64_t off) | 
 | { | 
 | 	return mntrdwr(Twrite, c, buf, n, off); | 
 | } | 
 |  | 
 | size_t mntrdwr(int type, struct chan *c, void *buf, size_t n, off64_t off) | 
 | { | 
 | 	ERRSTACK(1); | 
 | 	struct mnt *m; | 
 | 	struct mntrpc *r;	/* TO DO: volatile struct { Mntrpc *r; } r; */ | 
 | 	char *uba; | 
 | 	uint32_t cnt, nr, nreq; | 
 |  | 
 | 	m = mntchk(c); | 
 | 	uba = buf; | 
 | 	cnt = 0; | 
 | 	for (;;) { | 
 | 		r = mntralloc(c, m->msize); | 
 | 		if (waserror()) { | 
 | 			mntfree(r); | 
 | 			nexterror(); | 
 | 		} | 
 | 		r->request.type = type; | 
 | 		r->request.fid = c->fid; | 
 | 		r->request.offset = off; | 
 | 		r->request.data = uba; | 
 | 		nr = n; | 
 | 		if (nr > m->msize - IOHDRSZ) | 
 | 			nr = m->msize - IOHDRSZ; | 
 | 		r->request.count = nr; | 
 | 		mountrpc(m, r); | 
 | 		nreq = r->request.count; | 
 | 		nr = r->reply.count; | 
 | 		if (nr > nreq) | 
 | 			nr = nreq; | 
 |  | 
 | 		if (type == Tread) | 
 | 			r->b = bl2mem((uint8_t *) uba, r->b, nr); | 
 |  | 
 | 		poperror(); | 
 | 		mntfree(r); | 
 | 		off += nr; | 
 | 		uba += nr; | 
 | 		cnt += nr; | 
 | 		n -= nr; | 
 | 		if (nr != nreq || n == 0 /*|| current->killed */ ) | 
 | 			break; | 
 | 	} | 
 | 	return cnt; | 
 | } | 
 |  | 
 | void mountrpc(struct mnt *m, struct mntrpc *r) | 
 | { | 
 | 	char *sn, *cn; | 
 | 	int t; | 
 | 	char *e; | 
 |  | 
 | 	r->reply.tag = 0; | 
 | 	r->reply.type = Tmax;	/* can't ever be a valid message type */ | 
 |  | 
 | 	mountio(m, r); | 
 |  | 
 | 	t = r->reply.type; | 
 | 	switch (t) { | 
 | 	case Rerror: | 
 | 		/* in Akaros mode, first four characters | 
 | 		 * are errno. | 
 | 		 */ | 
 | 		e = r->reply.ename; | 
 | 		/* If it is in the format "XXXX <at least one char>" */ | 
 | 		if ((strlen(e) > 5) && isxdigit(e[0]) && | 
 | 			isxdigit(e[1]) && | 
 | 			isxdigit(e[2]) && | 
 | 			isxdigit(e[3])) { | 
 |  | 
 | 			int errno = strtoul(e, NULL, 16); | 
 |  | 
 | 			error(errno, &r->reply.ename[5]); | 
 | 		} else | 
 | 			error(EFAIL, r->reply.ename); | 
 | 	case Rflush: | 
 | 		error(EINTR, ERROR_FIXME); | 
 | 	default: | 
 | 		if (t == r->request.type + 1) | 
 | 			break; | 
 | 		sn = "?"; | 
 | 		if (m->c->name != NULL) | 
 | 			sn = m->c->name->s; | 
 | 		cn = "?"; | 
 | 		if (r->c != NULL && r->c->name != NULL) | 
 | 			cn = r->c->name->s; | 
 | 		warn("mnt: mismatch from %s %s rep %p tag %d fid %d T%d R%d rp %d\n", | 
 | 		     sn, cn, r, r->request.tag, r->request.fid, r->request.type, | 
 | 		     r->reply.type, r->reply.tag); | 
 | 		error(EPROTO, ERROR_FIXME); | 
 | 	} | 
 | } | 
 |  | 
 | static bool kth_proc_is_dying(struct kthread *kth) | 
 | { | 
 | 	return kth->proc ? proc_is_dying(kth->proc) : false; | 
 | } | 
 |  | 
 | void mountio(struct mnt *m, struct mntrpc *r) | 
 | { | 
 | 	ERRSTACK(1); | 
 | 	int n; | 
 |  | 
 | 	while (waserror()) { | 
 | 		if (m->rip == current_kthread) | 
 | 			mntgate(m); | 
 | 		/* Syscall aborts are like Plan 9 Eintr.  For those, we need to | 
 | 		 * change the old request to a flush (mntflushalloc) and try | 
 | 		 * again.  We'll always try to flush, and you can't get out | 
 | 		 * until the flush either succeeds or errors out with a | 
 | 		 * non-abort/Eintr error. | 
 | 		 * | 
 | 		 * This all means that regular aborts cannot break us out of | 
 | 		 * here!  We can consider that policy in the future, if we need | 
 | 		 * to.  Regardless, if the process is dying, we really do need | 
 | 		 * to abort.  We might not always have a process (RKM | 
 | 		 * chan_release), but in that case we're fine | 
 | 		 * - we're not preventing a process from dying. */ | 
 | 		if ((get_errno() != EINTR) || | 
 | 		    kth_proc_is_dying(current_kthread)) { | 
 | 			/* all other errors or dying, bail out! */ | 
 | 			mntflushfree(m, r); | 
 | 			nexterror(); | 
 | 		} | 
 | 		/* try again.  this is where you can get the "rpc tags" errstr. | 
 | 		 */ | 
 | 		r = mntflushalloc(r, m->msize); | 
 | 		/* need one for every waserror call; so this plus one outside */ | 
 | 		poperror(); | 
 | 	} | 
 |  | 
 | 	spin_lock(&m->lock); | 
 | 	r->m = m; | 
 | 	r->list = m->queue; | 
 | 	m->queue = r; | 
 | 	spin_unlock(&m->lock); | 
 |  | 
 | 	/* Transmit a file system rpc */ | 
 | 	if (m->msize == 0) | 
 | 		panic("msize"); | 
 | 	n = convS2M(&r->request, r->rpc, m->msize); | 
 | 	if (n < 0) | 
 | 		panic("bad message type in mountio"); | 
 | 	if (devtab[m->c->type].write(m->c, r->rpc, n, 0) != n) | 
 | 		error(EIO, ERROR_FIXME); | 
 | /*	r->stime = fastticks(NULL); */ | 
 | 	r->reqlen = n; | 
 |  | 
 | 	/* Gate readers onto the mount point one at a time */ | 
 | 	for (;;) { | 
 | 		spin_lock(&m->lock); | 
 | 		if (m->rip == 0) | 
 | 			break; | 
 | 		spin_unlock(&m->lock); | 
 | 		rendez_sleep(&r->r, rpcattn, r); | 
 | 		if (r->done) { | 
 | 			poperror(); | 
 | 			mntflushfree(m, r); | 
 | 			return; | 
 | 		} | 
 | 	} | 
 | 	m->rip = current_kthread; | 
 | 	spin_unlock(&m->lock); | 
 | 	while (r->done == 0) { | 
 | 		if (mntrpcread(m, r) < 0) | 
 | 			error(EIO, ERROR_FIXME); | 
 | 		mountmux(m, r); | 
 | 	} | 
 | 	mntgate(m); | 
 | 	poperror(); | 
 | 	mntflushfree(m, r); | 
 | } | 
 |  | 
 | static int doread(struct mnt *m, int len) | 
 | { | 
 | 	struct block *b; | 
 |  | 
 | 	while (qlen(m->q) < len) { | 
 | 		b = devtab[m->c->type].bread(m->c, m->msize, 0); | 
 | 		if (b == NULL) | 
 | 			return -1; | 
 | 		if (blocklen(b) == 0) { | 
 | 			freeblist(b); | 
 | 			return -1; | 
 | 		} | 
 | 		qaddlist(m->q, b); | 
 | 	} | 
 | 	return 0; | 
 | } | 
 |  | 
 | int mntrpcread(struct mnt *m, struct mntrpc *r) | 
 | { | 
 | 	int i, t, len, hlen; | 
 | 	struct block *b, **l, *nb; | 
 |  | 
 | 	r->reply.type = 0; | 
 | 	r->reply.tag = 0; | 
 |  | 
 | 	/* read at least length, type, and tag and pullup to a single block */ | 
 | 	if (doread(m, BIT32SZ + BIT8SZ + BIT16SZ) < 0) | 
 | 		return -1; | 
 | 	nb = pullupqueue(m->q, BIT32SZ + BIT8SZ + BIT16SZ); | 
 |  | 
 | 	/* read in the rest of the message, avoid ridiculous (for now) message | 
 | 	 * sizes */ | 
 | 	len = GBIT32(nb->rp); | 
 | 	if (len > m->msize) { | 
 | 		qdiscard(m->q, qlen(m->q)); | 
 | 		return -1; | 
 | 	} | 
 | 	if (doread(m, len) < 0) | 
 | 		return -1; | 
 |  | 
 | 	/* pullup the header (i.e. everything except data) */ | 
 | 	t = nb->rp[BIT32SZ]; | 
 | 	switch (t) { | 
 | 		case Rread: | 
 | 			hlen = BIT32SZ + BIT8SZ + BIT16SZ + BIT32SZ; | 
 | 			break; | 
 | 		default: | 
 | 			hlen = len; | 
 | 			break; | 
 | 	} | 
 | 	nb = pullupqueue(m->q, hlen); | 
 |  | 
 | 	if (convM2S(nb->rp, len, &r->reply) <= 0) { | 
 | 		/* bad message, dump it */ | 
 | 		printd("mntrpcread: convM2S failed\n"); | 
 | 		qdiscard(m->q, len); | 
 | 		return -1; | 
 | 	} | 
 |  | 
 | 	/* TODO: this should use a qio helper directly.  qputback should have | 
 | 	 * the qlocked, but I guess we assume we're the only one using it. */ | 
 |  | 
 | 	/* hang the data off of the fcall struct */ | 
 | 	l = &r->b; | 
 | 	*l = NULL; | 
 | 	do { | 
 | 		b = qget(m->q); | 
 | 		/* TODO: have better block helpers for this and the memmove | 
 | 		 * below */ | 
 | 		b = linearizeblock(b); | 
 | 		if (hlen > 0) { | 
 | 			b->rp += hlen; | 
 | 			len -= hlen; | 
 | 			hlen = 0; | 
 | 		} | 
 | 		i = BLEN(b); | 
 | 		if (i <= len) { | 
 | 			len -= i; | 
 | 			*l = b; | 
 | 			l = &(b->next); | 
 | 		} else { | 
 | 			/* split block and put unused bit back */ | 
 | 			nb = block_alloc(i - len, MEM_WAIT); | 
 | 			memmove(nb->wp, b->rp + len, i - len); | 
 | 			b->wp = b->rp + len; | 
 | 			nb->wp += i - len; | 
 | 			qputback(m->q, nb); | 
 | 			*l = b; | 
 | 			return 0; | 
 | 		} | 
 | 	} while (len > 0); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | void mntgate(struct mnt *m) | 
 | { | 
 | 	struct mntrpc *q; | 
 |  | 
 | 	spin_lock(&m->lock); | 
 | 	m->rip = 0; | 
 | 	for (q = m->queue; q; q = q->list) { | 
 | 		if (q->done == 0) | 
 | 			if (rendez_wakeup(&q->r)) | 
 | 				break; | 
 | 	} | 
 | 	spin_unlock(&m->lock); | 
 | } | 
 |  | 
 | void mountmux(struct mnt *m, struct mntrpc *r) | 
 | { | 
 | 	struct mntrpc **l, *q; | 
 |  | 
 | 	spin_lock(&m->lock); | 
 | 	l = &m->queue; | 
 | 	for (q = *l; q; q = q->list) { | 
 | 		/* look for a reply to a message */ | 
 | 		if (q->request.tag == r->reply.tag) { | 
 | 			*l = q->list; | 
 | 			if (q != r) { | 
 | 				/* | 
 | 				 * Completed someone else. | 
 | 				 * Trade pointers to receive buffer. | 
 | 				 */ | 
 | 				q->reply = r->reply; | 
 | 				q->b = r->b; | 
 | 				r->b = NULL; | 
 | 			} | 
 | 			q->done = 1; | 
 | 			spin_unlock(&m->lock); | 
 | 			if (mntstats != NULL) | 
 | 				(*mntstats) (q->request.type, m->c, q->stime, | 
 | 					     q->reqlen + r->replen); | 
 | 			if (q != r) | 
 | 				rendez_wakeup(&q->r); | 
 | 			return; | 
 | 		} | 
 | 		l = &q->list; | 
 | 	} | 
 | 	spin_unlock(&m->lock); | 
 | 	if (r->reply.type == Rerror) { | 
 | 		printd("unexpected reply tag %u; type %d (error %q)\n", | 
 | 		       r->reply.tag, r->reply.type, r->reply.ename); | 
 | 	} else { | 
 | 		printd("unexpected reply tag %u; type %d\n", r->reply.tag, | 
 | 		       r->reply.type); | 
 | 	} | 
 | } | 
 |  | 
 | /* | 
 |  * Create a new flush request and chain the previous | 
 |  * requests from it | 
 |  */ | 
 | struct mntrpc *mntflushalloc(struct mntrpc *r, uint32_t iounit) | 
 | { | 
 | 	struct mntrpc *fr; | 
 |  | 
 | 	fr = mntralloc(0, iounit); | 
 |  | 
 | 	fr->request.type = Tflush; | 
 | 	if (r->request.type == Tflush) | 
 | 		fr->request.oldtag = r->request.oldtag; | 
 | 	else | 
 | 		fr->request.oldtag = r->request.tag; | 
 | 	fr->flushed = r; | 
 |  | 
 | 	return fr; | 
 | } | 
 |  | 
 | /* | 
 |  *  Free a chain of flushes.  Remove each unanswered | 
 |  *  flush and the original message from the unanswered | 
 |  *  request queue.  Mark the original message as done | 
 |  *  and if it hasn't been answered set the reply to to | 
 |  *  Rflush. | 
 |  */ | 
 | void mntflushfree(struct mnt *m, struct mntrpc *r) | 
 | { | 
 | 	struct mntrpc *fr; | 
 |  | 
 | 	while (r) { | 
 | 		fr = r->flushed; | 
 | 		if (!r->done) { | 
 | 			r->reply.type = Rflush; | 
 | 			mntqrm(m, r); | 
 | 		} | 
 | 		if (fr) | 
 | 			mntfree(r); | 
 | 		r = fr; | 
 | 	} | 
 | } | 
 |  | 
 | static int alloctag(void) | 
 | { | 
 | 	return get_u16(mntalloc.tags); | 
 | } | 
 |  | 
 | static void freetag(int t) | 
 | { | 
 | 	put_u16(mntalloc.tags, t); | 
 | } | 
 |  | 
 | struct mntrpc *mntralloc(struct chan *c, uint32_t msize) | 
 | { | 
 | 	struct mntrpc *new; | 
 |  | 
 | 	spin_lock(&mntalloc.l); | 
 | 	new = mntalloc.rpcfree; | 
 | 	if (new == NULL) { | 
 | 		new = kzmalloc(sizeof(struct mntrpc), 0); | 
 | 		if (new == NULL) { | 
 | 			spin_unlock(&mntalloc.l); | 
 | 			exhausted("mount rpc header"); | 
 | 		} | 
 | 		rendez_init(&new->r); | 
 | 		/* | 
 | 		 * The header is split from the data buffer as | 
 | 		 * mountmux may swap the buffer with another header. | 
 | 		 */ | 
 | 		new->rpc = kzmalloc(msize, MEM_WAIT); | 
 | 		if (new->rpc == NULL) { | 
 | 			kfree(new); | 
 | 			spin_unlock(&mntalloc.l); | 
 | 			exhausted("mount rpc buffer"); | 
 | 		} | 
 | 		new->rpclen = msize; | 
 | 		new->request.tag = alloctag(); | 
 | 		if (new->request.tag == NOTAG) { | 
 | 			kfree(new); | 
 | 			spin_unlock(&mntalloc.l); | 
 | 			exhausted("rpc tags"); | 
 | 		} | 
 | 	} else { | 
 | 		mntalloc.rpcfree = new->list; | 
 | 		mntalloc.nrpcfree--; | 
 | 		if (new->rpclen < msize) { | 
 | 			kfree(new->rpc); | 
 | 			new->rpc = kzmalloc(msize, MEM_WAIT); | 
 | 			if (new->rpc == NULL) { | 
 | 				kfree(new); | 
 | 				mntalloc.nrpcused--; | 
 | 				spin_unlock(&mntalloc.l); | 
 | 				exhausted("mount rpc buffer"); | 
 | 			} | 
 | 			new->rpclen = msize; | 
 | 		} | 
 | 	} | 
 | 	mntalloc.nrpcused++; | 
 | 	spin_unlock(&mntalloc.l); | 
 | 	new->c = c; | 
 | 	new->done = 0; | 
 | 	new->flushed = NULL; | 
 | 	new->b = NULL; | 
 | 	return new; | 
 | } | 
 |  | 
 | void mntfree(struct mntrpc *r) | 
 | { | 
 | 	if (r->b != NULL) | 
 | 		freeblist(r->b); | 
 | 	spin_lock(&mntalloc.l); | 
 | 	if (mntalloc.nrpcfree >= 10) { | 
 | 		kfree(r->rpc); | 
 | 		freetag(r->request.tag); | 
 | 		kfree(r); | 
 | 	} else { | 
 | 		r->list = mntalloc.rpcfree; | 
 | 		mntalloc.rpcfree = r; | 
 | 		mntalloc.nrpcfree++; | 
 | 	} | 
 | 	mntalloc.nrpcused--; | 
 | 	spin_unlock(&mntalloc.l); | 
 | } | 
 |  | 
 | void mntqrm(struct mnt *m, struct mntrpc *r) | 
 | { | 
 | 	struct mntrpc **l, *f; | 
 |  | 
 | 	spin_lock(&m->lock); | 
 | 	r->done = 1; | 
 |  | 
 | 	l = &m->queue; | 
 | 	for (f = *l; f; f = f->list) { | 
 | 		if (f == r) { | 
 | 			*l = r->list; | 
 | 			break; | 
 | 		} | 
 | 		l = &f->list; | 
 | 	} | 
 | 	spin_unlock(&m->lock); | 
 | } | 
 |  | 
 | struct mnt *mntchk(struct chan *c) | 
 | { | 
 | 	struct mnt *m; | 
 |  | 
 | 	/* This routine is mostly vestiges of prior lives; now it's just sanity | 
 | 	 * checking */ | 
 |  | 
 | 	if (c->mchan == NULL) | 
 | 		panic("mntchk 1: NULL mchan c %s\n", /*c2name(c)*/ "channame?"); | 
 |  | 
 | 	m = c->mchan->mux; | 
 |  | 
 | 	if (m == NULL) | 
 | 		printd("mntchk 2: NULL mux c %s c->mchan %s \n", c2name(c), | 
 | 			   c2name(c->mchan)); | 
 |  | 
 | 	/* | 
 | 	 * Was it closed and reused (was error(Eshutdown); now, it can't happen) | 
 | 	 */ | 
 | 	if (m->id == 0 || m->id >= c->dev) | 
 | 		panic("mntchk 3: can't happen"); | 
 |  | 
 | 	return m; | 
 | } | 
 |  | 
 | /* | 
 |  * Rewrite channel type and dev for in-flight data to | 
 |  * reflect local values.  These entries are known to be | 
 |  * the first two in the Dir encoding after the count. | 
 |  */ | 
 | void mntdirfix(uint8_t * dirbuf, struct chan *c) | 
 | { | 
 | 	/* TODO: We used to use the device's char (dc), instead of the type. | 
 | 	 * not sure about the effects one way or the other.  This might be the | 
 | 	 * type[2] and dev[4] in a D (struct dir, see 9p's stat | 
 | 	 * (http://man.cat-v.org/plan_9/5/stat).  In which case, those should be | 
 | 	 * for the kernel's use.  Hopefully our kernel. */ | 
 | 	dirbuf += BIT16SZ;	/* skip count */ | 
 | 	PBIT16(dirbuf, c->type); | 
 | 	dirbuf += BIT16SZ; | 
 | 	PBIT32(dirbuf, c->dev); | 
 | } | 
 |  | 
 | int rpcattn(void *v) | 
 | { | 
 | 	struct mntrpc *r; | 
 |  | 
 | 	r = v; | 
 | 	return r->done || r->m->rip == 0; | 
 | } | 
 |  | 
 | struct dev mntdevtab __devtab = { | 
 | 	.name = "mnt", | 
 |  | 
 | 	.reset = devreset, | 
 | 	.init = mntinit, | 
 | 	.shutdown = devshutdown, | 
 | 	.attach = mntattach, | 
 | 	.walk = mntwalk, | 
 | 	.stat = mntstat, | 
 | 	.open = mntopen, | 
 | 	.create = mntcreate, | 
 | 	.close = mntclose, | 
 | 	.read = mntread, | 
 | 	.bread = devbread, | 
 | 	.write = mntwrite, | 
 | 	.bwrite = devbwrite, | 
 | 	.remove = mntremove, | 
 | 	.wstat = mntwstat, | 
 | 	.power = devpower, | 
 | 	.chaninfo = devchaninfo, | 
 | }; |