| /* Waiting for a subprocess to finish. |
| Copyright (C) 2001-2003, 2005-2012 Free Software Foundation, Inc. |
| Written by Bruno Haible <[email protected]>, 2001. |
| |
| 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 <config.h> |
| |
| /* Specification. */ |
| #include "wait-process.h" |
| |
| #include <errno.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <signal.h> |
| |
| #include <sys/types.h> |
| #include <sys/wait.h> |
| |
| #include "error.h" |
| #include "fatal-signal.h" |
| #include "xalloc.h" |
| #include "gettext.h" |
| |
| #define _(str) gettext (str) |
| |
| #define SIZEOF(a) (sizeof(a) / sizeof(a[0])) |
| |
| |
| #if defined _MSC_VER || defined __MINGW32__ |
| |
| #define WIN32_LEAN_AND_MEAN |
| #include <windows.h> |
| |
| /* The return value of spawnvp() is really a process handle as returned |
| by CreateProcess(). Therefore we can kill it using TerminateProcess. */ |
| #define kill(pid,sig) TerminateProcess ((HANDLE) (pid), sig) |
| |
| #endif |
| |
| |
| /* Type of an entry in the slaves array. |
| The 'used' bit determines whether this entry is currently in use. |
| (If pid_t was an atomic type like sig_atomic_t, we could just set the |
| 'child' field to 0 when unregistering a slave process, and wouldn't need |
| the 'used' field.) |
| The 'used' and 'child' fields are accessed from within the cleanup_slaves() |
| action, therefore we mark them as 'volatile'. */ |
| typedef struct |
| { |
| volatile sig_atomic_t used; |
| volatile pid_t child; |
| } |
| slaves_entry_t; |
| |
| /* The registered slave subprocesses. */ |
| static slaves_entry_t static_slaves[32]; |
| static slaves_entry_t * volatile slaves = static_slaves; |
| static sig_atomic_t volatile slaves_count = 0; |
| static size_t slaves_allocated = SIZEOF (static_slaves); |
| |
| /* The termination signal for slave subprocesses. |
| 2003-10-07: Terminator becomes Governator. */ |
| #ifdef SIGHUP |
| # define TERMINATOR SIGHUP |
| #else |
| # define TERMINATOR SIGTERM |
| #endif |
| |
| /* The cleanup action. It gets called asynchronously. */ |
| static void |
| cleanup_slaves (void) |
| { |
| for (;;) |
| { |
| /* Get the last registered slave. */ |
| size_t n = slaves_count; |
| if (n == 0) |
| break; |
| n--; |
| slaves_count = n; |
| /* Skip unused entries in the slaves array. */ |
| if (slaves[n].used) |
| { |
| pid_t slave = slaves[n].child; |
| |
| /* Kill the slave. */ |
| kill (slave, TERMINATOR); |
| } |
| } |
| } |
| |
| /* Register a subprocess as being a slave process. This means that the |
| subprocess will be terminated when its creator receives a catchable fatal |
| signal or exits normally. Registration ends when wait_subprocess() |
| notices that the subprocess has exited. */ |
| void |
| register_slave_subprocess (pid_t child) |
| { |
| static bool cleanup_slaves_registered = false; |
| if (!cleanup_slaves_registered) |
| { |
| atexit (cleanup_slaves); |
| at_fatal_signal (cleanup_slaves); |
| cleanup_slaves_registered = true; |
| } |
| |
| /* Try to store the new slave in an unused entry of the slaves array. */ |
| { |
| slaves_entry_t *s = slaves; |
| slaves_entry_t *s_end = s + slaves_count; |
| |
| for (; s < s_end; s++) |
| if (!s->used) |
| { |
| /* The two uses of 'volatile' in the slaves_entry_t type above |
| (and ISO C 99 section 5.1.2.3.(5)) ensure that we mark the |
| entry as used only after the child pid has been written to the |
| memory location s->child. */ |
| s->child = child; |
| s->used = 1; |
| return; |
| } |
| } |
| |
| if (slaves_count == slaves_allocated) |
| { |
| /* Extend the slaves array. Note that we cannot use xrealloc(), |
| because then the cleanup_slaves() function could access an already |
| deallocated array. */ |
| slaves_entry_t *old_slaves = slaves; |
| size_t new_slaves_allocated = 2 * slaves_allocated; |
| slaves_entry_t *new_slaves = |
| (slaves_entry_t *) |
| malloc (new_slaves_allocated * sizeof (slaves_entry_t)); |
| if (new_slaves == NULL) |
| { |
| /* xalloc_die() will call exit() which will invoke cleanup_slaves(). |
| Additionally we need to kill child, because it's not yet among |
| the slaves list. */ |
| kill (child, TERMINATOR); |
| xalloc_die (); |
| } |
| memcpy (new_slaves, old_slaves, |
| slaves_allocated * sizeof (slaves_entry_t)); |
| slaves = new_slaves; |
| slaves_allocated = new_slaves_allocated; |
| /* Now we can free the old slaves array. */ |
| if (old_slaves != static_slaves) |
| free (old_slaves); |
| } |
| /* The three uses of 'volatile' in the types above (and ISO C 99 section |
| 5.1.2.3.(5)) ensure that we increment the slaves_count only after the |
| new slave and its 'used' bit have been written to the memory locations |
| that make up slaves[slaves_count]. */ |
| slaves[slaves_count].child = child; |
| slaves[slaves_count].used = 1; |
| slaves_count++; |
| } |
| |
| /* Unregister a child from the list of slave subprocesses. */ |
| static void |
| unregister_slave_subprocess (pid_t child) |
| { |
| /* The easiest way to remove an entry from a list that can be used by |
| an asynchronous signal handler is just to mark it as unused. For this, |
| we rely on sig_atomic_t. */ |
| slaves_entry_t *s = slaves; |
| slaves_entry_t *s_end = s + slaves_count; |
| |
| for (; s < s_end; s++) |
| if (s->used && s->child == child) |
| s->used = 0; |
| } |
| |
| |
| /* Wait for a subprocess to finish. Return its exit code. |
| If it didn't terminate correctly, exit if exit_on_error is true, otherwise |
| return 127. */ |
| int |
| wait_subprocess (pid_t child, const char *progname, |
| bool ignore_sigpipe, bool null_stderr, |
| bool slave_process, bool exit_on_error, |
| int *termsigp) |
| { |
| #if HAVE_WAITID && defined WNOWAIT && 0 |
| /* Commented out because waitid() without WEXITED and with WNOWAIT doesn't |
| work: On Solaris 7 and OSF/1 4.0, it returns -1 and sets errno = ECHILD, |
| and on HP-UX 10.20 it just hangs. */ |
| /* Use of waitid() with WNOWAIT avoids a race condition: If slave_process is |
| true, and this process sleeps a very long time between the return from |
| waitpid() and the execution of unregister_slave_subprocess(), and |
| meanwhile another process acquires the same PID as child, and then - still |
| before unregister_slave_subprocess() - this process gets a fatal signal, |
| it would kill the other totally unrelated process. */ |
| siginfo_t info; |
| |
| if (termsigp != NULL) |
| *termsigp = 0; |
| for (;;) |
| { |
| if (waitid (P_PID, child, &info, WEXITED | (slave_process ? WNOWAIT : 0)) |
| < 0) |
| { |
| # ifdef EINTR |
| if (errno == EINTR) |
| continue; |
| # endif |
| if (exit_on_error || !null_stderr) |
| error (exit_on_error ? EXIT_FAILURE : 0, errno, |
| _("%s subprocess"), progname); |
| return 127; |
| } |
| |
| /* info.si_code is set to one of CLD_EXITED, CLD_KILLED, CLD_DUMPED, |
| CLD_TRAPPED, CLD_STOPPED, CLD_CONTINUED. Loop until the program |
| terminates. */ |
| if (info.si_code == CLD_EXITED |
| || info.si_code == CLD_KILLED || info.si_code == CLD_DUMPED) |
| break; |
| } |
| |
| /* The child process has exited or was signalled. */ |
| |
| if (slave_process) |
| { |
| /* Unregister the child from the list of slave subprocesses, so that |
| later, when we exit, we don't kill a totally unrelated process which |
| may have acquired the same pid. */ |
| unregister_slave_subprocess (child); |
| |
| /* Now remove the zombie from the process list. */ |
| for (;;) |
| { |
| if (waitid (P_PID, child, &info, WEXITED) < 0) |
| { |
| # ifdef EINTR |
| if (errno == EINTR) |
| continue; |
| # endif |
| if (exit_on_error || !null_stderr) |
| error (exit_on_error ? EXIT_FAILURE : 0, errno, |
| _("%s subprocess"), progname); |
| return 127; |
| } |
| break; |
| } |
| } |
| |
| switch (info.si_code) |
| { |
| case CLD_KILLED: |
| case CLD_DUMPED: |
| if (termsigp != NULL) |
| *termsigp = info.si_status; /* TODO: or info.si_signo? */ |
| # ifdef SIGPIPE |
| if (info.si_status == SIGPIPE && ignore_sigpipe) |
| return 0; |
| # endif |
| if (exit_on_error || (!null_stderr && termsigp == NULL)) |
| error (exit_on_error ? EXIT_FAILURE : 0, 0, |
| _("%s subprocess got fatal signal %d"), |
| progname, info.si_status); |
| return 127; |
| case CLD_EXITED: |
| if (info.si_status == 127) |
| { |
| if (exit_on_error || !null_stderr) |
| error (exit_on_error ? EXIT_FAILURE : 0, 0, |
| _("%s subprocess failed"), progname); |
| return 127; |
| } |
| return info.si_status; |
| default: |
| abort (); |
| } |
| #else |
| /* waitpid() is just as portable as wait() nowadays. */ |
| int status; |
| |
| if (termsigp != NULL) |
| *termsigp = 0; |
| status = 0; |
| for (;;) |
| { |
| int result = waitpid (child, &status, 0); |
| |
| if (result != child) |
| { |
| # ifdef EINTR |
| if (errno == EINTR) |
| continue; |
| # endif |
| # if 0 /* defined ECHILD */ |
| if (errno == ECHILD) |
| { |
| /* Child process nonexistent?! Assume it terminated |
| successfully. */ |
| status = 0; |
| break; |
| } |
| # endif |
| if (exit_on_error || !null_stderr) |
| error (exit_on_error ? EXIT_FAILURE : 0, errno, |
| _("%s subprocess"), progname); |
| return 127; |
| } |
| |
| /* One of WIFSIGNALED (status), WIFEXITED (status), WIFSTOPPED (status) |
| must always be true, since we did not specify WCONTINUED in the |
| waitpid() call. Loop until the program terminates. */ |
| if (!WIFSTOPPED (status)) |
| break; |
| } |
| |
| /* The child process has exited or was signalled. */ |
| |
| if (slave_process) |
| /* Unregister the child from the list of slave subprocesses, so that |
| later, when we exit, we don't kill a totally unrelated process which |
| may have acquired the same pid. */ |
| unregister_slave_subprocess (child); |
| |
| if (WIFSIGNALED (status)) |
| { |
| if (termsigp != NULL) |
| *termsigp = WTERMSIG (status); |
| # ifdef SIGPIPE |
| if (WTERMSIG (status) == SIGPIPE && ignore_sigpipe) |
| return 0; |
| # endif |
| if (exit_on_error || (!null_stderr && termsigp == NULL)) |
| error (exit_on_error ? EXIT_FAILURE : 0, 0, |
| _("%s subprocess got fatal signal %d"), |
| progname, (int) WTERMSIG (status)); |
| return 127; |
| } |
| if (!WIFEXITED (status)) |
| abort (); |
| if (WEXITSTATUS (status) == 127) |
| { |
| if (exit_on_error || !null_stderr) |
| error (exit_on_error ? EXIT_FAILURE : 0, 0, |
| _("%s subprocess failed"), progname); |
| return 127; |
| } |
| return WEXITSTATUS (status); |
| #endif |
| } |