summaryrefslogtreecommitdiff
path: root/source4/smbd
diff options
context:
space:
mode:
authorAndrew Bartlett <abartlet@samba.org>2015-02-19 12:45:31 +1300
committerAndrew Bartlett <abartlet@samba.org>2015-03-17 04:29:06 +0100
commit30e0238646bc3a891a67dea57baccea8afc1af43 (patch)
treeb08ff031ae71baea9b369d739bb4cdb96991bc66 /source4/smbd
parentf212143abc58c896eb406c1356715b78a52ab97b (diff)
downloadsamba-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.c176
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