lib/util: add double_fork() that avoids getting SIGCHLD and getting zombies
authorStefan Metzmacher <metze@samba.org>
Thu, 23 Sep 2010 16:10:02 +0000 (18:10 +0200)
committerStefan Metzmacher <metze@samba.org>
Wed, 27 Oct 2010 07:33:36 +0000 (09:33 +0200)
This function is a workaround for the problem of using fork() in
library code. In that case the library should avoid to set a global
signal handler for SIGCHLD, because the application may wants to use its
own handler!

metze

lib/util/double_fork.c [new file with mode: 0644]
lib/util/double_fork.h [new file with mode: 0644]
lib/util/util.h
lib/util/wscript_build

diff --git a/lib/util/double_fork.c b/lib/util/double_fork.c
new file mode 100644 (file)
index 0000000..4bee337
--- /dev/null
@@ -0,0 +1,148 @@
+/*
+   double fork to avoid SIGCHLD and zombies when the child exists.
+
+   Copyright (C) Stefan Metzmacher 2010
+
+   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 "replace.h"
+#include "system/wait.h"
+#include "system/filesys.h" /* for getpid() via unistd.h */
+#include "../lib/util/util.h"
+
+struct double_fork_state {
+       void (*old_sig_chld)(int);
+       int level0_pid;
+       int level0_status;
+       int level1_pid;
+       int level1_errno;
+       int level2_pid;
+};
+
+/*
+ * TODO: We should make this global thread local?
+ */
+struct double_fork_state *double_fork_global;
+
+static void double_fork_sig_chld(int signum)
+{
+       int ret;
+
+       if (double_fork_global->level1_pid > 0) {
+               ret = waitpid(double_fork_global->level1_pid,
+                             &double_fork_global->level0_status,
+                             WNOHANG);
+               if (ret == double_fork_global->level1_pid) {
+                       return;
+               }
+       }
+
+       if (double_fork_global->old_sig_chld == SIG_IGN) {
+               return;
+       }
+
+       if (double_fork_global->old_sig_chld == SIG_DFL) {
+               return;
+       }
+
+       double_fork_global->old_sig_chld(signum);
+}
+
+int double_fork(int *parent)
+{
+       int level1_pid;
+       int ret;
+       int child = -1;
+
+       double_fork_global = (struct double_fork_state *)
+               anonymous_shared_allocate(sizeof(struct double_fork_state));
+       if (double_fork_global == NULL) {
+               return -1;
+       }
+
+       double_fork_global->level0_pid = getpid();
+       double_fork_global->level0_status = -1;
+       double_fork_global->level1_pid = -1;
+       double_fork_global->level1_errno = ECANCELED;
+       double_fork_global->level2_pid = -1;
+
+       double_fork_global->old_sig_chld = signal(SIGCHLD, double_fork_sig_chld);
+
+       level1_pid = fork();
+       if (level1_pid == 0) {
+               int level2_pid;
+               signal(SIGCHLD, SIG_DFL);
+
+               level2_pid = fork();
+               if (level2_pid == 0) {
+                       /*
+                        * we're in the child and return the level0 parent pid
+                        */
+                       if (parent) {
+                               *parent = double_fork_global->level0_pid;
+                       }
+
+                       anonymous_shared_free(double_fork_global);
+                       double_fork_global = NULL;
+
+                       return 0;
+               }
+               double_fork_global->level2_pid = level2_pid;
+               if (double_fork_global->level2_pid == -1) {
+                       double_fork_global->level1_errno = errno;
+               }
+               _exit(0);
+       }
+       double_fork_global->level1_pid = level1_pid;
+       if (double_fork_global->level1_pid == -1) {
+               int saved_errno = errno;
+               anonymous_shared_free(double_fork_global);
+               double_fork_global = NULL;
+               errno = saved_errno;
+               return -1;
+       }
+       errno = 0;
+       while (true) {
+               ret = waitpid(double_fork_global->level1_pid,
+                             &double_fork_global->level0_status, 0);
+               if (ret == -1 && errno == EINTR) {
+                       continue;
+               }
+
+               break;
+       }
+
+       signal(SIGCHLD, double_fork_global->old_sig_chld);
+
+       if (double_fork_global->level0_status != 0) {
+               anonymous_shared_free(double_fork_global);
+               double_fork_global = NULL;
+               errno = ECHILD;
+               return -1;
+       }
+
+       if (double_fork_global->level2_pid == -1) {
+               int saved_errno = double_fork_global->level1_errno;
+               anonymous_shared_free(double_fork_global);
+               double_fork_global = NULL;
+               errno = saved_errno;
+               return -1;
+       }
+
+       child = double_fork_global->level2_pid;
+       anonymous_shared_free(double_fork_global);
+       double_fork_global = NULL;
+       return child;
+}
diff --git a/lib/util/double_fork.h b/lib/util/double_fork.h
new file mode 100644 (file)
index 0000000..65ca5a0
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+   double fork to avoid SIGCHLD and zombies when the child exists.
+
+   Copyright (C) Stefan Metzmacher 2010
+
+   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/>.
+*/
+
+#ifndef LIB_UTIL_DOUBLE_FORK_H
+#define LIB_UTIL_DOUBLE_FORK_H
+
+/**
+ * @brief do a fork() that avoids getting SIGCHLD and getting zombies
+ *
+ * This function is a workaround for the problem of using fork() in
+ * library code. In that case the library should avoid to set a global
+ * signal handler for SIGCHLD, because the application may wants to use its
+ * own handler!
+ *
+ * The child process will have handle SIGCHLD with SIG_DFL, so the
+ * child might need to setup its own handler.
+ *
+ * @param[out] parent   The PID of the parent process, if 0 is returned
+ *                      otherwise the variable will not be touched at all.
+ *                      It is possible to pass NULL.
+ *
+ * @return              Value > 0 in the parent, 0 in the child, -1 on error with errno set.
+ */
+int double_fork(int *parent);
+
+#endif /* LIB_UTIL_DOUBLE_FORK_H */
index c7300108b803583bbf0865233be5779b95cee52f..3228e12188266c3eb5ca38a1b181ade1e31caadc 100644 (file)
@@ -44,6 +44,7 @@ extern const char *panic_action;
 #include "../lib/util/xfile.h"
 #include "../lib/util/byteorder.h"
 #include "../lib/util/talloc_stack.h"
