root/lib/libpuffs/framebuf.c
/*      $NetBSD: framebuf.c,v 1.37 2022/04/19 20:32:17 rillig Exp $     */

/*
 * Copyright (c) 2007  Antti Kantee.  All Rights Reserved.
 *
 * Development of this software was supported by the
 * Finnish Cultural Foundation.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 * The event portion of this code is a twisty maze of pointers,
 * flags, yields and continues.  Sincere aplogies.
 */

#include <sys/cdefs.h>
#if !defined(lint)
__RCSID("$NetBSD: framebuf.c,v 1.37 2022/04/19 20:32:17 rillig Exp $");
#endif /* !lint */

#include <sys/types.h>
#include <sys/queue.h>

#include <assert.h>
#include <errno.h>
#include <poll.h>
#include <puffs.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include "puffs_priv.h"

struct puffs_framebuf {
        struct puffs_cc *pcc;   /* pcc to continue with */
        /* OR */
        puffs_framev_cb fcb;    /* non-blocking callback */
        void *fcb_arg;          /* argument for previous */

        uint8_t *buf;           /* buffer base */
        size_t len;             /* total length */

        size_t offset;          /* cursor, telloff() */
        size_t maxoff;          /* maximum offset for data, tellsize() */

        volatile int rv;        /* errno value */

        int     istat;

        TAILQ_ENTRY(puffs_framebuf) pfb_entries;
};
#define ISTAT_NODESTROY 0x01    /* indestructible by framebuf_destroy() */
#define ISTAT_INTERNAL  0x02    /* never leaves library                 */
#define ISTAT_NOREPLY   0x04    /* nuke after sending                   */
#define ISTAT_DIRECT    0x08    /* receive directly, no moveinfo        */

#define ISTAT_ONQUEUE   ISTAT_NODESTROY /* alias */

#define PUFBUF_INCRALLOC 4096
#define PUFBUF_REMAIN(p) (p->len - p->offset)

/* for poll/kqueue */
struct puffs_fbevent {
        struct puffs_cc *pcc;
        int what;
        volatile int rv;

        LIST_ENTRY(puffs_fbevent) pfe_entries;
};

static struct puffs_fctrl_io *
getfiobyfd(struct puffs_usermount *pu, int fd)
{
        struct puffs_fctrl_io *fio;

        LIST_FOREACH(fio, &pu->pu_ios, fio_entries)
                if (fio->io_fd == fd)
                        return fio;
        return NULL;
}

struct puffs_framebuf *
puffs_framebuf_make(void)
{
        struct puffs_framebuf *pufbuf;

        pufbuf = malloc(sizeof(struct puffs_framebuf));
        if (pufbuf == NULL)
                return NULL;
        memset(pufbuf, 0, sizeof(struct puffs_framebuf));

        pufbuf->buf = malloc(PUFBUF_INCRALLOC);
        if (pufbuf->buf == NULL) {
                free(pufbuf);
                return NULL;
        }
        pufbuf->len = PUFBUF_INCRALLOC;

        puffs_framebuf_recycle(pufbuf);
        return pufbuf;
}

void
puffs_framebuf_destroy(struct puffs_framebuf *pufbuf)
{

        assert((pufbuf->istat & ISTAT_NODESTROY) == 0);

        free(pufbuf->buf);
        free(pufbuf);
}

void
puffs_framebuf_recycle(struct puffs_framebuf *pufbuf)
{

        assert((pufbuf->istat & ISTAT_NODESTROY) == 0);

        pufbuf->offset = 0;
        pufbuf->maxoff = 0;
        pufbuf->istat = 0;
}

static int
reservespace(struct puffs_framebuf *pufbuf, size_t off, size_t wantsize)
{
        size_t incr;
        void *nd;

        if (off <= pufbuf->len && pufbuf->len - off >= wantsize)
                return 0;

        for (incr = PUFBUF_INCRALLOC;
            pufbuf->len + incr < off + wantsize;
            incr += PUFBUF_INCRALLOC)
                continue;

        nd = realloc(pufbuf->buf, pufbuf->len + incr);
        if (nd == NULL)
                return -1;

        pufbuf->buf = nd;
        pufbuf->len += incr;

        return 0;
}

