The branch, master has been updated via 143e7d3 lib/util: Make ECHILD in samba_runcmd_io_handler an error via 30e0238 s4-process_standard: Remove signal(SIGCHLD, SIG_IGN) from f212143 selftest: rename env member to nt4_member
https://git.samba.org/?p=samba.git;a=shortlog;h=master - Log ----------------------------------------------------------------- commit 143e7d362797dcb2f92de9231966bf63b604e02e Author: Andrew Bartlett <abart...@samba.org> Date: Thu Feb 19 12:41:34 2015 +1300 lib/util: Make ECHILD in samba_runcmd_io_handler an error We now print out a nasty message and set and error if we get ECHILD, as we no longer set SIGIGN on SIGCHLD in the standard process model. This was why samba_kcc was able to fail totally without us noticing. Signed-off-by: Andrew Bartlett <abart...@samba.org> Reviewed-by: Jelmer Vernooij <jel...@samba.org> Autobuild-User(master): Andrew Bartlett <abart...@samba.org> Autobuild-Date(master): Tue Mar 17 07:05:43 CET 2015 on sn-devel-104 commit 30e0238646bc3a891a67dea57baccea8afc1af43 Author: Andrew Bartlett <abart...@samba.org> Date: Thu Feb 19 12:45:31 2015 +1300 s4-process_standard: Remove signal(SIGCHLD, SIG_IGN) We replace this with a pipe between parent and child, and then watch for a read event in the parent to indicate that the child has gone away. The removal of signal(SIGCHLD, SIG_IGN) requires us to then call waitpid(). We can't do that in a main loop as we want to get the exit status to the legitimate waitpid calls in routines like samba_runcmd_*(). Signed-off-by: Andrew Bartlett <abart...@samba.org> Reviewed-by: Stefan Metzmacher <me...@samba.org> ----------------------------------------------------------------------- Summary of changes: lib/util/util_runcmd.c | 6 +- source4/smbd/process_standard.c | 176 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 179 insertions(+), 3 deletions(-) Changeset truncated at 500 lines: diff --git a/lib/util/util_runcmd.c b/lib/util/util_runcmd.c index 1ec717f..46c455d 100644 --- a/lib/util/util_runcmd.c +++ b/lib/util/util_runcmd.c @@ -288,7 +288,11 @@ static void samba_runcmd_io_handler(struct tevent_context *ev, SIGCHLD in the standard process model. */ - tevent_req_done(req); + DEBUG(0, ("Error in waitpid() unexpectedly got ECHILD " + "for %s child %d - %s, " + "someone has set SIGCHLD to SIG_IGN!\n", + state->arg0, state->pid, strerror(errno))); + tevent_req_error(req, errno); return; } DEBUG(0,("Error in waitpid() for child %s - %s \n", diff --git a/source4/smbd/process_standard.c b/source4/smbd/process_standard.c index c5377b3..d3622f9 100644 --- a/source4/smbd/process_standard.c +++ b/source4/smbd/process_standard.c @@ -29,6 +29,14 @@ #include "param/param.h" #include "ldb_wrap.h" +struct standard_child_state { + const char *name; + pid_t pid; + int to_parent_fd; + int from_child_fd; + struct tevent_fd *from_child_fde; +}; + NTSTATUS process_model_standard_init(void); /* we hold a pipe open in the parent, and the any child @@ -42,11 +50,10 @@ static int child_pipe[2]; static void standard_model_init(void) { pipe(child_pipe); - signal(SIGCHLD, SIG_IGN); } /* - handle EOF on the child pipe + handle EOF on the parent-to-all-children pipe in the child */ static void standard_pipe_handler(struct tevent_context *event_ctx, struct tevent_fd *fde, uint16_t flags, void *private_data) @@ -56,6 +63,132 @@ static void standard_pipe_handler(struct tevent_context *event_ctx, struct teven } /* + handle EOF on the child pipe in the parent, so we know when a + process terminates without using SIGCHLD or waiting on all possible pids. + + We need to ensure we do not ignore SIGCHLD because we need it to + work to get a valid error code from samba_runcmd_*(). + */ +static void standard_child_pipe_handler(struct tevent_context *ev, + struct tevent_fd *fde, + uint16_t flags, + void *private_data) +{ + struct standard_child_state *state + = talloc_get_type_abort(private_data, struct standard_child_state); + int status = 0; + pid_t pid; + + /* the child has closed the pipe, assume its dead */ + errno = 0; + pid = waitpid(state->pid, &status, 0); + + if (pid != state->pid) { + if (errno == ECHILD) { + /* + * this happens when the + * parent has set SIGCHLD to + * SIG_IGN. In that case we + * can only get error + * information for the child + * via its logging. We should + * stop using SIG_IGN on + * SIGCHLD in the standard + * process model. + */ + DEBUG(0, ("Error in waitpid() unexpectedly got ECHILD " + "for child %d (%s) - %s, someone has set SIGCHLD " + "to SIG_IGN!\n", + state->pid, state->name, strerror(errno))); + TALLOC_FREE(state); + return; + } + DEBUG(0, ("Error in waitpid() for child %d (%s) - %s \n", + state->pid, state->name, strerror(errno))); + if (errno == 0) { + errno = ECHILD; + } + TALLOC_FREE(state); + return; + } + if (WIFEXITED(status)) { + status = WEXITSTATUS(status); + DEBUG(2, ("Child %d (%s) exited with status %d\n", + state->pid, state->name, status)); + } else if (WIFSIGNALED(status)) { + status = WTERMSIG(status); + DEBUG(0, ("Child %d (%s) terminated with signal %d\n", + state->pid, state->name, status)); + } + TALLOC_FREE(state); + return; +} + +static struct standard_child_state *setup_standard_child_pipe(struct tevent_context *ev, + const char *name) +{ + struct standard_child_state *state; + int parent_child_pipe[2]; + int ret; + + /* + * Prepare a pipe to allow us to know when the child exits, + * because it will trigger a read event on this private + * pipe. + * + * We do all this before the accept and fork(), so we can + * clean up if it fails. + */ + state = talloc_zero(ev, struct standard_child_state); + if (state == NULL) { + return NULL; + } + + if (name == NULL) { + name = ""; + } + + state->name = talloc_strdup(state, name); + if (state->name == NULL) { + TALLOC_FREE(state); + return NULL; + } + + ret = pipe(parent_child_pipe); + if (ret == -1) { + DEBUG(0, ("Failed to create parent-child pipe to handle " + "SIGCHLD to track new process for socket\n")); + TALLOC_FREE(state); + return NULL; + } + + smb_set_close_on_exec(parent_child_pipe[0]); + smb_set_close_on_exec(parent_child_pipe[1]); + + state->from_child_fd = parent_child_pipe[0]; + state->to_parent_fd = parent_child_pipe[1]; + + /* + * The basic purpose of calling this handler is to ensure we + * call waitpid() and so avoid zombies (now that we no longer + * user SIGIGN on for SIGCHLD), but it also allows us to clean + * up other resources in the future. + */ + state->from_child_fde = tevent_add_fd(ev, state, + state->from_child_fd, + TEVENT_FD_READ, + standard_child_pipe_handler, + state); + if (state->from_child_fde == NULL) { + TALLOC_FREE(state); + return NULL; + } + tevent_fd_set_auto_close(state->from_child_fde); + + return state; +} + +/* called when a listening socket becomes readable. */ static void standard_accept_connection(struct tevent_context *ev, @@ -70,6 +203,12 @@ static void standard_accept_connection(struct tevent_context *ev, struct socket_context *sock2; pid_t pid; struct socket_address *c, *s; + struct standard_child_state *state; + + state = setup_standard_child_pipe(ev, NULL); + if (state == NULL) { + return; + } /* accept an incoming connection. */ status = socket_accept(sock, &sock2); @@ -79,18 +218,33 @@ static void standard_accept_connection(struct tevent_context *ev, /* this looks strange, but is correct. We need to throttle things until the system clears enough resources to handle this new socket */ sleep(1); + close(state->to_parent_fd); + state->to_parent_fd = -1; + TALLOC_FREE(state); return; } pid = fork(); if (pid != 0) { + close(state->to_parent_fd); + state->to_parent_fd = -1; + + if (pid > 0) { + state->pid = pid; + } else { + TALLOC_FREE(state); + } + /* parent or error code ... */ talloc_free(sock2); /* go back to the event loop */ return; } + /* this leaves state->to_parent_fd open */ + TALLOC_FREE(state); + pid = getpid(); /* This is now the child code. We need a completely new event_context to work with */ @@ -149,14 +303,32 @@ static void standard_new_task(struct tevent_context *ev, void *private_data) { pid_t pid; + struct standard_child_state *state; + + state = setup_standard_child_pipe(ev, service_name); + if (state == NULL) { + return; + } pid = fork(); if (pid != 0) { + close(state->to_parent_fd); + state->to_parent_fd = -1; + + if (pid > 0) { + state->pid = pid; + } else { + TALLOC_FREE(state); + } + /* parent or error code ... go back to the event loop */ return; } + /* this leaves state->to_parent_fd open */ + TALLOC_FREE(state); + pid = getpid(); /* this will free all the listening sockets and all state that -- Samba Shared Repository