root/lib/librumpclient/rumpclient.c
/*      $NetBSD: rumpclient.c,v 1.71 2023/07/31 04:37:04 rin Exp $      */

/*
 * Copyright (c) 2010, 2011 Antti Kantee.  All Rights Reserved.
 *
 * 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.
 */

/*
 * Client side routines for rump syscall proxy.
 */

#include <rump/rumpuser_port.h>

/*
 * We use kqueue on the BSDs, poll elsewhere.  We
 * want to use kqueue because it will give us the ability to get signal
 * notifications but defer their handling to a stage where we do not
 * hold the communication lock.  Taking a signal while holding on to
 * that lock may cause a deadlock.  Therefore, block signals throughout
 * the RPC when using poll.  On Linux, we use signalfd in the same role
 * as kqueue on NetBSD to be able to take signals while waiting for a
 * response from the server.
 */

#if defined(__NetBSD__) || defined(__FreeBSD__) || \
    defined(__DragonFly__) || defined(__OpenBSD__)
#define USE_KQUEUE
#endif
#if defined(__linux__)
#define USE_SIGNALFD
#endif

__RCSID("$NetBSD: rumpclient.c,v 1.71 2023/07/31 04:37:04 rin Exp $");

#include <sys/param.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/time.h>

#ifdef USE_KQUEUE
#include <sys/event.h>
#endif

#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/tcp.h>

#include <assert.h>
#include <dlfcn.h>
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <pthread.h>
#include <signal.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <rump/rumpclient.h>

#define HOSTOPS
int     (*host_socket)(int, int, int);
int     (*host_close)(int);
int     (*host_connect)(int, const struct sockaddr *, socklen_t);
int     (*host_fcntl)(int, int, ...);
int     (*host_poll)(struct pollfd *, nfds_t, int);
ssize_t (*host_read)(int, void *, size_t);
ssize_t (*host_sendmsg)(int, const struct msghdr *, int);
int     (*host_setsockopt)(int, int, int, const void *, socklen_t);
int     (*host_dup)(int);

#ifdef USE_KQUEUE
int     (*host_kqueue)(void);
#ifdef __NetBSD__
int     (*host_kevent)(int, const struct kevent *, size_t,
                       struct kevent *, size_t, const struct timespec *);
#else
int     (*host_kevent)(int, const struct kevent *, int,
                       struct kevent *, int, const struct timespec *);
#endif
#endif

#ifdef USE_SIGNALFD
#include <sys/signalfd.h>

int     (*host_signalfd)(int, const sigset_t *, int);
#endif

int     (*host_execve)(const char *, char *const[], char *const[]);

#include "sp_common.c"
#include "rumpuser_sigtrans.c"

static struct spclient clispc = {
        .spc_fd = -1,
};

static int holyfd = -1;
static sigset_t fullset;

static int doconnect(void);
static int handshake_req(struct spclient *, int, void *, int, bool);

/*
 * Default: don't retry.  Most clients can't handle it
 * (consider e.g. fds suddenly going missing).
 */
static time_t retrytimo = 0;

/* always defined to nothingness for now */
#define ERRLOG(a)

static int
send_with_recon(struct spclient *spc, struct iovec *iov, size_t iovlen)
{
        struct timeval starttime, curtime;
        time_t prevreconmsg;
        unsigned reconretries;
        int rv;

        for (prevreconmsg = 0, reconretries = 0;;) {
                rv = dosend(spc, iov, iovlen);
                if (__predict_false(rv == ENOTCONN || rv == EBADF)) {
                        /* no persistent connections */
                        if (retrytimo == 0) {
                                rv = ENOTCONN;
                                break;
                        }
                        if (retrytimo == RUMPCLIENT_RETRYCONN_DIE)
                                _exit(1);

                        if (!prevreconmsg) {
                                prevreconmsg = time(NULL);
                                gettimeofday(&starttime, NULL);
                        }
                        if (reconretries == 1) {
                                if (retrytimo == RUMPCLIENT_RETRYCONN_ONCE) {
                                        rv = ENOTCONN;
                                        break;
                                }
                                fprintf(stderr, "rump_sp: connection to "
                                    "kernel lost, trying to reconnect ...\n");
                        } else if (time(NULL) - prevreconmsg > 120) {
                                fprintf(stderr, "rump_sp: still trying to "
                                    "reconnect ...\n");
                                prevreconmsg = time(NULL);
                        }

                        /* check that we aren't over the limit */
                        if (retrytimo > 0) {
                                time_t tdiff;

                                gettimeofday(&curtime, NULL);
                                tdiff = curtime.tv_sec - starttime.tv_sec;
                                if (starttime.tv_usec > curtime.tv_usec)
                                        tdiff--;
                                if (tdiff >= retrytimo) {
                                        fprintf(stderr, "rump_sp: reconnect "
                                            "failed, %lld second timeout\n",
                                            (long long)retrytimo);
                                        return ENOTCONN;
                                }
                        }

                        /* adhoc backoff timer */
                        if (reconretries < 10) {
                                usleep(100000 * reconretries);
                        } else {
                                sleep(MIN(10, reconretries-9));
                        }
                        reconretries++;

                        if ((rv = doconnect()) != 0)
                                continue;
                        if ((rv = handshake_req(&clispc, HANDSHAKE_GUEST,
                            NULL, 0, true)) != 0)
                                continue;

                        /*
                         * ok, reconnect successful.  we need to return to
                         * the upper layer to get the entire PDU resent.
                         */
                        if (reconretries != 1)
                                fprintf(stderr, "rump_sp: reconnected!\n");
                        rv = EAGAIN;
                        break;
                } else {
                        _DIAGASSERT(errno != EAGAIN);
                        break;
                }
        }

        return rv;
}