int
puffs_framebuf_dup(struct puffs_framebuf *pb, struct puffs_framebuf **pbp)
{
        struct puffs_framebuf *newpb;

        newpb = puffs_framebuf_make();
        if (newpb == NULL) {
                errno = ENOMEM;
                return -1;
        }
        memcpy(newpb, pb, sizeof(struct puffs_framebuf));

        newpb->buf = NULL;
        newpb->len = 0;
        if (reservespace(newpb, 0, pb->maxoff) == -1) {
                puffs_framebuf_destroy(newpb);
                return -1;
        }

        memcpy(newpb->buf, pb->buf, pb->maxoff);
        newpb->istat = 0;
        *pbp = newpb;

        return 0;
}

int
puffs_framebuf_reserve_space(struct puffs_framebuf *pufbuf, size_t wantsize)
{

        return reservespace(pufbuf, pufbuf->offset, wantsize);
}

int
puffs_framebuf_putdata(struct puffs_framebuf *pufbuf,
        const void *data, size_t dlen)
{

        if (PUFBUF_REMAIN(pufbuf) < dlen)
                if (puffs_framebuf_reserve_space(pufbuf, dlen) == -1)
                        return -1;

        memcpy(pufbuf->buf + pufbuf->offset, data, dlen);
        pufbuf->offset += dlen;

        if (pufbuf->offset > pufbuf->maxoff)
                pufbuf->maxoff = pufbuf->offset;

        return 0;
}

int
puffs_framebuf_putdata_atoff(struct puffs_framebuf *pufbuf, size_t offset,
        const void *data, size_t dlen)
{

        if (reservespace(pufbuf, offset, dlen) == -1)
                return -1;

        memcpy(pufbuf->buf + offset, data, dlen);

        if (offset + dlen > pufbuf->maxoff)
                pufbuf->maxoff = offset + dlen;

        return 0;
}

int
puffs_framebuf_getdata(struct puffs_framebuf *pufbuf, void *data, size_t dlen)
{

        if (pufbuf->maxoff < pufbuf->offset + dlen) {
                errno = ENOBUFS;
                return -1;
        }

        memcpy(data, pufbuf->buf + pufbuf->offset, dlen);
        pufbuf->offset += dlen;

        return 0;
}

int
puffs_framebuf_getdata_atoff(struct puffs_framebuf *pufbuf, size_t offset,
        void *data, size_t dlen)
{

        if (pufbuf->maxoff < offset + dlen) {
                errno = ENOBUFS;
                return -1;
        }

        memcpy(data, pufbuf->buf + offset, dlen);
        return 0;
}

size_t
puffs_framebuf_telloff(struct puffs_framebuf *pufbuf)
{

        return pufbuf->offset;
}

size_t
puffs_framebuf_tellsize(struct puffs_framebuf *pufbuf)
{

        return pufbuf->maxoff;
}

size_t
puffs_framebuf_remaining(struct puffs_framebuf *pufbuf)
{

        return puffs_framebuf_tellsize(pufbuf) - puffs_framebuf_telloff(pufbuf);
}

int
puffs_framebuf_seekset(struct puffs_framebuf *pufbuf, size_t newoff)
{

        if (reservespace(pufbuf, newoff, 0) == -1)
                return -1;

        pufbuf->offset = newoff;
        return 0;
}

int
puffs_framebuf_getwindow(struct puffs_framebuf *pufbuf, size_t winoff,
        void **data, size_t *dlen)
{
        size_t winlen;

#ifdef WINTESTING
        winlen = MIN(*dlen, 32);
#else
        winlen = *dlen;
#endif

        if (reservespace(pufbuf, winoff, winlen) == -1)
                return -1;

        *data = pufbuf->buf + winoff;
        if (pufbuf->maxoff < winoff + winlen)
                pufbuf->maxoff = winoff + winlen;

        return 0;
}

void *
puffs__framebuf_getdataptr(struct puffs_framebuf *pufbuf)
{

        return pufbuf->buf;
}

