Files
xen/tools/libxl/libxl_exec.c
T
Ian Jackson 47cb2273b2 libxl: Deprecate synchronous waiting for the device model
libxl__wait_for_device_model blocks, with the ctx lock held, waiting
for a response from the device model.  If the dm doesn't respond
quickly (for example, because it has crashed), this may block the
whole process.  Explain this in a comment, rename the function to
libxl__wait_for_device_model_deprecated, and explain what to use
instead.

libxl__wait_for_offspring is the core implementation for the above.
Its name leads people to think it might be generally useful for
waiting for children, which is far from the case.  It only waits for
xenstore.  Also it has the problems described above.  Explain this,
rename it to libxl__xenstore_child_wait_deprecated, and explain what
to use instead.

Signed-off-by: Ian Jackson <Ian.Jackson@eu.citrix.com>
Acked-by: Ian Campbell <ian.campbell@citrix.com>
2013-11-12 17:27:09 +00:00

475 lines
15 KiB
C

/*
* Copyright (C) 2009 Citrix Ltd.
* Author Vincent Hanquez <vincent.hanquez@eu.citrix.com>
* Author Stefano Stabellini <stefano.stabellini@eu.citrix.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; version 2.1 only. with the special
* exception on linking described in file LICENSE.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*/
#include "libxl_osdeps.h" /* must come before any other headers */
#include "libxl_internal.h"
static void check_open_fds(const char *what)
{
const char *env_debug;
int debug;
int i, flags, badness = 0;
env_debug = getenv("_LIBXL_DEBUG_EXEC_FDS");
if (!env_debug) return;
debug = strtol(env_debug, (char **) NULL, 10);
if (debug <= 0) return;
for (i = 4; i < 256; i++) {
#ifdef __linux__
ssize_t len;
char path[PATH_MAX];
char linkpath[PATH_MAX+1];
#endif
flags = fcntl(i, F_GETFD);
if ( flags == -1 ) {
if ( errno != EBADF )
fprintf(stderr, "libxl: execing %s: fd %d flags returned %s (%d)\n",
what, i, strerror(errno), errno);
continue;
}
if ( flags & FD_CLOEXEC )
continue;
badness++;
#ifdef __linux__
snprintf(path, PATH_MAX, "/proc/%d/fd/%d", getpid(), i);
len = readlink(path, linkpath, PATH_MAX);
if (len > 0) {
linkpath[len] = '\0';
fprintf(stderr, "libxl: execing %s: fd %d is open to %s with flags %#x\n",
what, i, linkpath, flags);
} else
#endif
fprintf(stderr, "libxl: execing %s: fd %d is open with flags %#x\n",
what, i, flags);
}
if (debug < 2) return;
if (badness) abort();
}
void libxl__exec(libxl__gc *gc, int stdinfd, int stdoutfd, int stderrfd,
const char *arg0, char *const args[], char *const env[])
/* call this in the child */
{
if (stdinfd != -1)
dup2(stdinfd, STDIN_FILENO);
if (stdoutfd != -1)
dup2(stdoutfd, STDOUT_FILENO);
if (stderrfd != -1)
dup2(stderrfd, STDERR_FILENO);
if (stdinfd != -1)
close(stdinfd);
if (stdoutfd != -1 && stdoutfd != stdinfd)
close(stdoutfd);
if (stderrfd != -1 && stderrfd != stdinfd && stderrfd != stdoutfd)
close(stderrfd);
check_open_fds(arg0);
signal(SIGPIPE, SIG_DFL);
/* in case our caller set it to IGN. subprocesses are entitled
* to assume they got DFL. */
if (env != NULL) {
for (int i = 0; env[i] != NULL && env[i+1] != NULL; i += 2) {
if (setenv(env[i], env[i+1], 1) < 0) {
LOGEV(ERROR, errno, "setting env vars (%s = %s)",
env[i], env[i+1]);
goto out;
}
}
}
execvp(arg0, args);
out:
fprintf(stderr, "libxl: cannot execute %s: %s\n", arg0, strerror(errno));
_exit(-1);
}
void libxl_report_child_exitstatus(libxl_ctx *ctx,
xentoollog_level level,
const char *what, pid_t pid, int status)
{
if (WIFEXITED(status)) {
int st = WEXITSTATUS(status);
if (st)
LIBXL__LOG(ctx, level, "%s [%ld] exited"
" with error status %d", what, (unsigned long)pid, st);
else
LIBXL__LOG(ctx, level, "%s [%ld] unexpectedly"
" exited status zero", what, (unsigned long)pid);
} else if (WIFSIGNALED(status)) {
int sig = WTERMSIG(status);
const char *str = strsignal(sig);
const char *coredump = WCOREDUMP(status) ? " (core dumped)" : "";
if (str)
LIBXL__LOG(ctx, level, "%s [%ld] died due to"
" fatal signal %s%s", what, (unsigned long)pid,
str, coredump);
else
LIBXL__LOG(ctx, level, "%s [%ld] died due to unknown"
" fatal signal number %d%s", what, (unsigned long)pid,
sig, coredump);
} else {
LIBXL__LOG(ctx, level, "%s [%ld] gave unknown"
" wait status 0x%x", what, (unsigned long)pid, status);
}
}
int libxl__spawn_record_pid(libxl__gc *gc, libxl__spawn_state *spawn, pid_t pid)
{
int r, rc;
rc = libxl__ev_child_xenstore_reopen(gc, spawn->what);
if (rc) goto out;
r = libxl__xs_write(gc, XBT_NULL, spawn->pidpath, "%d", pid);
if (r) {
LOGE(ERROR,
"write %s = %d: xenstore write failed", spawn->pidpath, pid);
rc = ERROR_FAIL; goto out;
}
rc = 0;
out:
return rc ? SIGTERM : 0;
}
int libxl__xenstore_child_wait_deprecated(libxl__gc *gc,
uint32_t domid,
uint32_t timeout, char *what,
char *path, char *state,
libxl__spawn_starting *spawning,
int (*check_callback)(libxl__gc *gc,
uint32_t domid,
const char *state,
void *userdata),
void *check_callback_userdata)
{
libxl_ctx *ctx = libxl__gc_owner(gc);
char *p;
unsigned int len;
int rc = 0;
struct xs_handle *xsh;
int nfds;
fd_set rfds;
struct timeval tv;
unsigned int num;
char **l = NULL;
xsh = xs_daemon_open();
if (xsh == NULL) {
LIBXL__LOG(ctx, LIBXL__LOG_ERROR, "Unable to open xenstore connection");
goto err;
}
xs_watch(xsh, path, path);
tv.tv_sec = timeout;
tv.tv_usec = 0;
nfds = xs_fileno(xsh) + 1;
assert(!spawning);
while (rc > 0 || (!rc && tv.tv_sec > 0)) {
p = xs_read(xsh, XBT_NULL, path, &len);
if ( NULL == p )
goto again;
if ( NULL != state && strcmp(p, state) )
goto again;
if ( NULL != check_callback ) {
rc = (*check_callback)(gc, domid, p, check_callback_userdata);
if ( rc > 0 )
goto again;
}
free(p);
xs_unwatch(xsh, path, path);
xs_daemon_close(xsh);
return rc;
again:
free(p);
FD_ZERO(&rfds);
FD_SET(xs_fileno(xsh), &rfds);
rc = select(nfds, &rfds, NULL, NULL, &tv);
if (rc > 0) {
if (FD_ISSET(xs_fileno(xsh), &rfds)) {
l = xs_read_watch(xsh, &num);
if (l != NULL)
free(l);
else
goto again;
}
}
}
LIBXL__LOG(ctx, LIBXL__LOG_ERROR, "%s not ready", what);
xs_unwatch(xsh, path, path);
xs_daemon_close(xsh);
err:
return -1;
}
/*----- spawn implementation -----*/
/*
* Full set of possible states of a libxl__spawn_state and its _detachable:
*
* detaching failed mid timeout xswatch
* - Undefined undef undef - undef undef
* - Idle any any Idle Idle Idle
* - Attached OK 0 0 Active Active Active
* - Attached Failed 0 1 Active Idle Idle
* - Detaching 1 maybe Active Idle Idle
* - Partial any any Idle Active/Idle Active/Idle
*
* When in states Detaching or Attached Failed, the middle process has
* been sent a SIGKILL.
*
* The difference between Attached OK and Attached Failed is not
* directly visible to callers - callers see these two the same,
* although of course Attached OK will hopefully eventually result in
* a call to detached_cb, whereas Attached Failed will end up
* in a call to failure_cb.
*/
/* Event callbacks. */
static void spawn_watch_event(libxl__egc *egc, libxl__ev_xswatch *xsw,
const char *watch_path, const char *event_path);
static void spawn_timeout(libxl__egc *egc, libxl__ev_time *ev,
const struct timeval *requested_abs);
static void spawn_middle_death(libxl__egc *egc, libxl__ev_child *childw,
pid_t pid, int status);
/* Precondition: Partial. Results: Idle. */
static void spawn_cleanup(libxl__gc *gc, libxl__spawn_state *ss);
/* Precondition: Attached or Detaching; caller has logged failure reason.
* Results: Detaching, or Attached Failed */
static void spawn_fail(libxl__egc *egc, libxl__spawn_state *ss);
void libxl__spawn_init(libxl__spawn_state *ss)
{
libxl__ev_child_init(&ss->mid);
libxl__ev_time_init(&ss->timeout);
libxl__ev_xswatch_init(&ss->xswatch);
}
int libxl__spawn_spawn(libxl__egc *egc, libxl__spawn_state *ss)
{
STATE_AO_GC(ss->ao);
int r;
pid_t child;
int status, rc;
libxl__spawn_init(ss);
ss->failed = ss->detaching = 0;
rc = libxl__ev_time_register_rel(gc, &ss->timeout,
spawn_timeout, ss->timeout_ms);
if (rc) goto out_err;
rc = libxl__ev_xswatch_register(gc, &ss->xswatch,
spawn_watch_event, ss->xspath);
if (rc) goto out_err;
pid_t middle = libxl__ev_child_fork(gc, &ss->mid, spawn_middle_death);
if (middle ==-1) { rc = ERROR_FAIL; goto out_err; }
if (middle) {
/* parent */
return 1;
}
/* we are now the middle process */
pid_t (*fork_replacement)(void*) =
CTX->childproc_hooks
? CTX->childproc_hooks->fork_replacement
: 0;
child =
fork_replacement
? fork_replacement(CTX->childproc_user)
: fork();
if (child == -1)
exit(255);
if (!child) {
return 0; /* caller runs child code */
}
int failsig = ss->midproc_cb(gc, ss, child);
if (failsig) {
kill(child, failsig);
_exit(127);
}
for (;;) {
pid_t got = waitpid(child, &status, 0);
if (got == -1) {
assert(errno == EINTR);
continue;
}
assert(got == child);
break;
}
r = (WIFEXITED(status) && WEXITSTATUS(status) <= 127 ? WEXITSTATUS(status) :
WIFSIGNALED(status) && WTERMSIG(status) < 127 ? WTERMSIG(status)+128 :
-1);
_exit(r);
out_err:
spawn_cleanup(gc, ss);
return rc;
}
static void spawn_cleanup(libxl__gc *gc, libxl__spawn_state *ss)
{
assert(!libxl__ev_child_inuse(&ss->mid));
libxl__ev_time_deregister(gc, &ss->timeout);
libxl__ev_xswatch_deregister(gc, &ss->xswatch);
}
static void spawn_detach(libxl__gc *gc, libxl__spawn_state *ss)
/* Precondition: Attached or Detaching, but caller must have just set
* at least one of detaching or failed.
* Results: Detaching or Attached Failed */
{
int r;
assert(libxl__ev_child_inuse(&ss->mid));
libxl__ev_time_deregister(gc, &ss->timeout);
libxl__ev_xswatch_deregister(gc, &ss->xswatch);
pid_t child = ss->mid.pid;
r = kill(child, SIGKILL);
if (r && errno != ESRCH)
LOGE(WARN, "%s: failed to kill intermediate child (pid=%lu)",
ss->what, (unsigned long)child);
}
void libxl__spawn_initiate_detach(libxl__gc *gc, libxl__spawn_state *ss)
{
ss->detaching = 1;
spawn_detach(gc, ss);
}
static void spawn_fail(libxl__egc *egc, libxl__spawn_state *ss)
/* Caller must have logged. Must be last thing in calling function,
* as it may make the callback. Precondition: Attached or Detaching. */
{
EGC_GC;
ss->failed = 1;
spawn_detach(gc, ss);
}
static void spawn_timeout(libxl__egc *egc, libxl__ev_time *ev,
const struct timeval *requested_abs)
{
/* Before event, was Attached. */
EGC_GC;
libxl__spawn_state *ss = CONTAINER_OF(ev, *ss, timeout);
LOG(ERROR, "%s: startup timed out", ss->what);
spawn_fail(egc, ss); /* must be last */
}
static void spawn_watch_event(libxl__egc *egc, libxl__ev_xswatch *xsw,
const char *watch_path, const char *event_path)
{
/* On entry, is Attached. */
EGC_GC;
libxl__spawn_state *ss = CONTAINER_OF(xsw, *ss, xswatch);
char *p = libxl__xs_read(gc, 0, ss->xspath);
if (!p && errno != ENOENT) {
LOG(ERROR, "%s: xenstore read of %s failed", ss->what, ss->xspath);
spawn_fail(egc, ss); /* must be last */
return;
}
ss->confirm_cb(egc, ss, p); /* must be last */
}
static void spawn_middle_death(libxl__egc *egc, libxl__ev_child *childw,
pid_t pid, int status)
/* On entry, is Attached or Detaching */
{
EGC_GC;
libxl__spawn_state *ss = CONTAINER_OF(childw, *ss, mid);
if ((ss->failed || ss->detaching) &&
((WIFEXITED(status) && WEXITSTATUS(status)==0) ||
(WIFSIGNALED(status) && WTERMSIG(status)==SIGKILL))) {
/* as expected */
} else if (!WIFEXITED(status)) {
int loglevel = ss->detaching ? XTL_WARN : XTL_ERROR;
const char *what =
GCSPRINTF("%s intermediate process (startup monitor)", ss->what);
libxl_report_child_exitstatus(CTX, loglevel, what, pid, status);
ss->failed = 1;
} else {
if (!status)
LOG(ERROR, "%s [%ld]: unexpectedly exited with exit status 0,"
" when we were waiting for it to confirm startup",
ss->what, (unsigned long)pid);
else if (status <= 127)
LOG(ERROR, "%s [%ld]: failed startup with non-zero exit status %d",
ss->what, (unsigned long)pid, status);
else if (status < 255) {
int sig = status - 128;
const char *str = strsignal(sig);
if (str)
LOG(ERROR, "%s [%ld]: died during startup due to fatal"
" signal %s", ss->what, (unsigned long)pid, str);
else
LOG(ERROR, "%s [%ld]: died during startup due to unknown fatal"
" signal number %d", ss->what, (unsigned long)pid, sig);
}
ss->failed = 1;
}
spawn_cleanup(gc, ss);
if (ss->failed && !ss->detaching) {
ss->failure_cb(egc, ss); /* must be last */
return;
}
if (ss->failed && ss->detaching)
LOG(WARN,"%s underlying machinery seemed to fail,"
" but its function seems to have been successful", ss->what);
assert(ss->detaching);
ss->detached_cb(egc, ss);
}
/*
* Local variables:
* mode: C
* c-basic-offset: 4
* indent-tabs-mode: nil
* End:
*/