diff options
Diffstat (limited to 'tools/libxl/libxl_fork.c')
-rw-r--r-- | tools/libxl/libxl_fork.c | 255 |
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 |