source4 smbd prefork: Add backoff to process restart
[samba.git] / source4 / smbd / process_prefork.c
index 0d17e5301d44e6a7444cfaa5785bf9f6177424aa..a023290677728a5e88d2db275ef768b61315dc62 100644 (file)
@@ -1,4 +1,4 @@
-/* 
+/*
    Unix SMB/CIFS implementation.
 
    process model: prefork (n client connections per process)
    Copyright (C) Stefan (metze) Metzmacher 2004
    Copyright (C) Andrew Bartlett 2008 <abartlet@samba.org>
    Copyright (C) David Disseldorp 2008 <ddiss@sgi.com>
-   
+
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.
-   
+
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
-   
+
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
 #include "includes.h"
+#include <unistd.h>
+
 #include "lib/events/events.h"
-#include "../tdb/include/tdb.h"
+#include "lib/messaging/messaging.h"
 #include "lib/socket/socket.h"
 #include "smbd/process_model.h"
-#include "param/secrets.h"
-#include "system/filesys.h"
 #include "cluster/cluster.h"
 #include "param/param.h"
+#include "ldb_wrap.h"
+#include "lib/util/tfork.h"
+
+#define min(a, b) (((a) < (b)) ? (a) : (b))
+
+NTSTATUS process_model_prefork_init(void);
+static void prefork_new_task(
+    struct tevent_context *ev,
+    struct loadparm_context *lp_ctx,
+    const char *service_name,
+    struct task_server *(*new_task_fn)(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);
+static void prefork_fork_worker(struct task_server *task,
+                               struct tevent_context *ev,
+                               struct tevent_context *ev2,
+                               struct loadparm_context *lp_ctx,
+                               const struct service_details *service_details,
+                               const char *service_name,
+                               int control_pipe[2],
+                               unsigned restart_delay,
+                               struct process_details *pd);
+static void prefork_child_pipe_handler(struct tevent_context *ev,
+                                      struct tevent_fd *fde,
+                                      uint16_t flags,
+                                      void *private_data);
+static void setup_handlers(struct tevent_context *ev, int from_parent_fd);
 
-#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, ...)
+/*
+ * State needed to restart the master process or a worker process if they
+ * terminate early.
+ */
+struct master_restart_context {
+       struct task_server *(*new_task_fn)(struct tevent_context *,
+                                          struct loadparm_context *lp_ctx,
+                                          struct server_id,
+                                          void *,
+                                          void *);
+       void *private_data;
+};
+
+struct worker_restart_context {
+       unsigned int instance;
+       struct task_server *task;
+       struct tevent_context *ev2;
+       int control_pipe[2];
+};
+
+struct restart_context {
+       struct loadparm_context *lp_ctx;
+       struct tfork *t;
+       int from_parent_fd;
+       const struct service_details *service_details;
+       const char *service_name;
+       unsigned restart_delay;
+       struct master_restart_context *master;
+       struct worker_restart_context *worker;
+};
+
+static void sighup_signal_handler(struct tevent_context *ev,
+                               struct tevent_signal *se,
+                               int signum, int count, void *siginfo,
+                               void *private_data)
 {
-       return 0;
+       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_NOTICE("SIGTERM: killing children\n");
+               kill(-getpgrp(), SIGTERM);
+       }
 #endif
+       DBG_NOTICE("Exiting pid %d on SIGTERM\n", getpid());
+       talloc_free(ev);
+       exit(127);
+}
 
 /*
   called when the process model is selected
 */