static void
errnotify(struct puffs_usermount *pu, struct puffs_framebuf *pufbuf, int error)
{

        pufbuf->rv = error;
        if (pufbuf->pcc) {
                puffs__goto(pufbuf->pcc);
        } else if (pufbuf->fcb) {
                pufbuf->istat &= ~ISTAT_NODESTROY;
                pufbuf->fcb(pu, pufbuf, pufbuf->fcb_arg, error);
        } else {
                pufbuf->istat &= ~ISTAT_NODESTROY;
                puffs_framebuf_destroy(pufbuf);
        }
}

#define GETFIO(fd)                                                      \
do {                                                                    \
        fio = getfiobyfd(pu, fd);                                       \
        if (fio == NULL) {                                              \
                errno = EINVAL;                                         \
                return -1;                                              \
        }                                                               \
        if (fio->stat & FIO_WRGONE) {                                   \
                errno = ESHUTDOWN;                                      \
                return -1;                                              \
        }                                                               \
} while (0)

int
puffs_framev_enqueue_cc(struct puffs_cc *pcc, int fd,
        struct puffs_framebuf *pufbuf, int flags)
{
        struct puffs_usermount *pu = pcc->pcc_pu;
        struct puffs_fctrl_io *fio;

        /*
         * Technically we shouldn't allow this if RDGONE, but it's
         * difficult to trap write close without allowing writes.
         * And besides, there's probably a disconnect sequence in
         * the protocol, so unexpectedly getting a closed fd is
         * most likely an error condition.
         */
        GETFIO(fd);

        pufbuf->pcc = pcc;
        pufbuf->fcb = NULL;
        pufbuf->fcb_arg = NULL;

        pufbuf->offset = 0;
        pufbuf->istat |= ISTAT_NODESTROY;

        if (flags & PUFFS_FBQUEUE_URGENT)
                TAILQ_INSERT_HEAD(&fio->snd_qing, pufbuf, pfb_entries);
        else
                TAILQ_INSERT_TAIL(&fio->snd_qing, pufbuf, pfb_entries);

        puffs_cc_yield(pcc);
        if (pufbuf->rv) {
                pufbuf->istat &= ~ISTAT_NODESTROY;
                errno = pufbuf->rv;
                return -1;
        }

        return 0;
}

int
puffs_framev_enqueue_cb(struct puffs_usermount *pu, int fd,
        struct puffs_framebuf *pufbuf, puffs_framev_cb fcb, void *arg,
        int flags)
{
        struct puffs_fctrl_io *fio;

        /* see enqueue_cc */
        GETFIO(fd);

        pufbuf->pcc = NULL;
        pufbuf->fcb = fcb;
        pufbuf->fcb_arg = arg;

        pufbuf->offset = 0;
        pufbuf->istat |= ISTAT_NODESTROY;

        if (flags & PUFFS_FBQUEUE_URGENT)
                TAILQ_INSERT_HEAD(&fio->snd_qing, pufbuf, pfb_entries);
        else
                TAILQ_INSERT_TAIL(&fio->snd_qing, pufbuf, pfb_entries);

        return 0;
}

int
puffs_framev_enqueue_justsend(struct puffs_usermount *pu, int fd,
        struct puffs_framebuf *pufbuf, int reply, int flags)
{
        struct puffs_fctrl_io *fio;

        assert((pufbuf->istat & ISTAT_INTERNAL) == 0);

        GETFIO(fd);

        pufbuf->pcc = NULL;
        pufbuf->fcb = NULL;
        pufbuf->fcb_arg = NULL;

        pufbuf->offset = 0;
        pufbuf->istat |= ISTAT_NODESTROY;
        if (!reply)
                pufbuf->istat |= ISTAT_NOREPLY;

        if (flags & PUFFS_FBQUEUE_URGENT)
                TAILQ_INSERT_HEAD(&fio->snd_qing, pufbuf, pfb_entries);
        else
                TAILQ_INSERT_TAIL(&fio->snd_qing, pufbuf, pfb_entries);

        return 0;
}

/* ARGSUSED */
int
puffs_framev_enqueue_directreceive(struct puffs_cc *pcc, int fd,
        struct puffs_framebuf *pufbuf, int flags /* used in the future */)
{
        struct puffs_usermount *pu = pcc->pcc_pu;
        struct puffs_fctrl_io *fio;