static int
cliwaitresp(struct spclient *spc, struct respwait *rw, sigset_t *mask,
        bool keeplock)
{
        uint64_t mygen;
        bool imalive = true;

        pthread_mutex_lock(&spc->spc_mtx);
        if (!keeplock)
                sendunlockl(spc);
        mygen = spc->spc_generation;

        rw->rw_error = 0;
        while (!rw->rw_done && rw->rw_error == 0) {
                if (__predict_false(spc->spc_generation != mygen || !imalive))
                        break;

                /* are we free to receive? */
                if (spc->spc_istatus == SPCSTATUS_FREE) {
                        int gotresp, dosig, rv;

                        spc->spc_istatus = SPCSTATUS_BUSY;
                        pthread_mutex_unlock(&spc->spc_mtx);

                        dosig = 0;
                        for (gotresp = 0; !gotresp; ) {
#ifdef USE_KQUEUE
                                struct kevent kev[8];
                                int i;

                                /*
                                 * typically we don't have a frame waiting
                                 * when we come in here, so call kevent now
                                 */
                                rv = host_kevent(holyfd, NULL, 0,
                                    kev, __arraycount(kev), NULL);

                                if (__predict_false(rv == -1)) {
                                        goto activity;
                                }

                                /*
                                 * XXX: don't know how this can happen
                                 * (timeout cannot expire since there
                                 * isn't one), but it does happen.
                                 * treat it as an expectional condition
                                 * and go through tryread to determine
                                 * alive status.
                                 */
                                if (__predict_false(rv == 0))
                                        goto activity;

                                for (i = 0; i < rv; i++) {
                                        if (kev[i].filter == EVFILT_SIGNAL)
                                                dosig++;
                                }
                                if (dosig)
                                        goto cleanup;

                                /*
                                 * ok, activity.  try to read a frame to
                                 * determine what happens next.
                                 */
 activity:
#else /* !USE_KQUEUE */
                                struct pollfd pfd[2];

                                pfd[0].fd = clispc.spc_fd;
                                pfd[0].events = POLLIN;
                                pfd[1].fd = holyfd;
                                pfd[1].events = POLLIN;

                                rv = host_poll(pfd, 2, -1);
                                if (rv >= 1 && pfd[1].revents & POLLIN) {
                                        dosig = 1;
                                        goto cleanup;
                                }
#endif /* !USE_KQUEUE */

                                switch (readframe(spc)) {
                                case 0:
                                        continue;
                                case -1:
                                        imalive = false;
                                        goto cleanup;
                                default:
                                        /* case 1 */
                                        break;
                                }

                                switch (spc->spc_hdr.rsp_class) {
                                case RUMPSP_RESP:
                                case RUMPSP_ERROR:
                                        kickwaiter(spc);
                                        gotresp = spc->spc_hdr.rsp_reqno ==
                                            rw->rw_reqno;
                                        break;
                                case RUMPSP_REQ:
                                        handlereq(spc);
                                        break;
                                default:
                                        /* panic */
                                        break;
                                }
                        }

 cleanup:
                        pthread_mutex_lock(&spc->spc_mtx);
                        if (spc->spc_istatus == SPCSTATUS_WANTED)
                                kickall(spc);
                        spc->spc_istatus = SPCSTATUS_FREE;

                        /* take one for the team */
                        if (dosig) {
                                pthread_mutex_unlock(&spc->spc_mtx);
                                pthread_sigmask(SIG_SETMASK, mask, NULL);
                                pthread_sigmask(SIG_SETMASK, &fullset, NULL);
                                pthread_mutex_lock(&spc->spc_mtx);
                        }
                } else {
                        spc->spc_istatus = SPCSTATUS_WANTED;
                        pthread_cond_wait(&rw->rw_cv, &spc->spc_mtx);
                }
        }
        TAILQ_REMOVE(&spc->spc_respwait, rw, rw_entries);
        pthread_mutex_unlock(&spc->spc_mtx);
        pthread_cond_destroy(&rw->rw_cv);

        if (spc->spc_generation != mygen || !imalive) {
                return ENOTCONN;
        }
        return rw->rw_error;
}

