NetBSD/lib/librumpclient/rumpclient.c

1264 lines
27 KiB
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;
}