-static void prefork_model_init(struct event_context *ev)
+static void prefork_model_init(void)
 {
-       signal(SIGCHLD, SIG_IGN);
 }
 
 static void prefork_reload_after_fork(void)
 {
-       /* tdb needs special fork handling */
-       if (tdb_reopen_all(1) == -1) {
-               DEBUG(0,("prefork_reload_after_fork: tdb_reopen_all failed.\n"));
-       }
+       NTSTATUS status;
 
-       /* Ensure that the forked children do not expose identical random streams */
-       set_need_random_reseed();
+       ldb_wrap_fork_hook();
+       /* 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");
+       }
 }
 
 /*
-  called when a listening socket becomes readable. 
+  handle EOF on the parent-to-all-children pipe in the child
 */
-static void prefork_accept_connection(struct event_context *ev, 
-                                     struct loadparm_context *lp_ctx,
-                                     struct socket_context *listen_socket,
-                                      void (*new_conn)(struct event_context *,
-                                                       struct loadparm_context *, struct socket_context *, 
-                                                       struct server_id , void *), 
-                                      void *private)
+static void prefork_pipe_handler(struct tevent_context *event_ctx,
+                                struct tevent_fd *fde, uint16_t flags,
+                                void *private_data)
 {
-       NTSTATUS status;
-       struct socket_context *connected_socket;
-       pid_t pid = getpid();
-
-       /* accept an incoming connection. */
-       status = socket_accept(listen_socket, &connected_socket);
-       if (!NT_STATUS_IS_OK(status)) {
-               return;
-       }
-
-       talloc_steal(private, connected_socket);
-
-       new_conn(ev, lp_ctx, connected_socket, cluster_id(pid, socket_get_fd(connected_socket)), private);
+       /* free the fde which removes the event and stops it firing again */
+       TALLOC_FREE(fde);
+       DBG_NOTICE("Child %d exiting\n", getpid());
+       talloc_free(event_ctx);
+       exit(0);
 }
 
+
 /*
-  called to create a new server task
-*/
-static void prefork_new_task(struct event_context *ev, 
-                            struct loadparm_context *lp_ctx,
-                            const char *service_name,
-                            void (*new_task_fn)(struct event_context *, struct loadparm_context *lp_ctx, struct server_id , void *), 
-                            void *private)
+ * called to create a new server task
+ */
+static void prefork_fork_master(
+    struct tevent_context *ev,
+    struct loadparm_context *lp_ctx,
+    const char *service_name,
+    struct task_server *(*new_task_fn)(struct tevent_context *,
+                                      struct loadparm_context *lp_ctx,
+                                      struct server_id,
+                                      void *,
+                                      void *),
+    void *private_data,
+    const struct service_details *service_details,
+    unsigned restart_delay,
+    int from_parent_fd)
 {
        pid_t pid;
+       struct tfork* t = NULL;
        int i, num_children;
 
-       struct event_context *ev2, *ev_parent;
+       struct tevent_context *ev2;
+       struct task_server *task = NULL;
+       struct process_details pd = initial_process_details;
+       int control_pipe[2];
 
-       pid = fork();
+       t = tfork_create();
+       if (t == NULL) {
+               smb_panic("failure in tfork\n");
+       }
 
+       DBG_NOTICE("Forking [%s] pre-fork master process\n", service_name);
+       pid = tfork_child_pid(t);
        if (pid != 0) {
-               /* parent or error code ... go back to the event loop */
+               struct tevent_fd *fde = NULL;
+               int fd = tfork_event_fd(t);
+               struct restart_context *rc = NULL;
+
+               /* Register a pipe handler that gets called when the prefork
+                * master process terminates.
+                */
+               rc = talloc_zero(ev, struct restart_context);
+               if (rc == NULL) {
+                       smb_panic("OOM allocating restart context\n");
+               }
+               rc->t = t;
+               rc->lp_ctx = lp_ctx;
+               rc->service_name = service_name;
+               rc->service_details = service_details;
+               rc->from_parent_fd = from_parent_fd;
+               rc->restart_delay = restart_delay;
+               rc->master = talloc_zero(rc, struct master_restart_context);
+               if (rc->master == NULL) {
+                       smb_panic("OOM allocating master restart context\n");
+               }
+
+               rc->master->new_task_fn = new_task_fn;
+               rc->master->private_data = private_data;
+
+               fde = tevent_add_fd(
+                   ev, ev, fd, TEVENT_FD_READ, prefork_child_pipe_handler, rc);
+               if (fde == NULL) {
+                       smb_panic("Failed to add child pipe handler, "
+                                 "after fork");
+               }
+               tevent_fd_set_auto_close(fde);
                return;
        }
 
        pid = getpid();
+       setproctitle("task[%s] pre-fork master", service_name);
+
+       /*
+        * this will free all the listening sockets and all state that
+        * is not associated with this new connection
+        */
+       if (tevent_re_initialise(ev) != 0) {
+               smb_panic("Failed to re-initialise tevent after fork");
+       }
+       prefork_reload_after_fork();
+       setup_handlers(ev, from_parent_fd);
+
+       if (service_details->inhibit_pre_fork) {
+               task = new_task_fn(
+                   ev, lp_ctx, cluster_id(pid, 0), private_data, NULL);
+               /*
+                * The task does not support pre-fork
+                */
+               if (task != NULL && service_details->post_fork != NULL) {
+                       service_details->post_fork(task, &pd);
+               }
+               tevent_loop_wait(ev);
+               TALLOC_FREE(ev);
+               exit(0);
+       }
 
-       /* This is now the child code. We need a completely new event_context to work with */
+       /*
+        * This is now the child code. We need a completely new event_context
+        * to work with
+        */
        ev2 = s4_event_context_init(NULL);
 
-       /* 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);
+       /* setup this new connection: process will bind to it's sockets etc
+        *
+        * While we can use ev for the child, which has been re-initialised
+        * above we must run the new task under ev2 otherwise the children would
+        * be listening on the sockets.  Also we don't want the top level
+        * process accepting and handling requests, it's responsible for
+        * monitoring and controlling the child work processes.
+        */
+       task = new_task_fn(ev2, lp_ctx, cluster_id(pid, 0), private_data, NULL);
+       if (task == NULL) {
+               TALLOC_FREE(ev);
+               TALLOC_FREE(ev2);
+               exit(0);
+       }
 
-       /* this will free all the listening sockets and all state that
-          is not associated with this new connection */
-       talloc_free(ev);
+       {
+               int default_children;
+               default_children = lpcfg_prefork_children(lp_ctx);
+               num_children = lpcfg_parm_int(lp_ctx, NULL, "prefork children",
+                                             service_name, default_children);
+       }
+       if (num_children == 0) {
+               DBG_WARNING("Number of pre-fork children for %s is zero, "
+                           "NO worker processes will be started for %s\n",
+                           service_name, service_name);
+       }
+       DBG_NOTICE("Forking %d %s worker processes\n",
+                  num_children, service_name);
+       {
+               int ret;
+               ret = pipe(control_pipe);
+               if (ret != 0) {
+                       smb_panic("Unable to create worker control pipe\n");
+               }
+               smb_set_close_on_exec(control_pipe[0]);
+               smb_set_close_on_exec(control_pipe[1]);
+       }
 
-       setproctitle("task %s server_id[%d]", service_name, pid);
+       /*
+        * We are now free to spawn some worker processes
+        */
+       for (i=0; i < num_children; i++) {
+               prefork_fork_worker(task,
+                                   ev,
+                                   ev2,
+                                   lp_ctx,
+                                   service_details,
+                                   service_name,
+                                   control_pipe,
+                                   0,
+                                   &pd);
+               pd.instances++;
+       }
 
-       prefork_reload_after_fork();
+       /* Don't listen on the sockets we just gave to the children */
+       tevent_loop_wait(ev);
+       TALLOC_FREE(ev);
+       /* We need to keep ev2 until we're finished for the messaging to work */
+       TALLOC_FREE(ev2);
+       exit(0);
+
+}
+static void prefork_restart(struct tevent_context *ev,
+                           struct restart_context *rc)
+{
+       unsigned max_backoff = 0;
+       unsigned backoff = 0;
+       unsigned restart_delay = rc->restart_delay;
+       unsigned default_value = 0;
+
+       default_value = lpcfg_prefork_backoff_increment(rc->lp_ctx);
+       backoff = lpcfg_parm_int(rc->lp_ctx,
+                                NULL,
+                                "prefork backoff increment",
+                                rc->service_name,
+                                default_value);
+
+       default_value = lpcfg_prefork_maximum_backoff(rc->lp_ctx);
+       max_backoff = lpcfg_parm_int(rc->lp_ctx,
+                                    NULL,
+                                    "prefork maximum backoff",
+                                    rc->service_name,
+                                    default_value);
+
+       if (restart_delay > 0) {
+               DBG_ERR("Restarting [%s] pre-fork %s in (%d) seconds\n",
+                       rc->service_name,
+                       (rc->master == NULL) ? "worker" : "master",
+                       restart_delay);
+               sleep(restart_delay);
+       }
+       restart_delay += backoff;
+       restart_delay = min(restart_delay, max_backoff);
+
+       if (rc->master != NULL) {
+               DBG_ERR("Restarting [%s] pre-fork master\n", rc->service_name);
+               prefork_fork_master(ev,
+                                   rc->lp_ctx,
+                                   rc->service_name,
+                                   rc->master->new_task_fn,
+                                   rc->master->private_data,
+                                   rc->service_details,
+                                   restart_delay,
+                                   rc->from_parent_fd);
+       } else if (rc->worker != NULL) {
+               struct process_details pd = initial_process_details;
+               DBG_ERR("Restarting [%s] pre-fork worker(%d)\n",
+                       rc->service_name,
+                       rc->worker->instance);
+               pd.instances = rc->worker->instance;
+               prefork_fork_worker(rc->worker->task,
+                                   ev,
+                                   rc->worker->ev2,
+                                   rc->lp_ctx,
+                                   rc->service_details,
+                                   rc->service_name,
+                                   rc->worker->control_pipe,
+                                   restart_delay,
+                                   &pd);
+       }
+}
+/*
+  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.
 
-       /* setup this new connection: process will bind to it's sockets etc */
-       new_task_fn(ev2, lp_ctx, cluster_id(pid, 0), private);
+  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 prefork_child_pipe_handler(struct tevent_context *ev,
+                                      struct tevent_fd *fde,
+                                      uint16_t flags,
+                                      void *private_data)
+{
+       struct restart_context *rc = NULL;
+       int status = 0;
+       pid_t pid = 0;
+
+       /* free the fde which removes the event and stops it firing again */
+       TALLOC_FREE(fde);
+
+       /* the child has closed the pipe, assume its dead */
+
+       rc = talloc_get_type_abort(private_data, struct restart_context);
+       pid = tfork_child_pid(rc->t);
+       errno = 0;
+       status = tfork_status(&rc->t, false);
+       if (status == -1) {
+               DBG_ERR("Parent %d, Child %d terminated, "
+                       "unable to get status code from tfork\n",
+                       getpid(), pid);
+               prefork_restart(ev, rc);
+       } else if (WIFEXITED(status)) {
+               status = WEXITSTATUS(status);
+               DBG_ERR("Parent %d, Child %d exited with status %d\n",
+                        getpid(), pid,  status);
+               if (status != 0) {
+                       prefork_restart(ev, rc);
+               }
+       } else if (WIFSIGNALED(status)) {
+               status = WTERMSIG(status);
+               DBG_ERR("Parent %d, Child %d terminated with signal %d\n",
+                       getpid(), pid, status);
+               if (status == SIGABRT || status == SIGBUS || status == SIGFPE ||
+                   status == SIGILL || status == SIGSYS || status == SIGSEGV) {
+
+                       prefork_restart(ev, rc);
+               }
+       }
+       /* tfork allocates tfork structures with malloc */
+       tfork_destroy(&rc->t);
+       free(rc->t);
+       TALLOC_FREE(rc);
+       return;
+}
 
-       num_children = lp_parm_int(lp_ctx, NULL, "prefork children", service_name, 0);
-       if (num_children == 0) {
+/*
+  called when a listening socket becomes readable.
+*/
+static void prefork_accept_connection(
+       struct tevent_context *ev,
+       struct loadparm_context *lp_ctx,
+       struct socket_context *listen_socket,
+       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 *connected_socket;
+       pid_t pid = getpid();
 
-               /* We don't want any kids hanging around for this one,
-                * let the parent do all the work */
-               event_loop_wait(ev2);
-               
-               talloc_free(ev2);
-               exit(0);
+       /* accept an incoming connection. */
+       status = socket_accept(listen_socket, &connected_socket);
+       if (!NT_STATUS_IS_OK(status)) {
+               /*
+                * For prefork we can ignore STATUS_MORE_ENTRIES, as  once a
+                * connection becomes available all waiting processes are
+                * woken, but only one gets work to  process.
+                * AKA the thundering herd.
+                * In the short term this should not be an issue as the number
+                * of workers should be a small multiple of the number of cpus
+                * In the longer term socket_accept needs to implement a
+                * mutex/semaphore (like apache does) to serialise the accepts
+                */
+               if (!NT_STATUS_EQUAL(status, STATUS_MORE_ENTRIES)) {
+                       DBG_ERR("Worker process (%d), error in accept [%s]\n",
+                               getpid(), nt_errstr(status));
+               }
+               return;
        }
 
-       /* We are now free to spawn some child proccesses */
+       talloc_steal(private_data, connected_socket);
 
-       for (i=0; i < num_children; i++) {
+       new_conn(ev, lp_ctx, connected_socket,
+                cluster_id(pid, socket_get_fd(connected_socket)),
+                private_data, process_context);
+}
 
-               pid = fork();
-               if (pid > 0) {
-                       continue;
-               } else if (pid == -1) {
-                       return;
-               } else {
-                       pid = getpid();
-                       setproctitle("task %s server_id[%d]", service_name, pid);
-
-                       prefork_reload_after_fork();
-
-                       /* 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);
-                       
-                       talloc_free(ev2);
-                       exit(0);
-               }
+static void setup_handlers(struct tevent_context *ev, int from_parent_fd) {
+       struct tevent_fd *fde = NULL;
+       struct tevent_signal *se = NULL;
+
+       fde = tevent_add_fd(ev, ev, from_parent_fd, TEVENT_FD_READ,
+                     prefork_pipe_handler, NULL);
+       if (fde == NULL) {
+               smb_panic("Failed to add fd handler after fork");
        }
 
-       /* Don't listen on the sockets we just gave to the children */
-       talloc_free(ev2);
-       
-       /* But we need a events system to handle reaping children */
-       ev_parent = s4_event_context_init(NULL);
-       
-       /* TODO: Handle some events... */
-       
-       /* 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(ev_parent);
-       
-       talloc_free(ev_parent);
-       exit(0);
+       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");
+       }
 }
 
+static void prefork_fork_worker(struct task_server *task,
+                               struct tevent_context *ev,
+                               struct tevent_context *ev2,
+                               struct loadparm_context *lp_ctx,
+                               const struct service_details *service_details,
+                               const char *service_name,
+                               int control_pipe[2],
+                               unsigned restart_delay,
+                               struct process_details *pd)
+{
+       struct tfork *w = NULL;
+       pid_t pid;
+
+       w = tfork_create();
+       if (w == NULL) {
+               smb_panic("failure in tfork\n");
+       }
+
+       pid = tfork_child_pid(w);
+       if (pid != 0) {
+               struct tevent_fd *fde = NULL;
+               int fd = tfork_event_fd(w);
+               struct restart_context *rc = NULL;
 
-/* called when a task goes down */
-_NORETURN_ static void prefork_terminate(struct event_context *ev, const char *reason) 
+               rc = talloc_zero(ev, struct restart_context);
+               if (rc == NULL) {
+                       smb_panic("OOM allocating restart context\n");
+               }
+               rc->t = w;
+               rc->lp_ctx = lp_ctx;
+               rc->service_name = service_name;
+               rc->service_details = service_details;
+               rc->restart_delay = restart_delay;
+               rc->master = NULL;
+               rc->worker = talloc_zero(rc, struct worker_restart_context);
+               if (rc->worker == NULL) {
+                       smb_panic("OOM allocating master restart context\n");
+               }
+               rc->worker->ev2 = ev2;
+               rc->worker->instance = pd->instances;
+               rc->worker->task = task;
+               rc->worker->control_pipe[0] = control_pipe[0];
+               rc->worker->control_pipe[1] = control_pipe[1];
+
+               fde = tevent_add_fd(
+                   ev, ev, fd, TEVENT_FD_READ, prefork_child_pipe_handler, rc);
+               if (fde == NULL) {
+                       smb_panic("Failed to add child pipe handler, "
+                                 "after fork");
+               }
+               tevent_fd_set_auto_close(fde);
+       } else {
+               close(control_pipe[1]);
+               setup_handlers(ev2, control_pipe[0]);
+               /*
+                * tfork uses malloc
+                */
+               free(w);
+
+               TALLOC_FREE(ev);
+               setproctitle("task[%s] pre-forked worker(%d)",
+                            service_name,
+                            pd->instances);
+               prefork_reload_after_fork();
+               if (service_details->post_fork != NULL) {
+                       service_details->post_fork(task, pd);
+               }
+               tevent_loop_wait(ev2);
+               talloc_free(ev2);
+               exit(0);
+       }
+}
+/*
+ * called to create a new server task
+ */
+static void prefork_new_task(
+       struct tevent_context *ev,
+       struct loadparm_context *lp_ctx,
+       const char *service_name,
+       struct task_server *(*new_task_fn)(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)
 {
-       DEBUG(2,("prefork_terminate: reason[%s]\n",reason));
+       prefork_fork_master(ev,
+                           lp_ctx,
+                           service_name,
+                           new_task_fn,
+                           private_data,
+                           service_details,
+                           0,
+                           from_parent_fd);
+
 }
 
-/* called to set a title of a task or connection */
-static void prefork_set_title(struct event_context *ev, const char *title) 
+/*
+ * called when a task terminates
+ */
+static void prefork_terminate_task(struct tevent_context *ev,
+                                  struct loadparm_context *lp_ctx,
+                                  const char *reason,
+                                  bool fatal,
+                                  void *process_context)
 {
-       if (title) {
-               setproctitle("%s", title);
+       DBG_DEBUG("called with reason[%s]\n", reason);
+       if (fatal == true) {
+               exit(127);
        } else {
-               setproctitle(NULL);
+               exit(0);
        }
 }
 
+/*
+ * called when a connection completes
+ */
+static void prefork_terminate_connection(struct tevent_context *ev,
+                                        struct loadparm_context *lp_ctx,
+                                        const char *reason,
+                                        void *process_context)
+{
+}
+
+/* called to set a title of a task or connection */
+static void prefork_set_title(struct tevent_context *ev, const char *title)
+{
+}
+
 static const struct model_ops prefork_ops = {
        .name                   = "prefork",
        .model_init             = prefork_model_init,
        .accept_connection      = prefork_accept_connection,
-       .new_task               = prefork_new_task,
-       .terminate              = prefork_terminate,
-       .set_title              = prefork_set_title,
+       .new_task               = prefork_new_task,
+       .terminate_task         = prefork_terminate_task,
+       .terminate_connection   = prefork_terminate_connection,
+       .set_title              = prefork_set_title,
 };
 
 /*
-  initialise the prefork process model, registering ourselves with the process model subsystem
+ * initialise the prefork process model, registering ourselves with the
+ * process model subsystem
  */
 NTSTATUS process_model_prefork_init(void)
 {