static int
syscall_req(struct spclient *spc, sigset_t *omask, int sysnum,
        const void *data, size_t dlen, void **resp)
{
        struct rsp_hdr rhdr;
        struct respwait rw;
        struct iovec iov[2];
        int rv;

        rhdr.rsp_len = sizeof(rhdr) + dlen;
        rhdr.rsp_class = RUMPSP_REQ;
        rhdr.rsp_type = RUMPSP_SYSCALL;
        rhdr.rsp_sysnum = sysnum;

        IOVPUT(iov[0], rhdr);
        IOVPUT_WITHSIZE(iov[1], __UNCONST(data), dlen);

        do {
                putwait(spc, &rw, &rhdr);
                if ((rv = send_with_recon(spc, iov, __arraycount(iov))) != 0) {
                        unputwait(spc, &rw);
                        continue;
                }

                rv = cliwaitresp(spc, &rw, omask, false);
                if (rv == ENOTCONN)
                        rv = EAGAIN;
        } while (rv == EAGAIN);

        *resp = rw.rw_data;
        return rv;
}

static int
handshake_req(struct spclient *spc, int type, void *data,
        int cancel, bool haslock)
{
        struct handshake_fork rf;
        const char *myprogname = NULL; /* XXXgcc */
        struct rsp_hdr rhdr;
        struct respwait rw;
        sigset_t omask;
        size_t bonus;
        struct iovec iov[2];
        int rv;

        if (type == HANDSHAKE_FORK) {
                bonus = sizeof(rf);
        } else {
#ifdef __NetBSD__
                /* would procfs work on NetBSD too? */
                myprogname = getprogname();
#else
                int fd = open("/proc/self/comm", O_RDONLY);
                if (fd == -1) {
                        myprogname = "???";
                } else {
                        static char commname[128];

                        memset(commname, 0, sizeof(commname));
                        if (read(fd, commname, sizeof(commname)) > 0) {
                                char *n;

                                n = strrchr(commname, '\n');
                                if (n)
                                        *n = '\0';
                                myprogname = commname;
                        } else {
                                myprogname = "???";
                        }
                        close(fd);
                }
#endif
                bonus = strlen(myprogname)+1;
        }

        /* performs server handshake */
        rhdr.rsp_len = sizeof(rhdr) + bonus;
        rhdr.rsp_class = RUMPSP_REQ;
        rhdr.rsp_type = RUMPSP_HANDSHAKE;
        rhdr.rsp_handshake = type;

        IOVPUT(iov[0], rhdr);

        pthread_sigmask(SIG_SETMASK, &fullset, &omask);
        if (haslock)
                putwait_locked(spc, &rw, &rhdr);
        else
                putwait(spc, &rw, &rhdr);
        if (type == HANDSHAKE_FORK) {
                memcpy(rf.rf_auth, data, sizeof(rf.rf_auth)); /* uh, why? */
                rf.rf_cancel = cancel;
                IOVPUT(iov[1], rf);
        } else {
                IOVPUT_WITHSIZE(iov[1], __UNCONST(myprogname), bonus);
        }
        rv = send_with_recon(spc, iov, __arraycount(iov));
        if (rv || cancel) {
                if (haslock)
                        unputwait_locked(spc, &rw);
                else
                        unputwait(spc, &rw);
                if (cancel) {
                        goto out;
                }
        } else {
                rv = cliwaitresp(spc, &rw, &omask, haslock);
        }
        if (rv)
                goto out;

        rv = *(int *)rw.rw_data;
        free(rw.rw_data);

 out:
        pthread_sigmask(SIG_SETMASK, &omask, NULL);
        return rv;
}