+#include "../lib/util/double_fork.h"
 
 /**
  * assert macros 
index 799d7b1ce74d5c038d98d53f11e5370242ab2a4a..91fa04ddee3b4ce2684d0c4180253efc63197e12 100644 (file)
@@ -1,9 +1,15 @@
 #!/usr/bin/env python
 
 bld.SAMBA_LIBRARY('samba-util',
-       source='xfile.c debug.c fault.c signal.c system.c time.c genrand.c dprintf.c util_str.c rfc1738.c substitute.c util_strlist.c util_file.c data_blob.c util.c blocking.c util_net.c fsusage.c ms_fnmatch.c idtree.c become_daemon.c rbtree.c talloc_stack.c smb_threads.c params.c parmlist.c util_id.c select.c',
+       source='''xfile.c debug.c fault.c signal.c system.c time.c genrand.c
+               dprintf.c util_str.c rfc1738.c substitute.c util_strlist.c util_file.c
+               data_blob.c util.c blocking.c util_net.c fsusage.c ms_fnmatch.c
+               idtree.c become_daemon.c rbtree.c talloc_stack.c smb_threads.c
+               params.c parmlist.c util_id.c select.c double_fork.c''',
        public_deps='talloc LIBCRYPTO CHARSET execinfo uid_wrapper',
-       public_headers='attr.h byteorder.h data_blob.h debug.h memory.h safe_string.h time.h talloc_stack.h xfile.h dlinklist.h util.h',
+       public_headers='''attr.h byteorder.h data_blob.h debug.h memory.h
+               safe_string.h time.h talloc_stack.h xfile.h dlinklist.h
+               util.h double_fork.h''',
        header_path= [ ('dlinklist.h util.h', '.'), ('*', 'util') ],
        local_include=False,
        vnum='0.0.1',