source4 smdb: Add a post fork hook to the service API
[kai/samba-autobuild/.git] / source4 / smbd / process_standard.c
index dfa4fa6b1c7a4b742bc99c27e0f2b934b3d157b5..62620096af526e1f4b849cd1338ae347c4ed8058 100644 (file)
 #include "system/filesys.h"
 #include "cluster/cluster.h"
 #include "param/param.h"
-#include "lib/ldb_wrap.h"
+#include "ldb_wrap.h"
+#include "lib/messaging/messaging.h"
+#include "lib/util/debug.h"
+#include "source3/lib/messages_dgm.h"
 
-#ifdef HAVE_SETPROCTITLE
-#ifdef HAVE_SETPROCTITLE_H
-#include <setproctitle.h>
-#endif
-#else
-#define setproctitle none_setproctitle
-static int none_setproctitle(const char *fmt, ...) PRINTF_ATTRIBUTE(1, 2);
-static int none_setproctitle(const char *fmt, ...)
-{
-       return 0;
-}
-#endif
+struct standard_child_state {
+       const char *name;
+       pid_t pid;
+       int to_parent_fd;
+       int from_child_fd;
+       struct tevent_fd *from_child_fde;
+};
 
-/* we hold a pipe open in the parent, and the any child
-   processes wait for EOF on that pipe. This ensures that
-   children die when the parent dies */
-static int child_pipe[2];
+NTSTATUS process_model_standard_init(TALLOC_CTX *);
+struct process_context {
+       char *name;
+       int from_parent_fd;
+       bool inhibit_fork_on_accept;
+       bool forked_on_accept;
+};
 
 /*
   called when the process model is selected
 */
