From 5150a50d0d55e76e32295080dac53cb4324bce00 Mon Sep 17 00:00:00 2001 From: Bruno Haible Date: Mon, 20 Oct 2003 11:03:53 +0000 Subject: [PATCH] New module 'wait-process'. --- ChangeLog | 5 + MODULES.html.sh | 2 +- lib/ChangeLog | 5 + lib/wait-process.c | 305 +++++++++++++++++++++++++++++++++++++++++++ lib/wait-process.h | 56 ++++++++ m4/ChangeLog | 4 + m4/wait-process.m4 | 15 +++ modules/wait-process | 29 ++++ 8 files changed, 420 insertions(+), 1 deletion(-) create mode 100644 lib/wait-process.c create mode 100644 lib/wait-process.h create mode 100644 m4/wait-process.m4 create mode 100644 modules/wait-process diff --git a/ChangeLog b/ChangeLog index a7dffa3d18..2c9791cef5 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +2003-10-20 Bruno Haible + + * modules/wait-process: New file. + * MODULES.html.sh (func_all_modules): Add wait-process. + 2003-10-16 Paul Eggert * README: Mention that gnulib assumes that (foo *) NULL + 0 == NULL. diff --git a/MODULES.html.sh b/MODULES.html.sh index e40ea6d202..45aa2abd52 100755 --- a/MODULES.html.sh +++ b/MODULES.html.sh @@ -1868,7 +1868,7 @@ func_all_modules () func_begin_table func_module findprog - #func_module wait-process + func_module wait-process #func_module execute #func_module pipe #func_module sh-quote diff --git a/lib/ChangeLog b/lib/ChangeLog index 85a79f8980..41bc46c2fb 100644 --- a/lib/ChangeLog +++ b/lib/ChangeLog @@ -1,3 +1,8 @@ +2003-10-20 Bruno Haible + + * wait-process.h: New file, from GNU gettext. + * wait-process.c: New file, from GNU gettext. + 2003-10-19 Jim Meyering * vasnprintf.c (vasnprintf): Work around losing snprintf on HPUX 10.20. diff --git a/lib/wait-process.c b/lib/wait-process.c new file mode 100644 index 0000000000..138f25c647 --- /dev/null +++ b/lib/wait-process.c @@ -0,0 +1,305 @@ +/* Waiting for a subprocess to finish. + Copyright (C) 2001-2003 Free Software Foundation, Inc. + Written by Bruno Haible , 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 2, 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, write to the Free Software Foundation, + Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Specification. */ +#include "wait-process.h" + +#include +#include +#include +#include + +#include + +#if defined _MSC_VER || defined __MINGW32__ + +/* Native Woe32 API. */ +#include +#define waitpid(pid,statusp,options) _cwait (statusp, pid, WAIT_CHILD) +#define WAIT_T int +#define WTERMSIG(x) ((x) & 0xff) /* or: SIGABRT ?? */ +#define WCOREDUMP(x) 0 +#define WEXITSTATUS(x) (((x) >> 8) & 0xff) /* or: (x) ?? */ +#define WIFSIGNALED(x) (WTERMSIG (x) != 0) /* or: ((x) == 3) ?? */ +#define WIFEXITED(x) (WTERMSIG (x) == 0) /* or: ((x) != 3) ?? */ +#define WIFSTOPPED(x) 0 + +#else + +/* Unix API. */ +#include +/* On Linux, WEXITSTATUS are bits 15..8 and WTERMSIG are bits 7..0, while + BeOS uses the contrary. Therefore we use the abstract macros. */ +#if HAVE_UNION_WAIT +# define WAIT_T union wait +# ifndef WTERMSIG +# define WTERMSIG(x) ((x).w_termsig) +# endif +# ifndef WCOREDUMP +# define WCOREDUMP(x) ((x).w_coredump) +# endif +# ifndef WEXITSTATUS +# define WEXITSTATUS(x) ((x).w_retcode) +# endif +#else +# define WAIT_T int +# ifndef WTERMSIG +# define WTERMSIG(x) ((x) & 0x7f) +# endif +# ifndef WCOREDUMP +# define WCOREDUMP(x) ((x) & 0x80) +# endif +# ifndef WEXITSTATUS +# define WEXITSTATUS(x) (((x) >> 8) & 0xff) +# endif +#endif +/* For valid x, exactly one of WIFSIGNALED(x), WIFEXITED(x), WIFSTOPPED(x) + is true. */ +#ifndef WIFSIGNALED +# define WIFSIGNALED(x) (WTERMSIG (x) != 0 && WTERMSIG(x) != 0x7f) +#endif +#ifndef WIFEXITED +# define WIFEXITED(x) (WTERMSIG (x) == 0) +#endif +#ifndef WIFSTOPPED +# define WIFSTOPPED(x) (WTERMSIG (x) == 0x7f) +#endif +/* Note that portable applications may access + WTERMSIG(x) only if WIFSIGNALED(x) is true, and + WEXITSTATUS(x) only if WIFEXITED(x) is true. */ + +#endif + +#include "error.h" +#include "exit.h" +#include "fatal-signal.h" +#include "xalloc.h" +#include "gettext.h" + +#define _(str) gettext (str) + +#define SIZEOF(a) (sizeof(a) / sizeof(a[0])) + + +/* 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 () +{ + 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 = + 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 inline 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 null_stderr, + bool slave_process, bool exit_on_error) +{ + /* waitpid() is just as portable as wait() nowadays. */ + WAIT_T status; + + *(int *) &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. */ + *(int *) &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. 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 (exit_on_error || !null_stderr) + error (exit_on_error ? EXIT_FAILURE : 0, 0, + _("%s subprocess got fatal signal %d"), + progname, (int) WTERMSIG (status)); + return 127; + } + 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); +} diff --git a/lib/wait-process.h b/lib/wait-process.h new file mode 100644 index 0000000000..2200142636 --- /dev/null +++ b/lib/wait-process.h @@ -0,0 +1,56 @@ +/* Waiting for a subprocess to finish. + Copyright (C) 2001-2003 Free Software Foundation, Inc. + Written by Bruno Haible , 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 2, 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, write to the Free Software Foundation, + Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +#ifndef _WAIT_PROCESS_H +#define _WAIT_PROCESS_H + +/* Get pid_t. */ +#include +#ifdef HAVE_UNISTD_H +#include +#endif +#include + +#include + + +#ifdef __cplusplus +extern "C" { +#endif + + +/* 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. */ +extern int wait_subprocess (pid_t child, const char *progname, + bool null_stderr, + bool slave_process, bool exit_on_error); + +/* 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. */ +extern void register_slave_subprocess (pid_t child); + + +#ifdef __cplusplus +} +#endif + + +#endif /* _WAIT_PROCESS_H */ diff --git a/m4/ChangeLog b/m4/ChangeLog index 8a27acfca1..13f4349268 100644 --- a/m4/ChangeLog +++ b/m4/ChangeLog @@ -1,3 +1,7 @@ +2003-10-20 Bruno Haible + + * wait-process.m4: New file. + 2003-10-14 Bruno Haible * sig_atomic_t: New file, from GNU gettext. diff --git a/m4/wait-process.m4 b/m4/wait-process.m4 new file mode 100644 index 0000000000..963d401857 --- /dev/null +++ b/m4/wait-process.m4 @@ -0,0 +1,15 @@ +# wait-process.m4 serial 1 +dnl Copyright (C) 2003 Free Software Foundation, Inc. +dnl This file is free software, distributed under the terms of the GNU +dnl General Public License. As a special exception to the GNU General +dnl Public License, this file may be distributed as part of a program +dnl that contains a configuration script generated by Autoconf, under +dnl the same distribution terms as the rest of that program. + +AC_DEFUN([gl_WAIT_PROCESS], +[ + dnl Prerequisites of lib/wait-process.h. + AC_CHECK_HEADERS_ONCE(unistd.h) + dnl Prerequisites of lib/wait-process.c. + AC_REQUIRE([gt_TYPE_SIG_ATOMIC_T]) +]) diff --git a/modules/wait-process b/modules/wait-process new file mode 100644 index 0000000000..9e397b0553 --- /dev/null +++ b/modules/wait-process @@ -0,0 +1,29 @@ +Description: +Waiting for a subprocess to finish. + +Files: +lib/wait-process.h +lib/wait-process.c +m4/wait-process.m4 +m4/sig_atomic_t.m4 + +Depends-on: +fatal-signal +error +exit +xalloc +gettext +stdbool + +configure.ac: +gl_WAIT_PROCESS + +Makefile.am: +lib_SOURCES += wait-process.h wait-process.c + +Include: +"wait-process.h" + +Maintainer: +Bruno Haible + -- 2.30.2