        assert((pufbuf->istat & ISTAT_INTERNAL) == 0);

        fio = getfiobyfd(pu, fd);
        if (fio == NULL) {
                errno = EINVAL;
                return -1;
        }

        /* XXX: should have cur_in queue */
        assert(fio->cur_in == NULL);
        fio->cur_in = pufbuf;

        pufbuf->pcc = pcc;
        pufbuf->fcb = NULL;
        pufbuf->fcb_arg = NULL;

        pufbuf->offset = 0;
        pufbuf->istat |= ISTAT_NODESTROY | ISTAT_DIRECT;

        puffs_cc_yield(pcc);
        pufbuf->istat &= ~ISTAT_NODESTROY; /* XXX: not the right place */
        if (pufbuf->rv) {
                errno = pufbuf->rv;
                return -1;
        }

        return 0;
}

int
puffs_framev_enqueue_directsend(struct puffs_cc *pcc, int fd,
        struct puffs_framebuf *pufbuf, int flags)
{
        struct puffs_usermount *pu = pcc->pcc_pu;
        struct puffs_fctrl_io *fio;

        assert((pufbuf->istat & ISTAT_INTERNAL) == 0);

        if (flags & PUFFS_FBQUEUE_URGENT)
                abort(); /* EOPNOTSUPP for now */

        GETFIO(fd);

        pufbuf->pcc = pcc;
        pufbuf->fcb = NULL;
        pufbuf->fcb_arg = NULL;

        pufbuf->offset = 0;
        pufbuf->istat |= ISTAT_NODESTROY | ISTAT_DIRECT;

        TAILQ_INSERT_TAIL(&fio->snd_qing, pufbuf, pfb_entries);

        puffs_cc_yield(pcc);
        if (pufbuf->rv) {
                pufbuf->istat &= ~ISTAT_NODESTROY;
                errno = pufbuf->rv;
                return -1;
        }

        return 0;
}

int
puffs_framev_framebuf_ccpromote(struct puffs_framebuf *pufbuf,
        struct puffs_cc *pcc)
{

        if ((pufbuf->istat & ISTAT_ONQUEUE) == 0) {
                errno = EBUSY;
                return -1;
        }

        pufbuf->pcc = pcc;
        pufbuf->fcb = NULL;
        pufbuf->fcb_arg = NULL;
        pufbuf->istat &= ~ISTAT_NOREPLY;

        puffs_cc_yield(pcc);

        return 0;
}

int
puffs_framev_enqueue_waitevent(struct puffs_cc *pcc, int fd, int *what)
{
        struct puffs_usermount *pu = pcc->pcc_pu;
        struct puffs_fctrl_io *fio;
        struct puffs_fbevent feb;
        struct kevent kev;
        int rv, svwhat;

        svwhat = *what;

        if (*what == 0) {
                errno = EINVAL;
                return -1;
        }

        fio = getfiobyfd(pu, fd);
        if (fio == NULL) {
                errno = EINVAL;
                return -1;
        }

        feb.pcc = pcc;
        feb.what = *what & (PUFFS_FBIO_READ|PUFFS_FBIO_WRITE|PUFFS_FBIO_ERROR);

        if (*what & PUFFS_FBIO_READ)
                if ((fio->stat & FIO_ENABLE_R) == 0)
                        EV_SET(&kev, fd, EVFILT_READ, EV_ENABLE, 0, 0, fio);

        if (kevent(pu->pu_kq, &kev, 1, NULL, 0, NULL) == -1)
                return -1;

        if (*what & PUFFS_FBIO_READ)
                fio->rwait++;
        if (*what & PUFFS_FBIO_WRITE)
                fio->wwait++;

        LIST_INSERT_HEAD(&fio->ev_qing, &feb, pfe_entries);
        puffs_cc_yield(pcc);

        assert(svwhat == *what);

        if (*what & PUFFS_FBIO_READ) {
                fio->rwait--;
                if (fio->rwait == 0 && (fio->stat & FIO_ENABLE_R) == 0) {
                        EV_SET(&kev, fd, EVFILT_READ, EV_DISABLE, 0, 0, fio);
                        rv = kevent(pu->pu_kq, &kev, 1, NULL, 0, NULL);
#if 0
                        if (rv != 0)
                                /* XXXXX oh dear */;
#endif
                }
        }
        if (*what & PUFFS_FBIO_WRITE)
                fio->wwait--;

        if (feb.rv == 0) {
                *what = feb.what;
                rv = 0;
        } else {
                *what = PUFFS_FBIO_ERROR;
                errno = feb.rv;
                rv = -1;
        }

        return rv;
}

