From cfb3906f210bec09f48f5d48511b72064153311a Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Sat, 21 Jun 2008 14:32:55 -0600 Subject: [PATCH] New module sigaction, for mingw. * modules/sigaction: New module... * modules/sigaction-tests: ...and its test. * m4/sigaction.m4: New file. * lib/sigaction.c: Likewise. * tests/test-sigaction.c: Likewise. * m4/signal_h.m4 (gl_SIGNAL_H_DEFAULTS): Add sigaction variables. * modules/signal (Makefile.am): Likewise. * lib/signal.in.h (!@HAVE_SIGACTION@): Define replacements when needed. * doc/posix-headers/signal.texi (signal.h): Mention provided types. * doc/posix-functions/siginterrupt.texi (siginterrupt): Mention that sigaction is preferable. * doc/posix-functions/sigaction.texi (sigaction): Mention new module. * MODULES.html.sh (Support for systems lacking POSIX:2001): Add sigaction. Signed-off-by: Eric Blake --- ChangeLog | 19 ++++ MODULES.html.sh | 1 + doc/posix-functions/sigaction.texi | 25 ++++- doc/posix-functions/siginterrupt.texi | 3 + doc/posix-headers/signal.texi | 14 ++- lib/sigaction.c | 187 ++++++++++++++++++++++++++++++++++ lib/signal.in.h | 62 ++++++++++- m4/sigaction.m4 | 35 +++++++ m4/signal_h.m4 | 7 +- modules/sigaction | 25 +++++ modules/sigaction-tests | 10 ++ modules/signal | 3 + tests/test-sigaction.c | 97 ++++++++++++++++++ 13 files changed, 479 insertions(+), 9 deletions(-) create mode 100644 lib/sigaction.c create mode 100644 m4/sigaction.m4 create mode 100644 modules/sigaction create mode 100644 modules/sigaction-tests create mode 100644 tests/test-sigaction.c diff --git a/ChangeLog b/ChangeLog index 7d08ae6ef..e3b0279d4 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,24 @@ 2008-06-21 Eric Blake + New module sigaction, for mingw. + * modules/sigaction: New module... + * modules/sigaction-tests: ...and its test. + * m4/sigaction.m4: New file. + * lib/sigaction.c: Likewise. + * tests/test-sigaction.c: Likewise. + * m4/signal_h.m4 (gl_SIGNAL_H_DEFAULTS): Add sigaction variables. + * modules/signal (Makefile.am): Likewise. + * lib/signal.in.h (!@HAVE_SIGACTION@): Define replacements when + needed. + * doc/posix-headers/signal.texi (signal.h): Mention provided + types. + * doc/posix-functions/siginterrupt.texi (siginterrupt): Mention + that sigaction is preferable. + * doc/posix-functions/sigaction.texi (sigaction): Mention new + module. + * MODULES.html.sh (Support for systems lacking POSIX:2001): Add + sigaction. + Improve robustness of sigprocmask by overriding signal. * lib/signal.in.h (rpl_signal): Override signal when sigprocmask is in use. diff --git a/MODULES.html.sh b/MODULES.html.sh index f82e95763..8f3aa4f5b 100755 --- a/MODULES.html.sh +++ b/MODULES.html.sh @@ -2131,6 +2131,7 @@ func_all_modules () func_module rename func_module rmdir func_module search + func_module sigaction func_module sigprocmask func_module socklen func_module ssize_t diff --git a/doc/posix-functions/sigaction.texi b/doc/posix-functions/sigaction.texi index 80e3b2c65..56795d900 100644 --- a/doc/posix-functions/sigaction.texi +++ b/doc/posix-functions/sigaction.texi @@ -4,17 +4,38 @@ POSIX specification: @url{http://www.opengroup.org/susv3xsh/sigaction.html} -Gnulib module: --- +Gnulib module: sigaction Portability problems fixed by Gnulib: @itemize +@item +This function is missing on some platforms: +mingw. @end itemize Portability problems not fixed by Gnulib: @itemize @item -This function is missing on some platforms: +POSIX recommends that when specifying SA_RESETHAND, SA_NODEFER must +also be specified. + +@item +Support for SA_ONSTACK is missing on some platforms: +mingw, cygwin. + +@item +Support for SA_SIGINFO is missing on some platforms: +mingw, Interix 3.5. + +@item +Support for SIGCHLD, and thus for SA_NOCLDSTOP and SA_NOCLDWAIT, is +missing on some platforms: mingw. + +@item +Support for SA_RESTART is missing on some platforms: +mingw. + @item The symbolic value @code{SIG_IGN} for the @code{SIGCHLD} signal is equivalent to a signal handler diff --git a/doc/posix-functions/siginterrupt.texi b/doc/posix-functions/siginterrupt.texi index 48223ab27..68ac3c5f2 100644 --- a/doc/posix-functions/siginterrupt.texi +++ b/doc/posix-functions/siginterrupt.texi @@ -15,4 +15,7 @@ Portability problems not fixed by Gnulib: @item This function is missing on some platforms: Solaris 2.5.1, mingw, Interix 3.5, BeOS. + +@item +POSIX recommends using @code{sigaction} with SA_NODEFER instead. @end itemize diff --git a/doc/posix-headers/signal.texi b/doc/posix-headers/signal.texi index 272e64663..cb4f580e8 100644 --- a/doc/posix-headers/signal.texi +++ b/doc/posix-headers/signal.texi @@ -3,12 +3,24 @@ POSIX specification: @url{http://www.opengroup.org/susv3xbd/signal.h.html} -Gnulib module: --- +Gnulib module: signal Portability problems fixed by Gnulib: @itemize +@item +@code{sigset_t} is only declared in on some platforms: +mingw. + +@item +@code{struct sigaction} and @code{siginfo_t} are missing on some +platforms: +mingw. @end itemize Portability problems not fixed by Gnulib: @itemize +@item +@code{struct sigaction} lacks the @code{sa_sigaction} member on some +platforms; this can also be detected by whether @code{SA_SIGINFO} is defined: +Interix 3.5. @end itemize diff --git a/lib/sigaction.c b/lib/sigaction.c new file mode 100644 index 000000000..9b0c92fda --- /dev/null +++ b/lib/sigaction.c @@ -0,0 +1,187 @@ +/* POSIX compatible signal blocking. + Copyright (C) 2008 Free Software Foundation, Inc. + Written by Eric Blake , 2008. + + 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 + +/* Specification. */ +#include + +#include +#include +#include + +/* This implementation of sigaction is tailored to Woe32 behavior: + signal() has SysV semantics (ie. the handler is uninstalled before + it is invoked). This is an inherent data race if an asynchronous + signal is sent twice in a row before we can reinstall our handler, + but there's nothing we can do about it. Meanwhile, sigprocmask() + is not present, and while we can use the gnulib replacement to + provide critical sections, it too suffers from potential data races + in the face of an ill-timed asynchronous signal. And we compound + the situation by reading static storage in a signal handler, which + POSIX warns is not generically async-signal-safe. Oh well. + + Additionally, SIGCHLD is not defined, so we don't implement + SA_NOCLDSTOP or SA_NOCLDWAIT; sigaltstack() is not present, so we + don't implement SA_ONSTACK; and siginterrupt() is not present, so + we don't implement SA_RESTART. Supporting SA_SIGINFO is impossible + to do portably. + + POSIX states that an application should not mix signal() and + sigaction(). We support the use of signal() within the gnulib + sigprocmask() substitute, but all other application code linked + with this module should stick with only sigaction(). */ + +/* Check some of our assumptions. */ +#if defined SIGCHLD || defined HAVE_SIGALTSTACK || defined HAVE_SIGINTERRUPT +# error "Revisit the assumptions made in the sigaction module" +#endif + +/* Out-of-range substitutes make a good fallback for uncatchable + signals. */ +#ifndef SIGKILL +# define SIGKILL (-1) +#endif +#ifndef SIGSTOP +# define SIGSTOP (-1) +#endif + +/* A signal handler. */ +typedef void (*handler_t) (int signal); + +/* Set of current actions. If sa_handler for an entry is NULL, then + that signal is not currently handled by the sigaction handler. */ +static struct sigaction volatile action_array[NSIG] /* = 0 */; + +/* Signal handler that is installed for signals. */ +static void +sigaction_handler (int sig) +{ + handler_t handler; + sigset_t mask; + sigset_t oldmask; + int saved_errno = errno; + if (sig < 0 || NSIG <= sig || !action_array[sig].sa_handler) + { + /* Unexpected situation; be careful to avoid recursive abort. */ + if (sig == SIGABRT) + signal (SIGABRT, SIG_DFL); + abort (); + } + + /* Reinstall the signal handler when required; otherwise update the + bookkeeping so that the user's handler may call sigaction and get + accurate results. We know the signal isn't currently blocked, or + we wouldn't be in its handler, therefore we know that we are not + interrupting a sigaction() call. There is a race where any + asynchronous instance of the same signal occurring before we + reinstall the handler will trigger the default handler; oh + well. */ + handler = action_array[sig].sa_handler; + if ((action_array[sig].sa_flags & SA_RESETHAND) == 0) + signal (sig, sigaction_handler); + else + action_array[sig].sa_handler = NULL; + + /* Block appropriate signals. */ + mask = action_array[sig].sa_mask; + if ((action_array[sig].sa_flags & SA_NODEFER) == 0) + sigaddset (&mask, sig); + sigprocmask (SIG_BLOCK, &mask, &oldmask); + + /* Invoke the user's handler, then restore prior mask. */ + errno = saved_errno; + handler (sig); + saved_errno = errno; + sigprocmask (SIG_SETMASK, &oldmask, NULL); + errno = saved_errno; +} + +/* Change and/or query the action that will be taken on delivery of + signal SIG. If not NULL, ACT describes the new behavior. If not + NULL, OACT is set to the prior behavior. Return 0 on success, or + set errno and return -1 on failure. */ +int +sigaction (int sig, const struct sigaction *restrict act, + struct sigaction *restrict oact) +{ + sigset_t mask; + sigset_t oldmask; + int saved_errno; + + if (sig < 0 || NSIG <= sig || sig == SIGKILL || sig == SIGSTOP + || (act && act->sa_handler == SIG_ERR)) + { + errno = EINVAL; + return -1; + } + + /* POSIX requires sigaction() to be async-signal-safe. In other + words, if an asynchronous signal can occur while we are anywhere + inside this function, the user's handler could then call + sigaction() recursively and expect consistent results. We meet + this rule by using sigprocmask to block all signals before + modifying any data structure that could be read from a signal + handler; this works since we know that the gnulib sigprocmask + replacement does not try to use sigaction() from its handler. */ + if (!act && !oact) + return 0; + sigfillset (&mask); + sigprocmask (SIG_BLOCK, &mask, &oldmask); + if (oact) + { + if (action_array[sig].sa_handler) + *oact = action_array[sig]; + else + { + /* Safe to change the handler at will here, since all + signals are currently blocked. */ + oact->sa_handler = signal (sig, SIG_DFL); + if (oact->sa_handler == SIG_ERR) + goto failure; + signal (sig, oact->sa_handler); + oact->sa_flags = SA_RESETHAND | SA_NODEFER; + sigemptyset (&oact->sa_mask); + } + } + + if (act) + { + /* Safe to install the handler before updating action_array, + since all signals are currently blocked. */ + if (act->sa_handler == SIG_DFL || act->sa_handler == SIG_IGN) + { + if (signal (sig, act->sa_handler) == SIG_ERR) + goto failure; + action_array[sig].sa_handler = NULL; + } + else + { + if (signal (sig, sigaction_handler) == SIG_ERR) + goto failure; + action_array[sig] = *act; + } + } + sigprocmask (SIG_SETMASK, &oldmask, NULL); + return 0; + + failure: + saved_errno = errno; + sigprocmask (SIG_SETMASK, &oldmask, NULL); + errno = saved_errno; + return -1; +} diff --git a/lib/signal.in.h b/lib/signal.in.h index eb16afb27..30fa92900 100644 --- a/lib/signal.in.h +++ b/lib/signal.in.h @@ -34,9 +34,7 @@ /* The definition of GL_LINK_WARNING is copied here. */ /* Mingw defines sigset_t not in , but in . */ -#if !@HAVE_POSIX_SIGNALBLOCKING@ -# include -#endif +#include #ifdef __cplusplus extern "C" { @@ -91,7 +89,63 @@ extern int sigprocmask (int operation, const sigset_t *set, sigset_t *old_set); handler. */ extern void (*signal (int sig, void (*func) (int))) (int); -#endif +#endif /* !@HAVE_POSIX_SIGNALBLOCKING@ */ + +#if !@HAVE_SIGACTION@ + +# if !@HAVE_SIGINFO_T@ +/* Present to allow compilation, but unsupported by gnulib. */ +union sigval +{ + int sival_int; + void *sival_ptr; +}; + +/* Present to allow compilation, but unsupported by gnulib. */ +struct siginfo_t +{ + int si_signo; + int si_code; + int si_errno; + pid_t si_pid; + uid_t si_uid; + void *si_addr; + int si_status; + long si_band; + union sigval si_value; +}; +typedef struct siginfo_t siginfo_t; +# endif /* !@HAVE_SIGINFO_T@ */ + + /* Due to autoconf conventions, we can't tell if HAVE_SIGACTION + means we have the type or means we have the function. We assume + that all implementations either have both or neither. */ + +struct sigaction +{ + union + { + void (*_sa_handler) (int); + /* Present to allow compilation, but unsupported by gnulib. POSIX + says that implementations may, but not must, make sa_sigaction + overlap with sa_handler, but we know of no implementation where + they do not overlap. */ + void (*_sa_sigaction) (int, siginfo_t *, void *); + } _sa_func; + sigset_t sa_mask; + /* Not all POSIX flags are supported. */ + int sa_flags; +}; +# define sa_handler _sa_func._sa_handler +# define sa_sigaction _sa_func._sa_sigaction +/* Unsupported flags are not present. */ +# define SA_RESETHAND 1 +# define SA_NODEFER 2 + +extern int sigaction (int, const struct sigaction *restrict, + struct sigaction *restrict); + +#endif /* !@HAVE_SIGACTION@ */ #ifdef __cplusplus diff --git a/m4/sigaction.m4 b/m4/sigaction.m4 new file mode 100644 index 000000000..2f1a4cd8b --- /dev/null +++ b/m4/sigaction.m4 @@ -0,0 +1,35 @@ +# sigaction.m4 serial 1 +dnl Copyright (C) 2008 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +# Determine if sigaction interface is present. +AC_DEFUN([gl_SIGACTION], +[ + dnl Due to autoconf conventions, we can't tell if HAVE_SIGACTION + dnl means we have the type or means we have the function. We assume + dnl that all implementations either have both or neither. + AC_REPLACE_FUNCS([sigaction]) + if test $ac_cv_func_sigaction = no ; then + HAVE_SIGACTION=0 + AC_SUBST([HAVE_SIGACTION]) + gl_PREREQ_SIGACTION + fi +]) + +# Prerequisites of the part of lib/signal.in.h and of lib/sigprocmask.c. +AC_DEFUN([gl_PREREQ_SIGACTION], +[ + AC_REQUIRE([AC_C_RESTRICT]) + AC_REQUIRE([AC_TYPE_UID_T]) + AC_REQUIRE([gl_SIGNAL_H_DEFAULTS]) + AC_CHECK_FUNCS_ONCE([sigaltstack siginterrupt]) + AC_CHECK_TYPES([siginfo_t], [], [], [[ +#include + ]]) + if test $ac_cv_type_siginfo_t = no; then + HAVE_SIGINFO_T=0 + AC_SUBST([HAVE_SIGINFO_T]) + fi +]) diff --git a/m4/signal_h.m4 b/m4/signal_h.m4 index 37ebca99a..bf2bad41e 100644 --- a/m4/signal_h.m4 +++ b/m4/signal_h.m4 @@ -1,5 +1,5 @@ -# signal_h.m4 serial 3 -dnl Copyright (C) 2007 Free Software Foundation, Inc. +# signal_h.m4 serial 4 +dnl Copyright (C) 2007, 2008 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, dnl with or without modifications, as long as this notice is preserved. @@ -20,7 +20,10 @@ AC_DEFUN([gl_SIGNAL_MODULE_INDICATOR], AC_DEFUN([gl_SIGNAL_H_DEFAULTS], [ GNULIB_SIGPROCMASK=0; AC_SUBST([GNULIB_SIGPROCMASK]) + GNULIB_SIGACTION=0; AC_SUBST([GNULIB_SIGACTION]) dnl Assume proper GNU behavior unless another module says otherwise. HAVE_POSIX_SIGNALBLOCKING=1; AC_SUBST([HAVE_POSIX_SIGNALBLOCKING]) HAVE_SIGSET_T=1; AC_SUBST([HAVE_SIGSET_T]) + HAVE_SIGINFO_T=1; AC_SUBST([HAVE_SIGINFO_T]) + HAVE_SIGACTION=1; AC_SUBST([HAVE_SIGACTION]) ]) diff --git a/modules/sigaction b/modules/sigaction new file mode 100644 index 000000000..60c0e8c0f --- /dev/null +++ b/modules/sigaction @@ -0,0 +1,25 @@ +Description: +POSIX compatible signal handlers. + +Files: +lib/sigaction.c +m4/sigaction.m4 + +Depends-on: +signal +sigprocmask + +configure.ac: +gl_SIGACTION +gl_SIGNAL_MODULE_INDICATOR([sigaction]) + +Makefile.am: + +Include: + + +License: +LGPL + +Maintainer: +Eric Blake diff --git a/modules/sigaction-tests b/modules/sigaction-tests new file mode 100644 index 000000000..5084e9e3f --- /dev/null +++ b/modules/sigaction-tests @@ -0,0 +1,10 @@ +Files: +tests/test-sigaction.c + +Depends-on: + +configure.ac: + +Makefile.am: +TESTS += test-sigaction +check_PROGRAMS += test-sigaction diff --git a/modules/signal b/modules/signal index c96de909f..afe6ab3b4 100644 --- a/modules/signal +++ b/modules/signal @@ -23,8 +23,11 @@ signal.h: signal.in.h sed -e 's/@''INCLUDE_NEXT''@/$(INCLUDE_NEXT)/g' \ -e 's|@''NEXT_SIGNAL_H''@|$(NEXT_SIGNAL_H)|g' \ -e 's|@''GNULIB_SIGPROCMASK''@|$(GNULIB_SIGPROCMASK)|g' \ + -e 's|@''GNULIB_SIGACTION''@|$(GNULIB_SIGACTION)|g' \ -e 's|@''HAVE_POSIX_SIGNALBLOCKING''@|$(HAVE_POSIX_SIGNALBLOCKING)|g' \ -e 's|@''HAVE_SIGSET_T''@|$(HAVE_SIGSET_T)|g' \ + -e 's|@''HAVE_SIGINFO_T''@|$(HAVE_SIGINFO_T)|g' \ + -e 's|@''HAVE_SIGACTION''@|$(HAVE_SIGACTION)|g' \ -e '/definition of GL_LINK_WARNING/r $(LINK_WARNING_H)' \ < $(srcdir)/signal.in.h; \ } > $@-t diff --git a/tests/test-sigaction.c b/tests/test-sigaction.c new file mode 100644 index 000000000..81abe8919 --- /dev/null +++ b/tests/test-sigaction.c @@ -0,0 +1,97 @@ +/* Test of sigaction() function. + Copyright (C) 2008 Free Software Foundation, Inc. + + 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 . */ + +/* Written by Eric Blake , 2008. */ + +#include + +#include + +#include +#include + +#define ASSERT(expr) \ + do \ + { \ + if (!(expr)) \ + { \ + fprintf (stderr, "%s:%d: assertion failed\n", __FILE__, __LINE__); \ + fflush (stderr); \ + signal (SIGABRT, SIG_DFL); \ + abort (); \ + } \ + } \ + while (0) + +#ifndef SA_SIGINFO +# define SA_SIGINFO 0 +#endif + +/* This test is unsafe in the presence of an asynchronous SIGABRT, + because we install a signal-handler that is intentionally not + async-safe. Hopefully, this does not lead to too many reports of + false failures, since people don't generally use 'kill -s SIGABRT' + to end a runaway program. */ + +static void +handler (int sig) +{ + static int entry_count; + struct sigaction sa; + ASSERT (sig == SIGABRT); + ASSERT (sigaction (SIGABRT, NULL, &sa) == 0); + ASSERT ((sa.sa_flags & SA_SIGINFO) == 0); + switch (entry_count++) + { + case 0: + ASSERT ((sa.sa_flags & SA_RESETHAND) == 0); + ASSERT (sa.sa_handler == handler); + break; + case 1: + ASSERT (sa.sa_handler == SIG_DFL); + break; + default: + ASSERT (0); + } +} + +int +main (int argc, char *argv[]) +{ + struct sigaction sa; + struct sigaction old_sa; + sa.sa_handler = handler; + sa.sa_flags = 0; + ASSERT (sigemptyset (&sa.sa_mask) == 0); + ASSERT (sigaction (SIGABRT, &sa, NULL) == 0); + ASSERT (raise (SIGABRT) == 0); + sa.sa_flags = SA_RESETHAND | SA_NODEFER; + ASSERT (sigaction (SIGABRT, &sa, &old_sa) == 0); + ASSERT (old_sa.sa_flags == 0); + ASSERT (old_sa.sa_handler == handler); + ASSERT (raise (SIGABRT) == 0); + sa.sa_handler = SIG_DFL; + ASSERT (sigaction (SIGABRT, &sa, &old_sa) == 0); + ASSERT ((old_sa.sa_flags & SA_SIGINFO) == 0); + ASSERT (old_sa.sa_handler == SIG_DFL); + sa.sa_handler = SIG_IGN; + ASSERT (sigaction (SIGABRT, &sa, NULL) == 0); + ASSERT (raise (SIGABRT) == 0); + ASSERT (sigaction (SIGABRT, NULL, &old_sa) == 0); + ASSERT (old_sa.sa_handler == SIG_IGN); + ASSERT (raise (SIGABRT) == 0); + return 0; +} -- 2.11.0