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