/*      $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 */
}