-static void standard_model_init(struct tevent_context *ev)
+static void standard_model_init(void)
+{
+}
+
+static void sighup_signal_handler(struct tevent_context *ev,
+                               struct tevent_signal *se,
+                               int signum, int count, void *siginfo,
+                               void *private_data)
 {
-       pipe(child_pipe);
-       signal(SIGCHLD, SIG_IGN);
+       debug_schedule_reopen_logs();
+}
+
+static void sigterm_signal_handler(struct tevent_context *ev,
+                               struct tevent_signal *se,
+                               int signum, int count, void *siginfo,
+                               void *private_data)
+{
+#if HAVE_GETPGRP
+       if (getpgrp() == getpid()) {
+               /*
+                * We're the process group leader, send
+                * SIGTERM to our process group.
+                */
+               DBG_ERR("SIGTERM: killing children\n");
+               kill(-getpgrp(), SIGTERM);
+       }
+#endif
+       DBG_ERR("Exiting pid %u on SIGTERM\n", (unsigned int)getpid());
+       talloc_free(ev);
+       exit(127);
 }
 
 /*
-  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)
 {
-       DEBUG(10,("Child %d exiting\n", (int)getpid()));
+       DBG_DEBUG("Child %d exiting\n", (int)getpid());
+       talloc_free(event_ctx);
        exit(0);
 }
 
+/*
+  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;
+
+       messaging_dgm_cleanup(state->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.
+                        */
+                       DBG_ERR("Error in waitpid() unexpectedly got ECHILD "
+                               "for child %d (%s) - %s, someone has set SIGCHLD "
+                               "to SIG_IGN!\n",
+                               (int)state->pid, state->name,
+                               strerror(errno));
+                       TALLOC_FREE(state);
+                       return;
+               }
+               DBG_ERR("Error in waitpid() for child %d (%s) - %s \n",
+                       (int)state->pid, state->name, strerror(errno));
+               if (errno == 0) {
+                       errno = ECHILD;
+               }
+               TALLOC_FREE(state);
+               return;
+       }
+       if (WIFEXITED(status)) {
+               status = WEXITSTATUS(status);
+               if (status != 0) {
+                       DBG_ERR("Child %d (%s) exited with status %d\n",
+                               (int)state->pid, state->name, status);
+               }
+       } else if (WIFSIGNALED(status)) {
+               status = WTERMSIG(status);
+               DBG_ERR("Child %d (%s) terminated with signal %d\n",
+                       (int)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) {
+               DBG_ERR("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, 
-                                      struct loadparm_context *lp_ctx,
-                                      struct socket_context *sock, 
-                                      void (*new_conn)(struct tevent_context *,
-                                                       struct loadparm_context *, struct socket_context *, 
-                                                       struct server_id , void *), 
-                                      void *private_data)
+static void standard_accept_connection(
+               struct tevent_context *ev,
+               struct loadparm_context *lp_ctx,
+               struct socket_context *sock,
+               void (*new_conn)(struct tevent_context *,
+                               struct loadparm_context *,
+                               struct socket_context *,
+                               struct server_id,
+                               void *,
+                               void *),
+               void *private_data,
+               void *process_context)
 {
        NTSTATUS status;
        struct socket_context *sock2;
        pid_t pid;
-       struct tevent_context *ev2;
        struct socket_address *c, *s;
+       struct standard_child_state *state;
+       struct tevent_fd *fde = NULL;
+       struct tevent_signal *se = NULL;
+       struct process_context *proc_ctx = NULL;
+
 
        /* accept an incoming connection. */
        status = socket_accept(sock, &sock2);
        if (!NT_STATUS_IS_OK(status)) {
-               DEBUG(0,("standard_accept_connection: accept: %s\n",
-                        nt_errstr(status)));
-               /* this looks strange, but is correct. We need to throttle things until
-                  the system clears enough resources to handle this new socket */
+               DBG_DEBUG("standard_accept_connection: accept: %s\n",
+                         nt_errstr(status));
+               /* this looks strange, but is correct. We need to throttle
+                * things until the system clears enough resources to handle
+                * this new socket
+                */
                sleep(1);
                return;
        }
 
+       proc_ctx = talloc_get_type_abort(process_context,
+                                        struct process_context);
+
+       if (proc_ctx->inhibit_fork_on_accept) {
+               pid = getpid();
+               /*
+                * Service does not support forking a new process on a
+                * new connection, either it's maintaining shared
+                * state or the overhead of forking a new process is a
+                * significant fraction of the response time.
+                */
+               talloc_steal(private_data, sock2);
+               new_conn(ev, lp_ctx, sock2,
+                        cluster_id(pid, socket_get_fd(sock2)), private_data,
+                        process_context);
+               return;
+       }
+
+       state = setup_standard_child_pipe(ev, NULL);
+       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 ... */
                talloc_free(sock2);
                /* go back to the event loop */
                return;
        }
 
+       /* this leaves state->to_parent_fd open */
+       TALLOC_FREE(state);
+
+       /* Now in the child code so indicate that we forked
+        * so the terminate code knows what to do
+        */
+       proc_ctx->forked_on_accept = true;
+
        pid = getpid();
+       setproctitle("task[%s] standard worker", proc_ctx->name);
 
        /* This is now the child code. We need a completely new event_context to work with */
-       ev2 = s4_event_context_init(NULL);
-
-       /* setup this as the default context */
-       s4_event_context_set_default(ev2);
 
-       /* the service has given us a private pointer that
-          encapsulates the context it needs for this new connection -
-          everything else will be freed */
-       talloc_steal(ev2, private_data);
-       talloc_steal(private_data, sock2);
+       if (tevent_re_initialise(ev) != 0) {
+               smb_panic("Failed to re-initialise tevent after fork");
+       }
 
        /* this will free all the listening sockets and all state that
           is not associated with this new connection */
        talloc_free(sock);
-       talloc_free(ev);
 
        /* we don't care if the dup fails, as its only a select()
           speed optimisation */
@@ -129,108 +332,216 @@ static void standard_accept_connection(struct tevent_context *ev,
        /* tdb needs special fork handling */
        ldb_wrap_fork_hook();
 
-       tevent_add_fd(ev2, ev2, child_pipe[0], TEVENT_FD_READ,
+       /* Must be done after a fork() to reset messaging contexts. */
+       status = imessaging_reinit_all();
+       if (!NT_STATUS_IS_OK(status)) {
+               smb_panic("Failed to re-initialise imessaging after fork");
+       }
+
+       fde = tevent_add_fd(ev, ev, proc_ctx->from_parent_fd, TEVENT_FD_READ,
                      standard_pipe_handler, NULL);
-       close(child_pipe[1]);
+       if (fde == NULL) {
+               smb_panic("Failed to add fd handler after fork");
+       }
 
-       /* Ensure that the forked children do not expose identical random streams */
-       set_need_random_reseed();
+       se = tevent_add_signal(ev,
+                               ev,
+                               SIGHUP,
+                               0,
+                               sighup_signal_handler,
+                               NULL);
+       if (se == NULL) {
+               smb_panic("Failed to add SIGHUP handler after fork");
+       }
+
+       se = tevent_add_signal(ev,
+                               ev,
+                               SIGTERM,
+                               0,
+                               sigterm_signal_handler,
+                               NULL);
+       if (se == NULL) {
+               smb_panic("Failed to add SIGTERM handler after fork");
+       }
 
        /* setup the process title */
-       c = socket_get_peer_addr(sock2, ev2);
-       s = socket_get_my_addr(sock2, ev2);
+       c = socket_get_peer_addr(sock2, ev);
+       s = socket_get_my_addr(sock2, ev);
        if (s && c) {
                setproctitle("conn c[%s:%u] s[%s:%u] server_id[%d]",
-                            c->addr, c->port, s->addr, s->port, pid);
+                            c->addr, c->port, s->addr, s->port, (int)pid);
        }
        talloc_free(c);
        talloc_free(s);
 
-       /* setup this new connection.  Cluster ID is PID based for this process modal */
-       new_conn(ev2, lp_ctx, sock2, cluster_id(pid, 0), private_data);
+       /* setup this new connection.  Cluster ID is PID based for this process model */
+       new_conn(ev, lp_ctx, sock2, cluster_id(pid, 0), private_data,
+                process_context);
 
        /* we can't return to the top level here, as that event context is gone,
           so we now process events in the new event context until there are no
           more to process */      
-       event_loop_wait(ev2);
+       tevent_loop_wait(ev);
 
-       talloc_free(ev2);
+       talloc_free(ev);
        exit(0);
 }
 
 /*
   called to create a new server task
 */
-static void standard_new_task(struct tevent_context *ev, 
+static void standard_new_task(struct tevent_context *ev,
                              struct loadparm_context *lp_ctx,
                              const char *service_name,
-                             void (*new_task)(struct tevent_context *, struct loadparm_context *lp_ctx, struct server_id , void *), 
-                             void *private_data)
+                             struct task_server *(*new_task)(struct tevent_context *, struct loadparm_context *lp_ctx, struct server_id , void *, void *),
+                             void *private_data,
+                             const struct service_details *service_details,
+                             int from_parent_fd)
 {
        pid_t pid;
-       struct tevent_context *ev2;
+       NTSTATUS status;
+       struct standard_child_state *state;
+       struct tevent_fd *fde = NULL;
+       struct tevent_signal *se = NULL;
+       struct process_context *proc_ctx = NULL;
+       struct task_server* task = NULL;
+
+       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;
        }
 
-       pid = getpid();
+       /* this leaves state->to_parent_fd open */
+       TALLOC_FREE(state);
 
-       /* This is now the child code. We need a completely new event_context to work with */
-       ev2 = s4_event_context_init(NULL);
-
-       /* setup this as the default context */
-       s4_event_context_set_default(ev2);
-
-       /* the service has given us a private pointer that
-          encapsulates the context it needs for this new connection -
-          everything else will be freed */
-       talloc_steal(ev2, private_data);
+       pid = getpid();
 
        /* this will free all the listening sockets and all state that
           is not associated with this new connection */
-       talloc_free(ev);
+       if (tevent_re_initialise(ev) != 0) {
+               smb_panic("Failed to re-initialise tevent after fork");
+       }
 
        /* ldb/tdb need special fork handling */
        ldb_wrap_fork_hook();
 
-       tevent_add_fd(ev2, ev2, child_pipe[0], TEVENT_FD_READ,
+       /* Must be done after a fork() to reset messaging contexts. */
+       status = imessaging_reinit_all();
+       if (!NT_STATUS_IS_OK(status)) {
+               smb_panic("Failed to re-initialise imessaging after fork");
+       }
+
+       fde = tevent_add_fd(ev, ev, from_parent_fd, TEVENT_FD_READ,
                      standard_pipe_handler, NULL);
-       close(child_pipe[1]);
+       if (fde == NULL) {
+               smb_panic("Failed to add fd handler after fork");
+       }
+
+       se = tevent_add_signal(ev,
+                               ev,
+                               SIGHUP,
+                               0,
+                               sighup_signal_handler,
+                               NULL);
+       if (se == NULL) {
+               smb_panic("Failed to add SIGHUP handler after fork");
+       }
 
-       /* Ensure that the forked children do not expose identical random streams */
-       set_need_random_reseed();
+       se = tevent_add_signal(ev,
+                               ev,
+                               SIGTERM,
+                               0,
+                               sigterm_signal_handler,
+                               NULL);
+       if (se == NULL) {
+               smb_panic("Failed to add SIGTERM handler after fork");
+       }
 
-       setproctitle("task %s server_id[%d]", service_name, pid);
+       setproctitle("task[%s]", service_name);
+
+       /*
+        * Set up the process context to be passed through to the terminate
+        * and accept_connection functions
+        */
+       proc_ctx = talloc(ev, struct process_context);
+       proc_ctx->name = talloc_strdup(ev, service_name);
+       proc_ctx->from_parent_fd = from_parent_fd;
+       proc_ctx->inhibit_fork_on_accept  =
+               service_details->inhibit_fork_on_accept;
+       proc_ctx->forked_on_accept = false;
+
+       /* setup this new task.  Cluster ID is PID based for this process model */
+       task = new_task(ev, lp_ctx, cluster_id(pid, 0), private_data, proc_ctx);
+       /*
+        * Currently we don't support the post_fork functionality in the
+        * standard model, i.e. it is only called here not after a new process
+        * is forked in standard_accept_connection.
+        */
+       if (task != NULL && service_details->post_fork != NULL) {
+               service_details->post_fork(task);
+       }
 
-       /* setup this new task.  Cluster ID is PID based for this process modal */
-       new_task(ev2, lp_ctx, cluster_id(pid, 0), private_data);
 
        /* we can't return to the top level here, as that event context is gone,
           so we now process events in the new event context until there are no
-          more to process */      
-       event_loop_wait(ev2);
+          more to process */
+       tevent_loop_wait(ev);
 
-       talloc_free(ev2);
+       talloc_free(ev);
        exit(0);
 }
 
 
 /* called when a task goes down */
-_NORETURN_ static void standard_terminate(struct tevent_context *ev, struct loadparm_context *lp_ctx, 
-                                         const char *reason) 
+static void standard_terminate(struct tevent_context *ev,
+                              struct loadparm_context *lp_ctx,
+                              const char *reason,
+                              void *process_context)
 {
-       DEBUG(2,("standard_terminate: reason[%s]\n",reason));
+       struct process_context *proc_ctx = NULL;
 
-       talloc_free(ev);
+       DBG_DEBUG("process terminating reason[%s]\n", reason);
+       if (process_context == NULL) {
+               smb_panic("Panicking process_context is NULL");
+       }
+
+       proc_ctx = talloc_get_type(process_context, struct process_context);
+       if (proc_ctx->forked_on_accept == false) {
+               /*
+                * The current task was not forked on accept, so it needs to
+                * keep running and process requests from other connections
+                */
+               return;
+       }
+       /*
+        * The current process was forked on accept to handle a single
+        * connection/request. That request has now finished and the process
+        * should terminate
+        */
 
        /* this reload_charcnv() has the effect of freeing the iconv context memory,
           which makes leak checking easier */
        reload_charcnv(lp_ctx);
 
+       /* Always free event context last before exit. */
+       talloc_free(ev);
+
        /* terminate this process */
        exit(0);
 }
@@ -257,7 +568,7 @@ static const struct model_ops standard_ops = {
 /*
   initialise the standard process model, registering ourselves with the process model subsystem
  */
-NTSTATUS process_model_standard_init(void)
+NTSTATUS process_model_standard_init(TALLOC_CTX *ctx)
 {
        return register_process_model(&standard_ops);
 }