static int
prefork_req(struct spclient *spc, sigset_t *omask, void **resp)
{
        struct rsp_hdr rhdr;
        struct respwait rw;
        struct iovec iov[1];
        int rv;

        rhdr.rsp_len = sizeof(rhdr);
        rhdr.rsp_class = RUMPSP_REQ;
        rhdr.rsp_type = RUMPSP_PREFORK;
        rhdr.rsp_error = 0;

        IOVPUT(iov[0], rhdr);

        do {
                putwait(spc, &rw, &rhdr);
                rv = send_with_recon(spc, iov, __arraycount(iov));
                if (rv != 0) {
                        unputwait(spc, &rw);
                        continue;
                }

                rv = cliwaitresp(spc, &rw, omask, false);
                if (rv == ENOTCONN)
                        rv = EAGAIN;
        } while (rv == EAGAIN);

        *resp = rw.rw_data;
        return rv;
}

/*
 * prevent response code from deadlocking with reconnect code
 */
static int
resp_sendlock(struct spclient *spc)
{
        int rv = 0;

        pthread_mutex_lock(&spc->spc_mtx);
        while (spc->spc_ostatus != SPCSTATUS_FREE) {
                if (__predict_false(spc->spc_reconnecting)) {
                        rv = EBUSY;
                        goto out;
                }
                spc->spc_ostatus = SPCSTATUS_WANTED;
                pthread_cond_wait(&spc->spc_cv, &spc->spc_mtx);
        }
        spc->spc_ostatus = SPCSTATUS_BUSY;

 out:
        pthread_mutex_unlock(&spc->spc_mtx);
        return rv;
}

static void
send_copyin_resp(struct spclient *spc, uint64_t reqno, void *data, size_t dlen,
        int wantstr)
{
        struct rsp_hdr rhdr;
        struct iovec iov[2];

        if (wantstr)
                dlen = MIN(dlen, strlen(data)+1);

        rhdr.rsp_len = sizeof(rhdr) + dlen;
        rhdr.rsp_reqno = reqno;
        rhdr.rsp_class = RUMPSP_RESP;
        rhdr.rsp_type = RUMPSP_COPYIN;
        rhdr.rsp_sysnum = 0;

        IOVPUT(iov[0], rhdr);
        IOVPUT_WITHSIZE(iov[1], data, dlen);

        if (resp_sendlock(spc) != 0)
                return;
        (void)SENDIOV(spc, iov);
        sendunlock(spc);
}

static void
send_anonmmap_resp(struct spclient *spc, uint64_t reqno, void *addr)
{
        struct rsp_hdr rhdr;
        struct iovec iov[2];

        rhdr.rsp_len = sizeof(rhdr) + sizeof(addr);
        rhdr.rsp_reqno = reqno;
        rhdr.rsp_class = RUMPSP_RESP;
        rhdr.rsp_type = RUMPSP_ANONMMAP;
        rhdr.rsp_sysnum = 0;

        IOVPUT(iov[0], rhdr);
        IOVPUT(iov[1], addr);

        if (resp_sendlock(spc) != 0)
                return;
        (void)SENDIOV(spc, iov);
        sendunlock(spc);
}

int
rumpclient_syscall(int sysnum, const void *data, size_t dlen,
        register_t *retval)
{
        struct rsp_sysresp *resp;
        sigset_t omask;
        void *rdata;
        int rv;

        pthread_sigmask(SIG_SETMASK, &fullset, &omask);

        DPRINTF(("rumpsp syscall_req: syscall %d with %p/%zu\n",
            sysnum, data, dlen));

        rv = syscall_req(&clispc, &omask, sysnum, data, dlen, &rdata);
        if (rv)
                goto out;

        resp = rdata;
        DPRINTF(("rumpsp syscall_resp: syscall %d error %d, rv: %"
            PRIxREGISTER"/%"PRIxREGISTER"\n",
            sysnum, rv, resp->rsys_retval[0], resp->rsys_retval[1]));

        memcpy(retval, &resp->rsys_retval, sizeof(resp->rsys_retval));
        rv = resp->rsys_error;
        free(rdata);

 out:
        pthread_sigmask(SIG_SETMASK, &omask, NULL);
        return rv;
}

