diff options
author | Andrew Bartlett <abartlet@samba.org> | 2015-02-19 12:45:31 +1300 |
---|---|---|
committer | Andrew Bartlett <abartlet@samba.org> | 2015-03-17 04:29:06 +0100 |
commit | 30e0238646bc3a891a67dea57baccea8afc1af43 (patch) | |
tree | b08ff031ae71baea9b369d739bb4cdb96991bc66 /source4/smbd | |
parent | f212143abc58c896eb406c1356715b78a52ab97b (diff) | |
download | samba-30e0238646bc3a891a67dea57baccea8afc1af43.tar.gz |
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 <abartlet@samba.org>
Reviewed-by: Stefan Metzmacher <metze@samba.org>
Diffstat (limited to 'source4/smbd')
-rw-r--r-- | source4/smbd/process_standard.c | 176 |
1 files changed, 174 insertions, 2 deletions
diff --git a/source4/smbd/process_standard.c b/source4/smbd/process_standard.c index c5377b34e08..d3622f9f916 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 |