blob: d97acd51e0c17db421c5fa201811a3b23bf8e31b [file] [log] [blame]
/* 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,
};