static void
handlereq(struct spclient *spc)
{
        struct rsp_copydata *copydata;
        struct rsp_hdr *rhdr = &spc->spc_hdr;
        void *mapaddr;
        size_t maplen;
        int reqtype = spc->spc_hdr.rsp_type;
        int sig;

        switch (reqtype) {
        case RUMPSP_COPYIN:
        case RUMPSP_COPYINSTR:
                /*LINTED*/
                copydata = (struct rsp_copydata *)spc->spc_buf;
                DPRINTF(("rump_sp handlereq: copyin request: %p/%zu\n",
                    copydata->rcp_addr, copydata->rcp_len));
                send_copyin_resp(spc, spc->spc_hdr.rsp_reqno,
                    copydata->rcp_addr, copydata->rcp_len,
                    reqtype == RUMPSP_COPYINSTR);
                break;
        case RUMPSP_COPYOUT:
        case RUMPSP_COPYOUTSTR:
                /*LINTED*/
                copydata = (struct rsp_copydata *)spc->spc_buf;
                DPRINTF(("rump_sp handlereq: copyout request: %p/%zu\n",
                    copydata->rcp_addr, copydata->rcp_len));
                /*LINTED*/
                memcpy(copydata->rcp_addr, copydata->rcp_data,
                    copydata->rcp_len);
                break;
        case RUMPSP_ANONMMAP:
                /*LINTED*/
                maplen = *(size_t *)spc->spc_buf;
                mapaddr = mmap(NULL, maplen, PROT_READ|PROT_WRITE,
                    MAP_ANON|MAP_PRIVATE, -1, 0);
                if (mapaddr == MAP_FAILED)
                        mapaddr = NULL;
                DPRINTF(("rump_sp handlereq: anonmmap: %p\n", mapaddr));
                send_anonmmap_resp(spc, spc->spc_hdr.rsp_reqno, mapaddr);
                break;
        case RUMPSP_RAISE:
                sig = rumpuser__sig_rump2host(rhdr->rsp_signo);
                DPRINTF(("rump_sp handlereq: raise sig %d\n", sig));
                raise(sig);
                /*
                 * We most likely have signals blocked, but the signal
                 * will be handled soon enough when we return.
                 */
                break;
        default:
                printf("PANIC: INVALID TYPE %d\n", reqtype);
                abort();
                break;
        }

        spcfreebuf(spc);
}

static unsigned ptab_idx;
static struct sockaddr *serv_sa;

/* dup until we get a "good" fd which does not collide with stdio */
static int
dupgood(int myfd, int mustchange)
{
        int ofds[4];
        int sverrno;
        unsigned int i;

        for (i = 0; (myfd <= 2 || mustchange) && myfd != -1; i++) {
                assert(i < __arraycount(ofds));
                ofds[i] = myfd;
                myfd = host_dup(myfd);
                if (mustchange) {
                        i--; /* prevent closing old fd */
                        mustchange = 0;
                }
        }

        sverrno = 0;
        if (myfd == -1 && i > 0)
                sverrno = errno;

        while (i-- > 0) {
                host_close(ofds[i]);
        }

        if (sverrno)
                errno = sverrno;

        return myfd;
}

#if defined(USE_KQUEUE)

static int
makeholyfd(void)
{
        struct kevent kev[NSIG+1];
        int i, fd;

        /* setup kqueue, we want all signals and the fd */
        if ((fd = dupgood(host_kqueue(), 0)) == -1) {
                ERRLOG(("rump_sp: cannot setup kqueue"));
                return -1;
        }

        for (i = 0; i < NSIG; i++) {
                EV_SET(&kev[i], i+1, EVFILT_SIGNAL, EV_ADD|EV_ENABLE, 0, 0, 0);
        }
        EV_SET(&kev[NSIG], clispc.spc_fd,
            EVFILT_READ, EV_ADD|EV_ENABLE, 0, 0, 0);
        if (host_kevent(fd, kev, NSIG+1, NULL, 0, NULL) == -1) {
                ERRLOG(("rump_sp: kevent() failed"));
                host_close(fd);
                return -1;
        }

        return fd;
}

#elif defined(USE_SIGNALFD) /* !USE_KQUEUE */

static int
makeholyfd(void)
{

        return host_signalfd(-1, &fullset, 0);
}

#else /* !USE_KQUEUE && !USE_SIGNALFD */

static int
makeholyfd(void)
{

        return -1;
}

#endif