void
puffs__framev_notify(struct puffs_fctrl_io *fio, int what)
{
        struct puffs_fbevent *fbevp;

 restart:
        LIST_FOREACH(fbevp, &fio->ev_qing, pfe_entries) {
                if (fbevp->what & what) {
                        fbevp->what = what;
                        fbevp->rv = 0;
                        LIST_REMOVE(fbevp, pfe_entries);
                        puffs_cc_continue(fbevp->pcc);
                        goto restart;
                }
        }
}

static struct puffs_framebuf *
findbuf(struct puffs_usermount *pu, struct puffs_framectrl *fctrl,
        struct puffs_fctrl_io *fio, struct puffs_framebuf *findme)
{
        struct puffs_framebuf *cand;
        int notresp = 0;

        TAILQ_FOREACH(cand, &fio->res_qing, pfb_entries)
                if (fctrl->cmpfb(pu, findme, cand, &notresp) == 0 || notresp)
                        break;

        assert(!(notresp && cand == NULL));
        if (notresp || cand == NULL)
                return NULL;

        TAILQ_REMOVE(&fio->res_qing, cand, pfb_entries);
        return cand;
}

void
puffs__framebuf_moveinfo(struct puffs_framebuf *from, struct puffs_framebuf *to)
{

        assert(from->istat & ISTAT_INTERNAL);

        /* migrate buffer */
        free(to->buf);
        to->buf = from->buf;

        /* migrate buffer info */
        to->len = from->len;
        to->offset = from->offset;
        to->maxoff = from->maxoff;

        from->buf = NULL;
        from->len = 0;
}

void
puffs__framev_input(struct puffs_usermount *pu, struct puffs_framectrl *fctrl,
        struct puffs_fctrl_io *fio)
{
        struct puffs_framebuf *pufbuf, *appbuf;
        int rv, complete;

        while ((fio->stat & FIO_DEAD) == 0 && (fio->stat & FIO_ENABLE_R)) {
                if ((pufbuf = fio->cur_in) == NULL) {
                        pufbuf = puffs_framebuf_make();
                        if (pufbuf == NULL)
                                return;
                        pufbuf->istat |= ISTAT_INTERNAL;
                        fio->cur_in = pufbuf;
                }

                complete = 0;
                rv = fctrl->rfb(pu, pufbuf, fio->io_fd, &complete);

                /* error */
                if (rv) {
                        puffs__framev_readclose(pu, fio, rv);
                        fio->cur_in = NULL;
                        return;
                }

                /* partial read, come back to fight another day */
                if (complete == 0)
                        break;

                /* else: full read, process */
                fio->cur_in = NULL;
                if ((pufbuf->istat & ISTAT_DIRECT) == 0) {
                        appbuf = findbuf(pu, fctrl, fio, pufbuf);

                        /*
                         * No request for this frame?  If fs implements
                         * gotfb, give frame to that.  Otherwise drop it.
                         */
                        if (appbuf == NULL) {
                                if (fctrl->gotfb) {
                                        pufbuf->istat &= ~ISTAT_INTERNAL;
                                        fctrl->gotfb(pu, pufbuf);
                                } else {
                                        puffs_framebuf_destroy(pufbuf);
                                }
                                continue;
                        }
                        
                        puffs__framebuf_moveinfo(pufbuf, appbuf);
                        puffs_framebuf_destroy(pufbuf);
                } else {
                        appbuf = pufbuf;
                }
                appbuf->istat &= ~ISTAT_NODESTROY;
        
                if (appbuf->pcc) {
                        puffs__cc_cont(appbuf->pcc);
                } else if (appbuf->fcb) {
                        appbuf->fcb(pu, appbuf, appbuf->fcb_arg, 0);
                } else {
                        puffs_framebuf_destroy(appbuf);
                }

                /* hopeless romantics, here we go again */
        }
}

