aboutsummaryrefslogtreecommitdiffstats
path: root/tools/libxl/libxl_fork.c
diff options
context:
space:
mode:
Diffstat (limited to 'tools/libxl/libxl_fork.c')
-rw-r--r--tools/libxl/libxl_fork.c255
1 files changed, 255 insertions, 0 deletions
diff --git a/tools/libxl/libxl_fork.c b/tools/libxl/libxl_fork.c
index dce88ad54b..35c8bdd7f3 100644
--- a/tools/libxl/libxl_fork.c
+++ b/tools/libxl/libxl_fork.c
@@ -46,6 +46,12 @@ static int atfork_registered;
static LIBXL_LIST_HEAD(, libxl__carefd) carefds =
LIBXL_LIST_HEAD_INITIALIZER(carefds);
+/* non-null iff installed, protected by no_forking */
+static libxl_ctx *sigchld_owner;
+static struct sigaction sigchld_saved_action;
+
+static void sigchld_removehandler_core(void);
+
static void atfork_lock(void)
{
int r = pthread_mutex_lock(&no_forking);
@@ -107,6 +113,7 @@ void libxl_postfork_child_noexec(libxl_ctx *ctx)
int r;
atfork_lock();
+
LIBXL_LIST_FOREACH_SAFE(cf, &carefds, entry, cf_tmp) {
if (cf->fd >= 0) {
r = close(cf->fd);
@@ -118,6 +125,10 @@ void libxl_postfork_child_noexec(libxl_ctx *ctx)
free(cf);
}
LIBXL_LIST_INIT(&carefds);
+
+ if (sigchld_owner)
+ sigchld_removehandler_core();
+
atfork_unlock();
}
@@ -141,6 +152,250 @@ int libxl__carefd_fd(const libxl__carefd *cf)
}
/*
+ * Actual child process handling
+ */
+
+static void sigchld_handler(int signo)
+{
+ int e = libxl__self_pipe_wakeup(sigchld_owner->sigchld_selfpipe[1]);
+ assert(!e); /* errors are probably EBADF, very bad */
+}
+
+static void sigchld_removehandler_core(void)
+{
+ struct sigaction was;
+ int r;
+
+ r = sigaction(SIGCHLD, &sigchld_saved_action, &was);
+ assert(!r);
+ assert(!(was.sa_flags & SA_SIGINFO));
+ assert(was.sa_handler == sigchld_handler);
+ sigchld_owner = 0;
+}
+
+void libxl__sigchld_removehandler(libxl_ctx *ctx) /* non-reentrant */
+{
+ atfork_lock();
+ if (sigchld_owner == ctx)
+ sigchld_removehandler_core();
+ atfork_unlock();
+}
+
+int libxl__sigchld_installhandler(libxl_ctx *ctx) /* non-reentrant */
+{
+ int r, rc;
+
+ if (ctx->sigchld_selfpipe[0] < 0) {
+ r = pipe(ctx->sigchld_selfpipe);
+ if (r) {
+ ctx->sigchld_selfpipe[0] = -1;
+ LIBXL__LOG_ERRNO(ctx, LIBXL__LOG_ERROR,
+ "failed to create sigchld pipe");
+ rc = ERROR_FAIL;
+ goto out;
+ }
+ }
+
+ atfork_lock();
+ if (sigchld_owner != ctx) {
+ struct sigaction ours;
+
+ assert(!sigchld_owner);
+ sigchld_owner = ctx;
+
+ memset(&ours,0,sizeof(ours));
+ ours.sa_handler = sigchld_handler;
+ sigemptyset(&ours.sa_mask);
+ ours.sa_flags = SA_NOCLDSTOP | SA_RESTART;
+ r = sigaction(SIGCHLD, &ours, &sigchld_saved_action);
+ assert(!r);
+
+ assert(((void)"application must negotiate with libxl about SIGCHLD",
+ !(sigchld_saved_action.sa_flags & SA_SIGINFO) &&
+ (sigchld_saved_action.sa_handler == SIG_DFL ||
+ sigchld_saved_action.sa_handler == SIG_IGN)));
+ }
+ atfork_unlock();
+
+ rc = 0;
+ out:
+ return rc;
+}
+
+static int chldmode_ours(libxl_ctx *ctx)
+{
+ return ctx->childproc_hooks->chldowner == libxl_sigchld_owner_libxl;
+}
+
+int libxl__fork_selfpipe_active(libxl_ctx *ctx)
+{
+ /* Returns the fd to read, or -1 */
+ if (!chldmode_ours(ctx))
+ return -1;
+
+ if (LIBXL_LIST_EMPTY(&ctx->children))
+ return -1;
+
+ return ctx->sigchld_selfpipe[0];
+}
+
+static void perhaps_removehandler(libxl_ctx *ctx)
+{
+ if (LIBXL_LIST_EMPTY(&ctx->children) &&
+ ctx->childproc_hooks->chldowner != libxl_sigchld_owner_libxl_always)
+ libxl__sigchld_removehandler(ctx);
+}
+
+static int childproc_reaped(libxl__egc *egc, pid_t pid, int status)
+{
+ EGC_GC;
+ libxl__ev_child *ch;
+
+ LIBXL_LIST_FOREACH(ch, &CTX->children, entry)
+ if (ch->pid == pid)
+ goto found;
+
+ /* not found */
+ return ERROR_UNKNOWN_CHILD;
+
+ found:
+ LIBXL_LIST_REMOVE(ch, entry);
+ ch->pid = -1;
+ ch->callback(egc, ch, pid, status);
+
+ perhaps_removehandler(CTX);
+
+ return 0;
+}
+
+int libxl_childproc_reaped(libxl_ctx *ctx, pid_t pid, int status)
+{
+ EGC_INIT(ctx);
+ CTX_LOCK;
+ int rc = childproc_reaped(egc, pid, status);
+ CTX_UNLOCK;
+ EGC_FREE;
+ return rc;
+}
+
+void libxl__fork_selfpipe_woken(libxl__egc *egc)
+{
+ /* May make callbacks into the application for child processes.
+ * ctx must be locked EXACTLY ONCE */
+ EGC_GC;
+
+ while (chldmode_ours(CTX) /* in case the app changes the mode */) {
+ int status;
+ pid_t pid = waitpid(-1, &status, WNOHANG);
+
+ if (pid == 0) return;
+
+ if (pid == -1) {
+ if (errno == ECHILD) return;
+ if (errno == EINTR) continue;
+ LIBXL__EVENT_DISASTER(egc, "waitpid() failed", errno, 0);
+ return;
+ }
+
+ int rc = childproc_reaped(egc, pid, status);
+
+ if (rc) {
+ if (CTX->childproc_hooks->reaped_callback) {
+ CTX_UNLOCK;
+ rc = CTX->childproc_hooks->reaped_callback
+ (pid, status, CTX->childproc_user);
+ CTX_LOCK;
+ if (rc != 0 && rc != ERROR_UNKNOWN_CHILD) {
+ char disasterbuf[200];
+ snprintf(disasterbuf, sizeof(disasterbuf), " reported by"
+ " libxl_childproc_hooks->reaped_callback"
+ " (for pid=%lu, status=%d; error code %d)",
+ (unsigned long)pid, status, rc);
+ LIBXL__EVENT_DISASTER(egc, disasterbuf, 0, 0);
+ return;
+ }
+ } else {
+ rc = ERROR_UNKNOWN_CHILD;
+ }
+ if (rc)
+ libxl_report_child_exitstatus(CTX, XTL_WARN,
+ "unknown child", (long)pid, status);
+ }
+ }
+}
+
+pid_t libxl__ev_child_fork(libxl__gc *gc, libxl__ev_child *ch,
+ libxl__ev_child_callback *death)
+{
+ CTX_LOCK;
+ int rc;
+
+ if (chldmode_ours(CTX)) {
+ rc = libxl__sigchld_installhandler(CTX);
+ if (rc) goto out;
+ }
+
+ pid_t pid =
+ CTX->childproc_hooks->fork_replacement
+ ? CTX->childproc_hooks->fork_replacement(CTX->childproc_user)
+ : fork();
+ if (pid == -1) {
+ LOGE(ERROR, "fork failed");
+ rc = ERROR_FAIL;
+ goto out;
+ }
+
+ if (!pid) {
+ /* woohoo! */
+ return 0; /* Yes, CTX is left locked in the child. */
+ }
+
+ ch->pid = pid;
+ ch->callback = death;
+ LIBXL_LIST_INSERT_HEAD(&CTX->children, ch, entry);
+ rc = pid;
+
+ out:
+ perhaps_removehandler(CTX);
+ CTX_UNLOCK;
+ return rc;
+}
+
+void libxl_childproc_setmode(libxl_ctx *ctx, const libxl_childproc_hooks *hooks,
+ void *user)
+{
+ GC_INIT(ctx);
+ CTX_LOCK;
+
+ assert(LIBXL_LIST_EMPTY(&CTX->children));
+
+ if (!hooks)
+ hooks = &libxl__childproc_default_hooks;
+
+ ctx->childproc_hooks = hooks;
+ ctx->childproc_user = user;
+
+ switch (ctx->childproc_hooks->chldowner) {
+ case libxl_sigchld_owner_mainloop:
+ case libxl_sigchld_owner_libxl:
+ libxl__sigchld_removehandler(ctx);
+ break;
+ case libxl_sigchld_owner_libxl_always:
+ libxl__sigchld_installhandler(ctx);
+ break;
+ default:
+ abort();
+ }
+
+ CTX_UNLOCK;
+ GC_FREE;
+}
+
+const libxl_childproc_hooks libxl__childproc_default_hooks = {
+ libxl_sigchld_owner_libxl, 0, 0
+};
+
+/*
* Local variables:
* mode: C
* c-basic-offset: 4