From f2c57c948a4e002831cf8aba9850ae808b123256 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Thu, 23 Sep 2010 18:10:02 +0200 Subject: [PATCH] lib/util: add double_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! metze --- lib/util/double_fork.c | 148 +++++++++++++++++++++++++++++++++++++++++ lib/util/double_fork.h | 42 ++++++++++++ lib/util/util.h | 1 + lib/util/wscript_build | 10 ++- 4 files changed, 199 insertions(+), 2 deletions(-) create mode 100644 lib/util/double_fork.c create mode 100644 lib/util/double_fork.h diff --git a/lib/util/double_fork.c b/lib/util/double_fork.c new file mode 100644 index 000000000000..4bee33733af6 --- /dev/null +++ b/lib/util/double_fork.c @@ -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 . +*/ + +#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 index 000000000000..65ca5a07cb65 --- /dev/null +++ b/lib/util/double_fork.h @@ -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 . +*/ + +#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 */ diff --git a/lib/util/util.h b/lib/util/util.h index c7300108b803..3228e1218826 100644 --- a/lib/util/util.h +++ b/lib/util/util.h @@ -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 diff --git a/lib/util/wscript_build b/lib/util/wscript_build index 799d7b1ce74d..91fa04ddee3b 100644 --- a/lib/util/wscript_build +++ b/lib/util/wscript_build @@ -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', -- 2.34.1