static int
doconnect(void)
{
        struct respwait rw;
        struct rsp_hdr rhdr;
        char banner[MAXBANNER];
        int s, error, flags;
        ssize_t n;

        if (holyfd != -1)
                host_close(holyfd);
        holyfd = -1;
        s = -1;

        if (clispc.spc_fd != -1)
                host_close(clispc.spc_fd);
        clispc.spc_fd = -1;

        /*
         * for reconnect, gate everyone out of the receiver code
         */
        putwait_locked(&clispc, &rw, &rhdr);

        pthread_mutex_lock(&clispc.spc_mtx);
        clispc.spc_reconnecting = 1;
        pthread_cond_broadcast(&clispc.spc_cv);
        clispc.spc_generation++;
        while (clispc.spc_istatus != SPCSTATUS_FREE) {
                clispc.spc_istatus = SPCSTATUS_WANTED;
                pthread_cond_wait(&rw.rw_cv, &clispc.spc_mtx);
        }
        kickall(&clispc);

        /*
         * we can release it already since we hold the
         * send lock during reconnect
         * XXX: assert it
         */
        clispc.spc_istatus = SPCSTATUS_FREE;
        pthread_mutex_unlock(&clispc.spc_mtx);
        unputwait_locked(&clispc, &rw);

        free(clispc.spc_buf);
        clispc.spc_off = 0;

        s = dupgood(host_socket(parsetab[ptab_idx].domain, SOCK_STREAM, 0), 0);
        if (s == -1)
                return -1;

        while (host_connect(s, serv_sa, parsetab[ptab_idx].slen) == -1) {
                if (errno == EINTR)
                        continue;
                ERRLOG(("rump_sp: client connect failed: %s\n",
                    strerror(errno)));
                return -1;
        }

        if ((error = parsetab[ptab_idx].connhook(s)) != 0) {
                ERRLOG(("rump_sp: connect hook failed\n"));
                return -1;
        }

        if ((n = host_read(s, banner, sizeof(banner)-1)) <= 0) {
                ERRLOG(("rump_sp: failed to read banner\n"));
                return -1;
        }

        if (banner[n-1] != '\n') {
                ERRLOG(("rump_sp: invalid banner\n"));
                return -1;
        }
        banner[n] = '\0';
        /* XXX parse the banner some day */

        flags = host_fcntl(s, F_GETFL, 0);
        if (host_fcntl(s, F_SETFL, flags | O_NONBLOCK) == -1) {
                ERRLOG(("rump_sp: socket fd NONBLOCK: %s\n", strerror(errno)));
                return -1;
        }
        clispc.spc_fd = s;
        clispc.spc_state = SPCSTATE_RUNNING;
        clispc.spc_reconnecting = 0;
        holyfd = makeholyfd();

        return 0;
}

static int
doinit(void)
{

        TAILQ_INIT(&clispc.spc_respwait);
        pthread_mutex_init(&clispc.spc_mtx, NULL);
        pthread_cond_init(&clispc.spc_cv, NULL);

        return 0;
}

#ifdef RTLD_NEXT
void *rumpclient__dlsym(void *, const char *);
void *
rumpclient__dlsym(void *handle, const char *symbol)
{

        return dlsym(handle, symbol);
}
void *rumphijack_dlsym(void *, const char *)
    __attribute__((__weak__, alias("rumpclient__dlsym")));
#endif

static pid_t init_done = 0;