int
puffs__framev_output(struct puffs_usermount *pu, struct puffs_framectrl *fctrl,
        struct puffs_fctrl_io *fio)
{
        struct puffs_framebuf *pufbuf;
        int rv, complete, done;

        if (fio->stat & FIO_DEAD)
                return 0;

        for (pufbuf = TAILQ_FIRST(&fio->snd_qing), done = 0;
            pufbuf && (fio->stat & FIO_DEAD) == 0 && fio->stat & FIO_ENABLE_W;
            pufbuf = TAILQ_FIRST(&fio->snd_qing)) {
                complete = 0;
                rv = fctrl->wfb(pu, pufbuf, fio->io_fd, &complete);

                if (rv) {
                        puffs__framev_writeclose(pu, fio, rv);
                        done = 1;
                        break;
                }

                /* partial write */
                if (complete == 0)
                        return done;

                /* else, complete write */
                TAILQ_REMOVE(&fio->snd_qing, pufbuf, pfb_entries);

                /* can't wait for result if we can't read */
                if (fio->stat & FIO_RDGONE) {
                        errnotify(pu, pufbuf, ENXIO);
                        done = 1;
                } else if ((pufbuf->istat & ISTAT_DIRECT)) {
                        pufbuf->istat &= ~ISTAT_NODESTROY;
                        done = 1;
                        puffs__cc_cont(pufbuf->pcc);
                } else if ((pufbuf->istat & ISTAT_NOREPLY) == 0) {
                        TAILQ_INSERT_TAIL(&fio->res_qing, pufbuf,
                            pfb_entries);
                } else {
                        pufbuf->istat &= ~ISTAT_NODESTROY;
                        puffs_framebuf_destroy(pufbuf);
                }

                /* omstart! */
        }

        return done;
}

int
puffs__framev_addfd_ctrl(struct puffs_usermount *pu, int fd, int what,
        struct puffs_framectrl *pfctrl)
{
        struct puffs_fctrl_io *fio;
        struct kevent kev[2];
        size_t nevs;
        int rv, readenable;

        nevs = pu->pu_nevs+2;
        if (reallocarr(&pu->pu_evs, nevs, sizeof(struct kevent)) != 0) {
                errno = ENOMEM;
                return -1;
        }

        fio = malloc(sizeof(struct puffs_fctrl_io));
        if (fio == NULL)
                return -1;
        memset(fio, 0, sizeof(struct puffs_fctrl_io));
        fio->io_fd = fd;
        fio->cur_in = NULL;
        fio->fctrl = pfctrl;
        TAILQ_INIT(&fio->snd_qing);
        TAILQ_INIT(&fio->res_qing);
        LIST_INIT(&fio->ev_qing);

        readenable = 0;
        if ((what & PUFFS_FBIO_READ) == 0)
                readenable = EV_DISABLE;

        if (pu->pu_state & PU_INLOOP) {
                struct stat st;
                size_t nf = 0;

                if (fstat(fd, &st) == -1)
                        goto out;
                EV_SET(&kev[nf], fd, EVFILT_READ, EV_ADD|readenable, 0, 0, fio);
                nf++;
                if (S_ISSOCK(st.st_mode)) {
                        EV_SET(&kev[nf], fd, EVFILT_WRITE,
                            EV_ADD|EV_DISABLE, 0, 0, fio);
                        nf++;
                }
                rv = kevent(pu->pu_kq, kev, nf, NULL, 0, NULL);
                if (rv == -1)
                        goto out;
        }
        if (what & PUFFS_FBIO_READ)
                fio->stat |= FIO_ENABLE_R;
        if (what & PUFFS_FBIO_WRITE)
                fio->stat |= FIO_ENABLE_W;

        LIST_INSERT_HEAD(&pu->pu_ios, fio, fio_entries);
        pu->pu_nevs = nevs;

