/* * Copyright (C) 2009 Citrix Ltd. * Author Vincent Hanquez * Author Stefano Stabellini * * 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__wait_for_offspring(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: */