int
rumpclient_init(void)
{
        char *p;
        int error;
        int rv = -1;
        int hstype;
        pid_t mypid;

        /*
         * Make sure we're not riding the context of a previous
         * host fork.  Note: it's *possible* that after n>1 forks
         * we have the same pid as one of our exited parents, but
         * I'm pretty sure there are 0 practical implications, since
         * it means generations would have to skip rumpclient init.
         */
        if (init_done == (mypid = getpid()))
                return 0;

#ifdef USE_KQUEUE
        /* kq does not traverse fork() */
        holyfd = -1;
#endif
        init_done = mypid;

        sigfillset(&fullset);

        /*
         * sag mir, wo die symbols sind.  zogen fort, der krieg beginnt.
         * wann wird man je verstehen?  wann wird man je verstehen?
         */
#ifdef RTLD_NEXT
#define FINDSYM2(_name_,_syscall_)                                      \
        if ((host_##_name_ = rumphijack_dlsym(RTLD_NEXT,                \
            #_syscall_)) == NULL) {                                     \
                if (rumphijack_dlsym == rumpclient__dlsym)              \
                        host_##_name_ = _name_; /* static fallback */   \
                if (host_##_name_ == NULL) {                            \
                        fprintf(stderr,"cannot find %s: %s", #_syscall_,\
                            dlerror());                                 \
                        exit(1);                                        \
                }                                                       \
        }
#else
#define FINDSYM2(_name_,_syscall)                                       \
        host_##_name_ = _name_;
#endif
#define FINDSYM(_name_) FINDSYM2(_name_,_name_)
#ifdef __NetBSD__
        FINDSYM2(socket,__socket30)
#else
        FINDSYM(socket)
#endif

        FINDSYM(close)
        FINDSYM(connect)
        FINDSYM(fcntl)
        FINDSYM(poll)
        FINDSYM(read)
        FINDSYM(sendmsg)
        FINDSYM(setsockopt)
        FINDSYM(dup)
        FINDSYM(execve)

#ifdef USE_KQUEUE
        FINDSYM(kqueue)
#ifdef __NetBSD__
#if !__NetBSD_Prereq__(5,99,7)
        FINDSYM(kevent)
#elif !__NetBSD_Prereq__(10,99,7)
        FINDSYM2(kevent,_sys___kevent50)
#else
        FINDSYM2(kevent,_sys___kevent100)
#endif
#else
        FINDSYM(kevent)
#endif
#endif /* USE_KQUEUE */

#ifdef USE_SIGNALFD
        FINDSYM(signalfd)
#endif

#undef  FINDSYM
#undef  FINDSY2

        if ((p = getenv("RUMP__PARSEDSERVER")) == NULL) {
                if ((p = getenv("RUMP_SERVER")) == NULL) {
                        fprintf(stderr, "error: RUMP_SERVER not set\n");
                        errno = ENOENT;
                        goto out;
                }
        }

        if ((error = parseurl(p, &serv_sa, &ptab_idx, 0)) != 0) {
                errno = error;
                goto out;
        }

        if (doinit() == -1)
                goto out;

        if ((p = getenv("RUMPCLIENT__EXECFD")) != NULL) {
                sscanf(p, "%d,%d", &clispc.spc_fd, &holyfd);
                unsetenv("RUMPCLIENT__EXECFD");
                hstype = HANDSHAKE_EXEC;
        } else {
                if (doconnect() == -1)
                        goto out;
                hstype = HANDSHAKE_GUEST;
        }

        error = handshake_req(&clispc, hstype, NULL, 0, false);
        if (error) {
                pthread_mutex_destroy(&clispc.spc_mtx);
                pthread_cond_destroy(&clispc.spc_cv);
                if (clispc.spc_fd != -1)
                        host_close(clispc.spc_fd);
                errno = error;
                goto out;
        }
        rv = 0;

 out:
        if (rv == -1)
                init_done = 0;
        return rv;
}

struct rumpclient_fork {
        uint32_t fork_auth[AUTHLEN];
        struct spclient fork_spc;
        int fork_holyfd;
};

struct rumpclient_fork *
rumpclient_prefork(void)
{
        struct rumpclient_fork *rpf;
        sigset_t omask;
        void *resp;
        int rv;

        pthread_sigmask(SIG_SETMASK, &fullset, &omask);
        rpf = malloc(sizeof(*rpf));
        if (rpf == NULL)
                goto out;

        if ((rv = prefork_req(&clispc, &omask, &resp)) != 0) {
                free(rpf);
                errno = rv;
                rpf = NULL;
                goto out;
        }

        memcpy(rpf->fork_auth, resp, sizeof(rpf->fork_auth));
        free(resp);

        rpf->fork_spc = clispc;
        rpf->fork_holyfd = holyfd;

 out:
        pthread_sigmask(SIG_SETMASK, &omask, NULL);
        return rpf;
}

int
rumpclient_fork_init(struct rumpclient_fork *rpf)
{
        int error;
        int osock;

        osock = clispc.spc_fd;
        memset(&clispc, 0, sizeof(clispc));
        clispc.spc_fd = osock;

#ifdef USE_KQUEUE
        holyfd = -1; /* kqueue descriptor is not copied over fork() */
#else
        if (holyfd != -1) {
                host_close(holyfd);
                holyfd = -1;
        }
#endif

        if (doinit() == -1)
                return -1;
        if (doconnect() == -1)
                return -1;

        error = handshake_req(&clispc, HANDSHAKE_FORK, rpf->fork_auth,
            0, false);
        if (error) {
                pthread_mutex_destroy(&clispc.spc_mtx);
                pthread_cond_destroy(&clispc.spc_cv);
                errno = error;
                return -1;
        }

        return 0;
}

/*ARGSUSED*/
void
rumpclient_fork_cancel(struct rumpclient_fork *rpf)
{

        /* EUNIMPL */
}

void
rumpclient_fork_vparent(struct rumpclient_fork *rpf)
{

        clispc = rpf->fork_spc;
        holyfd = rpf->fork_holyfd;
}

void
rumpclient_setconnretry(time_t timeout)
{

        if (timeout < RUMPCLIENT_RETRYCONN_DIE)
                return; /* gigo */

        retrytimo = timeout;
}

int
rumpclient__closenotify(int *fdp, enum rumpclient_closevariant variant)
{
        int fd = *fdp;
        int untilfd;
        int newfd;

        switch (variant) {
        case RUMPCLIENT_CLOSE_FCLOSEM:
                untilfd = MAX(clispc.spc_fd, holyfd);
                for (; fd <= untilfd; fd++) {
                        if (fd == clispc.spc_fd || fd == holyfd)
                                continue;
                        (void)host_close(fd);
                }
                *fdp = fd;
                break;

        case RUMPCLIENT_CLOSE_CLOSE:
        case RUMPCLIENT_CLOSE_DUP2:
                if (fd == clispc.spc_fd) {
                        newfd = dupgood(clispc.spc_fd, 1);
                        if (newfd == -1)
                                return -1;

#ifdef USE_KQUEUE
                        {
                        struct kevent kev[2];

                        /*
                         * now, we have a new socket number, so change
                         * the file descriptor that kqueue is
                         * monitoring.  remove old and add new.
                         */
                        EV_SET(&kev[0], clispc.spc_fd,
                            EVFILT_READ, EV_DELETE, 0, 0, 0);
                        EV_SET(&kev[1], newfd,
                            EVFILT_READ, EV_ADD|EV_ENABLE, 0, 0, 0);
                        if (host_kevent(holyfd, kev, 2, NULL, 0, NULL) == -1) {
                                int sverrno = errno;
                                host_close(newfd);
                                errno = sverrno;
                                return -1;
                        }}
#endif /* !USE_KQUEUE */
                        clispc.spc_fd = newfd;
                }
                if (holyfd != -1 && fd == holyfd) {
                        newfd = dupgood(holyfd, 1);
                        if (newfd == -1)
                                return -1;
                        holyfd = newfd;
                }
                break;
        }

        return 0;
}

pid_t
rumpclient_fork(void)
{

        return rumpclient__dofork(fork);
}

/*
 * Process is about to exec.  Save info about our existing connection
 * in the env.  rumpclient will check for this info in init().
 * This is mostly for the benefit of rumphijack, but regular applications
 * may use it as well.
 */
int
rumpclient_exec(const char *path, char *const argv[], char *const envp[])
{
        char buf[4096];
        char **newenv;
        char *envstr, *envstr2;
        size_t nelem;
        int rv, sverrno;

        snprintf(buf, sizeof(buf), "RUMPCLIENT__EXECFD=%d,%d",
            clispc.spc_fd, holyfd);
        envstr = malloc(strlen(buf)+1);
        if (envstr == NULL) {
                return ENOMEM;
        }
        strcpy(envstr, buf);

        /* do we have a fully parsed url we want to forward in the env? */
        if (*parsedurl != '\0') {
                snprintf(buf, sizeof(buf),
                    "RUMP__PARSEDSERVER=%s", parsedurl);
                envstr2 = malloc(strlen(buf)+1);
                if (envstr2 == NULL) {
                        free(envstr);
                        return ENOMEM;
                }
                strcpy(envstr2, buf);
        } else {
                envstr2 = NULL;
        }

        for (nelem = 0; envp && envp[nelem]; nelem++)
                continue;

        newenv = malloc(sizeof(*newenv) * (nelem+3));
        if (newenv == NULL) {
                free(envstr2);
                free(envstr);
                return ENOMEM;
        }
        memcpy(&newenv[0], envp, nelem*sizeof(*envp));

        newenv[nelem] = envstr;
        newenv[nelem+1] = envstr2;
        newenv[nelem+2] = NULL;

        rv = host_execve(path, argv, newenv);

        _DIAGASSERT(rv != 0);
        sverrno = errno;
        free(envstr2);
        free(envstr);
        free(newenv);
        errno = sverrno;
        return rv;
}

/*
 * daemon() is handwritten for the benefit of platforms which
 * do not support daemon().
 */
int
rumpclient_daemon(int nochdir, int noclose)
{
        struct rumpclient_fork *rf;
        int sverrno;

        if ((rf = rumpclient_prefork()) == NULL)
                return -1;

        switch (fork()) {
        case 0:
                break;
        case -1:
                goto daemonerr;
        default:
                _exit(0);
        }

        if (setsid() == -1)
                goto daemonerr;
        if (!nochdir && chdir("/") == -1)
                goto daemonerr;
        if (!noclose) {
                int fd = open("/dev/null", O_RDWR);
                dup2(fd, 0);
                dup2(fd, 1);
                dup2(fd, 2);
                if (fd > 2)
                        close(fd);
        }

        /* note: fork is either completed or cancelled by the call */
        if (rumpclient_fork_init(rf) == -1)
                return -1;

        return 0;

 daemonerr:
        sverrno = errno;
        rumpclient_fork_cancel(rf);
        errno = sverrno;
        return -1;
}