        return 0;
out:
        free(fio);
        return -1;
}

int
puffs_framev_addfd(struct puffs_usermount *pu, int fd, int what)
{

        return puffs__framev_addfd_ctrl(pu, fd, what,
            &pu->pu_framectrl[PU_FRAMECTRL_USER]);
}

/*
 * XXX: the following en/disable should be coalesced and executed
 * only during the actual kevent call.  So feel free to fix if
 * threatened by mindblowing boredom.
 */

int
puffs_framev_enablefd(struct puffs_usermount *pu, int fd, int what)
{
        struct kevent kev;
        struct puffs_fctrl_io *fio;
        int rv = 0;

        assert((what & (PUFFS_FBIO_READ | PUFFS_FBIO_WRITE)) != 0);

        fio = getfiobyfd(pu, fd);
        if (fio == NULL) {
                errno = ENXIO;
                return -1;
        }

        /* write is enabled in the event loop if there is output */
        if (what & PUFFS_FBIO_READ && fio->rwait == 0) {
                EV_SET(&kev, fd, EVFILT_READ, EV_ENABLE, 0, 0, fio);
                rv = kevent(pu->pu_kq, &kev, 1, NULL, 0, NULL);
        }

        if (rv == 0) {
                if (what & PUFFS_FBIO_READ)
                        fio->stat |= FIO_ENABLE_R;
                if (what & PUFFS_FBIO_WRITE)
                        fio->stat |= FIO_ENABLE_W;
        }

        return rv;
}

int
puffs_framev_disablefd(struct puffs_usermount *pu, int fd, int what)
{
        struct kevent kev[2];
        struct puffs_fctrl_io *fio;
        size_t i;
        int rv;

        assert((what & (PUFFS_FBIO_READ | PUFFS_FBIO_WRITE)) != 0);

        fio = getfiobyfd(pu, fd);
        if (fio == NULL) {
                errno = ENXIO;
                return -1;
        }

        i = 0;
        if (what & PUFFS_FBIO_READ && fio->rwait == 0) {
                EV_SET(&kev[0], fd, EVFILT_READ, EV_DISABLE, 0, 0, fio);
                i++;
        }
        if (what & PUFFS_FBIO_WRITE && fio->stat & FIO_WR && fio->wwait == 0) {
                EV_SET(&kev[1], fd, EVFILT_WRITE, EV_DISABLE, 0, 0, fio);
                i++;
        }
        if (i)
                rv = kevent(pu->pu_kq, kev, i, NULL, 0, NULL);
        else
                rv = 0;

        if (rv == 0) {
                if (what & PUFFS_FBIO_READ)
                        fio->stat &= ~FIO_ENABLE_R;
                if (what & PUFFS_FBIO_WRITE)
                        fio->stat &= ~FIO_ENABLE_W;
        }

        return rv;
}

void
puffs__framev_readclose(struct puffs_usermount *pu,
        struct puffs_fctrl_io *fio, int error)
{
        struct puffs_framebuf *pufbuf;
        struct kevent kev;
        int notflag;

        if (fio->stat & FIO_RDGONE || fio->stat & FIO_DEAD)
                return;
        fio->stat |= FIO_RDGONE;

        if (fio->cur_in) {
                if ((fio->cur_in->istat & ISTAT_DIRECT) == 0) {
                        puffs_framebuf_destroy(fio->cur_in);
                        fio->cur_in = NULL;
                } else {
                        errnotify(pu, fio->cur_in, error);
                }
        }

        while ((pufbuf = TAILQ_FIRST(&fio->res_qing)) != NULL) {
                TAILQ_REMOVE(&fio->res_qing, pufbuf, pfb_entries);
                errnotify(pu, pufbuf, error);
        }

        EV_SET(&kev, fio->io_fd, EVFILT_READ, EV_DELETE, 0, 0, 0);
        (void) kevent(pu->pu_kq, &kev, 1, NULL, 0, NULL);

        notflag = PUFFS_FBIO_READ;
        if (fio->stat & FIO_WRGONE)
                notflag |= PUFFS_FBIO_WRITE;

