| /* 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> | 
 |  | 
 | struct dev pipedevtab; | 
 |  | 
 | static char *devname(void) | 
 | { | 
 | 	return pipedevtab.name; | 
 | } | 
 |  | 
 | typedef struct Pipe Pipe; | 
 | struct Pipe { | 
 | 	qlock_t qlock; | 
 | 	Pipe *next; | 
 | 	struct kref ref; | 
 | 	uint32_t path; | 
 | 	struct queue *q[2]; | 
 | 	int qref[2]; | 
 | 	struct dirtab *pipedir; | 
 | 	char *user; | 
 | 	struct fdtap_slist data_taps; | 
 | 	spinlock_t tap_lock; | 
 | }; | 
 |  | 
 | static struct { | 
 | 	spinlock_t lock; | 
 | 	uint32_t path; | 
 | 	int pipeqsize; | 
 | } pipealloc; | 
 |  | 
 | enum { | 
 | 	Qdir, | 
 | 	Qctl, | 
 | 	Qdata0, | 
 | 	Qdata1, | 
 | }; | 
 |  | 
 | static | 
 | struct dirtab pipedir[] = { | 
 | 	{".", {Qdir, 0, QTDIR}, 0, DMDIR | 0500}, | 
 | 	{"ctl", {Qctl}, 0, 0660}, | 
 | 	{"data", {Qdata0}, 0, 0660}, | 
 | 	{"data1", {Qdata1}, 0, 0660}, | 
 | }; | 
 |  | 
 | static void freepipe(Pipe * p) | 
 | { | 
 | 	if (p != NULL) { | 
 | 		kfree(p->user); | 
 | 		kfree(p->q[0]); | 
 | 		kfree(p->q[1]); | 
 | 		kfree(p->pipedir); | 
 | 		kfree(p); | 
 | 	} | 
 | } | 
 |  | 
 | static void pipe_release(struct kref *kref) | 
 | { | 
 | 	Pipe *pipe = container_of(kref, Pipe, ref); | 
 | 	freepipe(pipe); | 
 | } | 
 |  | 
 | static void pipeinit(void) | 
 | { | 
 | 	pipealloc.pipeqsize = 32 * 1024; | 
 | } | 
 |  | 
 | /* | 
 |  *  create a pipe, no streams are created until an open | 
 |  */ | 
 | static struct chan *pipeattach(char *spec) | 
 | { | 
 | 	ERRSTACK(2); | 
 | 	Pipe *p; | 
 | 	struct chan *c; | 
 |  | 
 | 	c = devattach(devname(), spec); | 
 | 	p = kzmalloc(sizeof(Pipe), 0); | 
 | 	if (p == 0) | 
 | 		error(ENOMEM, ERROR_FIXME); | 
 | 	if (waserror()) { | 
 | 		freepipe(p); | 
 | 		nexterror(); | 
 | 	} | 
 | 	p->pipedir = kzmalloc(sizeof(pipedir), 0); | 
 | 	if (p->pipedir == 0) | 
 | 		error(ENOMEM, ERROR_FIXME); | 
 | 	memmove(p->pipedir, pipedir, sizeof(pipedir)); | 
 | 	kstrdup(&p->user, current->user.name); | 
 | 	kref_init(&p->ref, pipe_release, 1); | 
 | 	qlock_init(&p->qlock); | 
 |  | 
 | 	p->q[0] = qopen(pipealloc.pipeqsize, Qcoalesce, 0, 0); | 
 | 	if (p->q[0] == 0) | 
 | 		error(ENOMEM, ERROR_FIXME); | 
 | 	p->q[1] = qopen(pipealloc.pipeqsize, Qcoalesce, 0, 0); | 
 | 	if (p->q[1] == 0) | 
 | 		error(ENOMEM, ERROR_FIXME); | 
 | 	poperror(); | 
 |  | 
 | 	spin_lock(&(&pipealloc)->lock); | 
 | 	p->path = ++pipealloc.path; | 
 | 	spin_unlock(&(&pipealloc)->lock); | 
 |  | 
 | 	c->qid.path = NETQID(2 * p->path, Qdir); | 
 | 	c->qid.vers = 0; | 
 | 	c->qid.type = QTDIR; | 
 | 	c->aux = p; | 
 | 	c->dev = 0; | 
 |  | 
 | 	/* taps. */ | 
 | 	SLIST_INIT(&p->data_taps);	/* already = 0; set to be futureproof */ | 
 | 	spinlock_init(&p->tap_lock); | 
 | 	return c; | 
 | } | 
 |  | 
 | static int | 
 | pipegen(struct chan *c, char *unused, | 
 | 		struct dirtab *tab, int ntab, int i, struct dir *dp) | 
 | { | 
 | 	int id, len; | 
 | 	struct qid qid; | 
 | 	Pipe *p; | 
 |  | 
 | 	if (i == DEVDOTDOT) { | 
 | 		devdir(c, c->qid, devname(), 0, eve.name, 0555, dp); | 
 | 		return 1; | 
 | 	} | 
 | 	i++;	/* skip . */ | 
 | 	if (tab == 0 || i >= ntab) | 
 | 		return -1; | 
 | 	tab += i; | 
 | 	p = c->aux; | 
 | 	switch (NETTYPE(tab->qid.path)) { | 
 | 		case Qdata0: | 
 | 			len = qlen(p->q[0]); | 
 | 			break; | 
 | 		case Qdata1: | 
 | 			len = qlen(p->q[1]); | 
 | 			break; | 
 | 		default: | 
 | 			len = tab->length; | 
 | 			break; | 
 | 	} | 
 | 	id = NETID(c->qid.path); | 
 | 	qid.path = NETQID(id, tab->qid.path); | 
 | 	qid.vers = 0; | 
 | 	qid.type = QTFILE; | 
 | 	devdir(c, qid, tab->name, len, eve.name, tab->perm, dp); | 
 | 	return 1; | 
 | } | 
 |  | 
 | static struct walkqid *pipewalk(struct chan *c, struct chan *nc, char **name, | 
 | 				unsigned int nname) | 
 | { | 
 | 	struct walkqid *wq; | 
 | 	Pipe *p; | 
 |  | 
 | 	p = c->aux; | 
 | 	wq = devwalk(c, nc, name, nname, p->pipedir, ARRAY_SIZE(pipedir), | 
 | 		     pipegen); | 
 | 	if (wq != NULL && wq->clone != NULL && wq->clone != c) { | 
 | 		qlock(&p->qlock); | 
 | 		kref_get(&p->ref, 1); | 
 | 		if (c->flag & COPEN) { | 
 | 			switch (NETTYPE(c->qid.path)) { | 
 | 				case Qdata0: | 
 | 					p->qref[0]++; | 
 | 					break; | 
 | 				case Qdata1: | 
 | 					p->qref[1]++; | 
 | 					break; | 
 | 			} | 
 | 		} | 
 | 		qunlock(&p->qlock); | 
 | 	} | 
 | 	return wq; | 
 | } | 
 |  | 
 | static size_t pipestat(struct chan *c, uint8_t *db, size_t n) | 
 | { | 
 | 	Pipe *p; | 
 | 	struct dir dir; | 
 | 	struct dirtab *tab; | 
 | 	int perm; | 
 | 	int type = NETTYPE(c->qid.path); | 
 |  | 
 | 	p = c->aux; | 
 | 	tab = p->pipedir; | 
 |  | 
 | 	switch (type) { | 
 | 	case Qdir: | 
 | 	case Qctl: | 
 | 		devdir(c, c->qid, tab[type].name, tab[type].length, eve.name, | 
 | 		       tab[type].perm, &dir); | 
 | 		break; | 
 | 	case Qdata0: | 
 | 		perm = tab[1].perm; | 
 | 		perm |= qreadable(p->q[0]) ? DMREADABLE : 0; | 
 | 		perm |= qwritable(p->q[1]) ? DMWRITABLE : 0; | 
 | 		devdir(c, c->qid, tab[1].name, qlen(p->q[0]), eve.name, perm, | 
 | 		       &dir); | 
 | 		break; | 
 | 	case Qdata1: | 
 | 		perm = tab[2].perm; | 
 | 		perm |= qreadable(p->q[1]) ? DMREADABLE : 0; | 
 | 		perm |= qwritable(p->q[0]) ? DMWRITABLE : 0; | 
 | 		devdir(c, c->qid, tab[2].name, qlen(p->q[1]), eve.name, perm, | 
 | 		       &dir); | 
 | 		break; | 
 | 	default: | 
 | 		panic("pipestat"); | 
 | 	} | 
 | 	n = convD2M(&dir, db, n); | 
 | 	if (n < BIT16SZ) | 
 | 		error(ENODATA, ERROR_FIXME); | 
 | 	return n; | 
 | } | 
 |  | 
 | /* | 
 |  *  if the stream doesn't exist, create it | 
 |  */ | 
 | static struct chan *pipeopen(struct chan *c, int omode) | 
 | { | 
 | 	ERRSTACK(2); | 
 | 	Pipe *p; | 
 |  | 
 | 	if (c->qid.type & QTDIR) { | 
 | 		if (omode & O_WRITE) | 
 | 			error(EINVAL, | 
 | 			      "Can only open directories O_READ, mode is %o oct", | 
 | 			      omode); | 
 | 		c->mode = openmode(omode); | 
 | 		c->flag |= COPEN; | 
 | 		c->offset = 0; | 
 | 		return c; | 
 | 	} | 
 |  | 
 | 	openmode(omode);	/* check it */ | 
 |  | 
 | 	p = c->aux; | 
 | 	qlock(&p->qlock); | 
 | 	if (waserror()) { | 
 | 		qunlock(&p->qlock); | 
 | 		nexterror(); | 
 | 	} | 
 | 	switch (NETTYPE(c->qid.path)) { | 
 | 	case Qdata0: | 
 | 		devpermcheck(p->user, p->pipedir[1].perm, omode); | 
 | 		p->qref[0]++; | 
 | 		break; | 
 | 	case Qdata1: | 
 | 		devpermcheck(p->user, p->pipedir[2].perm, omode); | 
 | 		p->qref[1]++; | 
 | 		break; | 
 | 	} | 
 | 	poperror(); | 
 | 	qunlock(&p->qlock); | 
 |  | 
 | 	c->mode = openmode(omode); | 
 | 	c->flag |= COPEN; | 
 | 	c->offset = 0; | 
 | 	c->iounit = qiomaxatomic; | 
 | 	return c; | 
 | } | 
 |  | 
 | static void pipeclose(struct chan *c) | 
 | { | 
 | 	Pipe *p; | 
 |  | 
 | 	p = c->aux; | 
 | 	qlock(&p->qlock); | 
 |  | 
 | 	if (c->flag & COPEN) { | 
 | 		/* | 
 | 		 *  closing either side hangs up the stream | 
 | 		 */ | 
 | 		switch (NETTYPE(c->qid.path)) { | 
 | 		case Qdata0: | 
 | 			p->qref[0]--; | 
 | 			if (p->qref[0] == 0) { | 
 | 				qhangup(p->q[1], 0); | 
 | 				qclose(p->q[0]); | 
 | 			} | 
 | 			break; | 
 | 		case Qdata1: | 
 | 			p->qref[1]--; | 
 | 			if (p->qref[1] == 0) { | 
 | 				qhangup(p->q[0], 0); | 
 | 				qclose(p->q[1]); | 
 | 			} | 
 | 			break; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	/* | 
 | 	 *  if both sides are closed, they are reusable | 
 | 	 */ | 
 | 	if (p->qref[0] == 0 && p->qref[1] == 0) { | 
 | 		qreopen(p->q[0]); | 
 | 		qreopen(p->q[1]); | 
 | 	} | 
 |  | 
 | 	qunlock(&p->qlock); | 
 | 	/* | 
 | 	 *  free the structure on last close | 
 | 	 */ | 
 | 	kref_put(&p->ref); | 
 | } | 
 |  | 
 | static size_t piperead(struct chan *c, void *va, size_t n, off64_t offset) | 
 | { | 
 | 	Pipe *p; | 
 |  | 
 | 	p = c->aux; | 
 |  | 
 | 	switch (NETTYPE(c->qid.path)) { | 
 | 	case Qdir: | 
 | 		return devdirread(c, va, n, p->pipedir, ARRAY_SIZE(pipedir), | 
 | 						  pipegen); | 
 | 	case Qctl: | 
 | 		return readnum(offset, va, n, p->path, NUMSIZE32); | 
 | 	case Qdata0: | 
 | 		if (c->flag & O_NONBLOCK) | 
 | 			return qread_nonblock(p->q[0], va, n); | 
 | 		else | 
 | 			return qread(p->q[0], va, n); | 
 | 	case Qdata1: | 
 | 		if (c->flag & O_NONBLOCK) | 
 | 			return qread_nonblock(p->q[1], va, n); | 
 | 		else | 
 | 			return qread(p->q[1], va, n); | 
 | 	default: | 
 | 		panic("piperead"); | 
 | 	} | 
 | 	return -1;	/* not reached */ | 
 | } | 
 |  | 
 | static struct block *pipebread(struct chan *c, size_t n, off64_t offset) | 
 | { | 
 | 	Pipe *p; | 
 |  | 
 | 	p = c->aux; | 
 |  | 
 | 	switch (NETTYPE(c->qid.path)) { | 
 | 	case Qdata0: | 
 | 		if (c->flag & O_NONBLOCK) | 
 | 			return qbread_nonblock(p->q[0], n); | 
 | 		else | 
 | 			return qbread(p->q[0], n); | 
 | 	case Qdata1: | 
 | 		if (c->flag & O_NONBLOCK) | 
 | 			return qbread_nonblock(p->q[1], n); | 
 | 		else | 
 | 			return qbread(p->q[1], n); | 
 | 	} | 
 |  | 
 | 	return devbread(c, n, offset); | 
 | } | 
 |  | 
 | /* | 
 |  *  A write to a closed pipe causes an EPIPE error to be thrown. | 
 |  */ | 
 | static size_t pipewrite(struct chan *c, void *va, size_t n, off64_t ignored) | 
 | { | 
 | 	ERRSTACK(1); | 
 | 	Pipe *p; | 
 | 	struct cmdbuf *cb; | 
 |  | 
 | 	p = c->aux; | 
 |  | 
 | 	switch (NETTYPE(c->qid.path)) { | 
 | 	case Qctl: | 
 | 		cb = parsecmd(va, n); | 
 | 		if (waserror()) { | 
 | 			kfree(cb); | 
 | 			nexterror(); | 
 | 		} | 
 | 		if (cb->nf < 1) | 
 | 			error(EFAIL, "short control request"); | 
 | 		if (strcmp(cb->f[0], "oneblock") == 0) { | 
 | 			q_toggle_qmsg(p->q[0], TRUE); | 
 | 			q_toggle_qcoalesce(p->q[0], TRUE); | 
 | 			q_toggle_qmsg(p->q[1], TRUE); | 
 | 			q_toggle_qcoalesce(p->q[1], TRUE); | 
 | 		} else { | 
 | 			error(EFAIL, "unknown control request"); | 
 | 		} | 
 | 		kfree(cb); | 
 | 		poperror(); | 
 | 		break; | 
 |  | 
 | 	case Qdata0: | 
 | 		if (c->flag & O_NONBLOCK) | 
 | 			n = qwrite_nonblock(p->q[1], va, n); | 
 | 		else | 
 | 			n = qwrite(p->q[1], va, n); | 
 | 		break; | 
 |  | 
 | 	case Qdata1: | 
 | 		if (c->flag & O_NONBLOCK) | 
 | 			n = qwrite_nonblock(p->q[0], va, n); | 
 | 		else | 
 | 			n = qwrite(p->q[0], va, n); | 
 | 		break; | 
 |  | 
 | 	default: | 
 | 		panic("pipewrite"); | 
 | 	} | 
 |  | 
 | 	return n; | 
 | } | 
 |  | 
 | static size_t pipebwrite(struct chan *c, struct block *bp, off64_t offset) | 
 | { | 
 | 	long n; | 
 | 	Pipe *p; | 
 | 	//Prog *r; | 
 |  | 
 | 	p = c->aux; | 
 | 	switch (NETTYPE(c->qid.path)) { | 
 | 	case Qctl: | 
 | 		return devbwrite(c, bp, offset); | 
 | 	case Qdata0: | 
 | 		if (c->flag & O_NONBLOCK) | 
 | 			n = qbwrite_nonblock(p->q[1], bp); | 
 | 		else | 
 | 			n = qbwrite(p->q[1], bp); | 
 | 		break; | 
 |  | 
 | 	case Qdata1: | 
 | 		if (c->flag & O_NONBLOCK) | 
 | 			n = qbwrite_nonblock(p->q[0], bp); | 
 | 		else | 
 | 			n = qbwrite(p->q[0], bp); | 
 | 		break; | 
 |  | 
 | 	default: | 
 | 		n = 0; | 
 | 		panic("pipebwrite"); | 
 | 	} | 
 |  | 
 | 	return n; | 
 | } | 
 |  | 
 | static size_t pipewstat(struct chan *c, uint8_t *dp, size_t n) | 
 | { | 
 | 	ERRSTACK(2); | 
 | 	struct dir *d; | 
 | 	Pipe *p; | 
 | 	int d1; | 
 |  | 
 | 	if (c->qid.type & QTDIR) | 
 | 		error(EPERM, ERROR_FIXME); | 
 | 	p = c->aux; | 
 | 	if (strcmp(current->user.name, p->user) != 0) | 
 | 		error(EPERM, ERROR_FIXME); | 
 | 	d = kzmalloc(sizeof(*d) + n, 0); | 
 | 	if (waserror()) { | 
 | 		kfree(d); | 
 | 		nexterror(); | 
 | 	} | 
 | 	n = convM2D(dp, n, d, (char *)&d[1]); | 
 | 	if (n == 0) | 
 | 		error(ENODATA, ERROR_FIXME); | 
 | 	d1 = NETTYPE(c->qid.path) == Qdata1; | 
 | 	if (!emptystr(d->name)) { | 
 | 		validwstatname(d->name); | 
 | 		if (strlen(d->name) >= KNAMELEN) | 
 | 			error(ENAMETOOLONG, ERROR_FIXME); | 
 | 		if (strncmp(p->pipedir[1 + !d1].name, d->name, KNAMELEN) == 0) | 
 | 			error(EEXIST, ERROR_FIXME); | 
 | 		strlcpy(p->pipedir[1 + d1].name, d->name, KNAMELEN); | 
 | 	} | 
 | 	if (d->mode != -1) | 
 | 		p->pipedir[d1 + 1].perm = d->mode & 0777; | 
 | 	poperror(); | 
 | 	kfree(d); | 
 | 	return n; | 
 | } | 
 |  | 
 | static char *pipechaninfo(struct chan *chan, char *ret, size_t ret_l) | 
 | { | 
 | 	Pipe *p = chan->aux; | 
 |  | 
 | 	switch (NETTYPE(chan->qid.path)) { | 
 | 	case Qdir: | 
 | 		snprintf(ret, ret_l, "Qdir, ID %d", p->path); | 
 | 		break; | 
 | 	case Qctl: | 
 | 		snprintf(ret, ret_l, "Qctl, ID %d", p->path); | 
 | 		break; | 
 | 	case Qdata0: | 
 | 		snprintf(ret, ret_l, | 
 | 		         "Qdata%d, ID %d, %s, rq len %d, wq len %d, total read %llu", | 
 | 		         0, p->path, | 
 | 		         SLIST_EMPTY(&p->data_taps) ? "untapped" : "tapped", | 
 | 		         qlen(p->q[0]), | 
 | 		         qlen(p->q[1]), q_bytes_read(p->q[0])); | 
 | 		break; | 
 | 	case Qdata1: | 
 | 		snprintf(ret, ret_l, | 
 | 		         "Qdata%d, ID %d, %s, rq len %d, wq len %d, total read %llu", | 
 | 		         1, p->path, | 
 | 		         SLIST_EMPTY(&p->data_taps) ? "untapped" : "tapped", | 
 | 		         qlen(p->q[1]), | 
 | 		         qlen(p->q[0]), q_bytes_read(p->q[1])); | 
 | 		break; | 
 | 	default: | 
 | 		ret = "Unknown type"; | 
 | 		break; | 
 | 	} | 
 | 	return ret; | 
 | } | 
 |  | 
 | /* We pass the pipe as data.  The pipe will outlive any potential qio callbacks. | 
 |  * Meaning, we don't need to worry about the pipe disappearing if we're in here. | 
 |  * If we're in here, then the q exists, which means the pipe exists. | 
 |  * | 
 |  * However, the chans do not necessarily exist.  The taps keep the chans around. | 
 |  * So we only know which chan we're firing when we look at an individual tap. */ | 
 | static void pipe_wake_cb(struct queue *q, void *data, int filter) | 
 | { | 
 | 	Pipe *p = (Pipe*)data; | 
 | 	struct fd_tap *tap_i; | 
 | 	struct chan *chan; | 
 |  | 
 | 	spin_lock(&p->tap_lock); | 
 | 	SLIST_FOREACH(tap_i, &p->data_taps, link) { | 
 | 		chan = tap_i->chan; | 
 | 		/* Depending which chan did the tapping, we'll care about | 
 | 		 * different filters on different qs.  For instance, if we | 
 | 		 * tapped Qdata0, then we only care about readables on q[0], | 
 | 		 * writables on q[1], and hangups on either.  More precisely, we | 
 | 		 * don't care about writables on q[0] or readables on q[1]. | 
 | 		 * | 
 | 		 * Note the *tap's* filter might differ from the CB's filter. | 
 | 		 * The CB could be for read|write|hangup on q[1], with a Qdata0 | 
 | 		 * tap for just read.  We don't want to just pass the CB filt | 
 | 		 * directly to fire_tap, since that would pass the CB's read on | 
 | 		 * q[1] to the tap and fire.  The user would think q[0] was | 
 | 		 * readable.  This is why I mask out the CB filter events that | 
 | 		 * we know they don't want. */ | 
 | 		switch (NETTYPE(chan->qid.path)) { | 
 | 		case Qdata0: | 
 | 			if (q == p->q[0]) | 
 | 				filter &= ~FDTAP_FILT_WRITABLE; | 
 | 			else | 
 | 				filter &= ~FDTAP_FILT_READABLE; | 
 | 			break; | 
 | 		case Qdata1: | 
 | 			if (q == p->q[1]) | 
 | 				filter &= ~FDTAP_FILT_WRITABLE; | 
 | 			else | 
 | 				filter &= ~FDTAP_FILT_READABLE; | 
 | 			break; | 
 | 		default: | 
 | 			panic("Shouldn't be able to tap pipe qid %p", | 
 | 			      chan->qid.path); | 
 | 		} | 
 | 		fire_tap(tap_i, filter); | 
 | 	} | 
 | 	spin_unlock(&p->tap_lock); | 
 | } | 
 |  | 
 | static int pipetapfd(struct chan *chan, struct fd_tap *tap, int cmd) | 
 | { | 
 | 	int ret; | 
 | 	Pipe *p; | 
 |  | 
 | 	p = chan->aux; | 
 | #define DEVPIPE_LEGAL_DATA_TAPS (FDTAP_FILT_READABLE | FDTAP_FILT_WRITABLE | \ | 
 |                                  FDTAP_FILT_HANGUP | FDTAP_FILT_ERROR) | 
 |  | 
 | 	switch (NETTYPE(chan->qid.path)) { | 
 | 	case Qdata0: | 
 | 	case Qdata1: | 
 | 		if (tap->filter & ~DEVPIPE_LEGAL_DATA_TAPS) { | 
 | 			set_errno(ENOSYS); | 
 | 			set_errstr("Unsupported #%s data tap %p, must be %p", | 
 | 				   devname(), tap->filter, | 
 | 				   DEVPIPE_LEGAL_DATA_TAPS); | 
 | 			return -1; | 
 | 		} | 
 | 		spin_lock(&p->tap_lock); | 
 | 		switch (cmd) { | 
 | 		case (FDTAP_CMD_ADD): | 
 | 			if (SLIST_EMPTY(&p->data_taps)) { | 
 | 				qio_set_wake_cb(p->q[0], pipe_wake_cb, p); | 
 | 				qio_set_wake_cb(p->q[1], pipe_wake_cb, p); | 
 | 			} | 
 | 			SLIST_INSERT_HEAD(&p->data_taps, tap, link); | 
 | 			ret = 0; | 
 | 			break; | 
 | 		case (FDTAP_CMD_REM): | 
 | 			SLIST_REMOVE(&p->data_taps, tap, fd_tap, link); | 
 | 			if (SLIST_EMPTY(&p->data_taps)) { | 
 | 				qio_set_wake_cb(p->q[0], 0, p); | 
 | 				qio_set_wake_cb(p->q[1], 0, p); | 
 | 			} | 
 | 			ret = 0; | 
 | 			break; | 
 | 		default: | 
 | 			set_errno(ENOSYS); | 
 | 			set_errstr("Unsupported #%s data tap command %p", | 
 | 				   devname(), cmd); | 
 | 			ret = -1; | 
 | 		} | 
 | 		spin_unlock(&p->tap_lock); | 
 | 		return ret; | 
 | 	default: | 
 | 		set_errno(ENOSYS); | 
 | 		set_errstr("Can't tap #%s file type %d", devname(), | 
 | 		           NETTYPE(chan->qid.path)); | 
 | 		return -1; | 
 | 	} | 
 | } | 
 |  | 
 | static unsigned long pipe_chan_ctl(struct chan *c, int op, unsigned long a1, | 
 |                                    unsigned long a2, unsigned long a3, | 
 |                                    unsigned long a4) | 
 | { | 
 | 	switch (op) { | 
 | 	case CCTL_SET_FL: | 
 | 		return 0; | 
 | 	default: | 
 | 		error(EINVAL, "%s does not support %d", __func__, op); | 
 | 	} | 
 | } | 
 |  | 
 | struct dev pipedevtab __devtab = { | 
 | 	.name = "pipe", | 
 |  | 
 | 	.reset = devreset, | 
 | 	.init = pipeinit, | 
 | 	.shutdown = devshutdown, | 
 | 	.attach = pipeattach, | 
 | 	.walk = pipewalk, | 
 | 	.stat = pipestat, | 
 | 	.open = pipeopen, | 
 | 	.create = devcreate, | 
 | 	.close = pipeclose, | 
 | 	.read = piperead, | 
 | 	.bread = pipebread, | 
 | 	.write = pipewrite, | 
 | 	.bwrite = pipebwrite, | 
 | 	.remove = devremove, | 
 | 	.wstat = pipewstat, | 
 | 	.power = devpower, | 
 | 	.chaninfo = pipechaninfo, | 
 | 	.tapfd = pipetapfd, | 
 | 	.chan_ctl = pipe_chan_ctl, | 
 | }; |