        if (fio->fctrl->fdnotfn)
                fio->fctrl->fdnotfn(pu, fio->io_fd, notflag);
}

void
puffs__framev_writeclose(struct puffs_usermount *pu,
        struct puffs_fctrl_io *fio, int error)
{
        struct puffs_framebuf *pufbuf;
        struct kevent kev;
        int notflag;

        if (fio->stat & FIO_WRGONE || fio->stat & FIO_DEAD)
                return;
        fio->stat |= FIO_WRGONE;

        while ((pufbuf = TAILQ_FIRST(&fio->snd_qing)) != NULL) {
                TAILQ_REMOVE(&fio->snd_qing, pufbuf, pfb_entries);
                errnotify(pu, pufbuf, error);
        }

        EV_SET(&kev, fio->io_fd, EVFILT_WRITE, EV_DELETE, 0, 0, 0);
        (void) kevent(pu->pu_kq, &kev, 1, NULL, 0, NULL);

        notflag = PUFFS_FBIO_WRITE;
        if (fio->stat & FIO_RDGONE)
                notflag |= PUFFS_FBIO_READ;

        if (fio->fctrl->fdnotfn)
                fio->fctrl->fdnotfn(pu, fio->io_fd, notflag);
}

static int
removefio(struct puffs_usermount *pu, struct puffs_fctrl_io *fio, int error)
{
        struct puffs_fbevent *fbevp;

        LIST_REMOVE(fio, fio_entries);
        if (pu->pu_state & PU_INLOOP) {
                puffs__framev_readclose(pu, fio, error);
                puffs__framev_writeclose(pu, fio, error);
        }

        while ((fbevp = LIST_FIRST(&fio->ev_qing)) != NULL) {
                fbevp->rv = error;
                LIST_REMOVE(fbevp, pfe_entries);
                puffs__goto(fbevp->pcc);
        }

        /* don't bother with realloc */
        pu->pu_nevs -= 2;

        /* don't free us yet, might have some references in event arrays */
        fio->stat |= FIO_DEAD;
        LIST_INSERT_HEAD(&pu->pu_ios_rmlist, fio, fio_entries);

        return 0;

}

int
puffs_framev_removefd(struct puffs_usermount *pu, int fd, int error)
{
        struct puffs_fctrl_io *fio;

        fio = getfiobyfd(pu, fd);
        if (fio == NULL) {
                errno = ENXIO;
                return -1;
        }

        return removefio(pu, fio, error ? error : ECONNRESET);
}

void
puffs_framev_removeonclose(struct puffs_usermount *pu, int fd, int what)
{

        if (what == (PUFFS_FBIO_READ | PUFFS_FBIO_WRITE))
                (void) puffs_framev_removefd(pu, fd, ECONNRESET);
}

void
puffs_framev_unmountonclose(struct puffs_usermount *pu, int fd, int what)
{

        /* XXX & X: unmount is non-sensible */
        puffs_framev_removeonclose(pu, fd, what);
        if (what == (PUFFS_FBIO_READ | PUFFS_FBIO_WRITE))
                PU_SETSTATE(pu, PUFFS_STATE_UNMOUNTED);
}

void
puffs_framev_init(struct puffs_usermount *pu,
        puffs_framev_readframe_fn rfb, puffs_framev_writeframe_fn wfb,
        puffs_framev_cmpframe_fn cmpfb, puffs_framev_gotframe_fn gotfb,
        puffs_framev_fdnotify_fn fdnotfn)
{
        struct puffs_framectrl *pfctrl;

        pfctrl = &pu->pu_framectrl[PU_FRAMECTRL_USER];
        pfctrl->rfb = rfb;
        pfctrl->wfb = wfb;
        pfctrl->cmpfb = cmpfb;
        pfctrl->gotfb = gotfb;
        pfctrl->fdnotfn = fdnotfn;
}

void
puffs__framev_exit(struct puffs_usermount *pu)
{
        struct puffs_fctrl_io *fio;

        while ((fio = LIST_FIRST(&pu->pu_ios)) != NULL)
                removefio(pu, fio, ENXIO);
        free(pu->pu_evs);

        /* closing pu->pu_kq takes care of puffsfd */
}