diff --git a/bootstrap.conf b/bootstrap.conf index 427a5f7..7c2ffd9 100644 --- a/bootstrap.conf +++ b/bootstrap.conf @@ -124,8 +124,9 @@ stat stat-macros stat-size stat-time -stdckdint-h stdc_bit_width +stdckdint-h +stdcountof-h stdint-h stpcpy strcase diff --git a/src/Makefile.am b/src/Makefile.am index 4869b68..1cac35c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -47,8 +47,8 @@ diff3_SOURCES = diff3.c system.c sdiff_SOURCES = sdiff.c system.c diff_SOURCES = \ analyze.c context.c diff.c dir.c ed.c ifdef.c io.c \ - normal.c side.c system.c util.c -noinst_HEADERS = diff.h system.h + normal.c side.c syncsig.c system.c util.c +noinst_HEADERS = diff.h syncsig.h system.h MOSTLYCLEANFILES = paths.h paths.ht diff --git a/src/syncsig.c b/src/syncsig.c new file mode 100644 index 0000000..1f48301 --- /dev/null +++ b/src/syncsig.c @@ -0,0 +1,350 @@ +/* Synchronous signal handling + + Copyright 2025 Free Software Foundation, Inc. + + This file is part of GNU DIFF. + + 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 + +#include "syncsig.h" + +#include "minmax.h" + +#include +#include + +/* Use SA_NOCLDSTOP as a proxy for whether the sigaction machinery is + present. */ +#ifndef SA_NOCLDSTOP +# define SA_NOCLDSTOP 0 +# define sigprocmask(how, set, oset) 0 +# if ! HAVE_SIGINTERRUPT +# define siginterrupt(sig, flag) 0 +# endif +#endif + +#ifndef SA_RESTART +# define SA_RESTART 0 +#endif +#ifndef SIGSTOP +# define SIGSTOP 0 +#endif + +/* The set of signals that are caught. */ +static sigset_t caught_signals; + +/* The signals we can catch. + This includes all catchable GNU/Linux or POSIX signals that by + default are ignored, or that stop or terminate the process. + It also includes SIGQUIT since that can come from the terminal. + It excludes other signals that normally come from program failure. + If you modify this table, also modify signal_count's initializer. */ +static int const catchable[] = + { + /* SIGABRT is normally from program failure. */ + SIGALRM, + /* SIGBUS is normally from program failure. */ +#ifdef SIGCHLD + SIGCHLD, +#else +# define SIGCHLD 0 +#endif + /* SIGCLD (older platforms) is an alias for SIGCHLD. */ +#ifdef SIGCONT + SIGCONT, +#else +# define SIGCONT 0 +#endif + /* SIGEMT is normally from program failure. */ + /* SIGFPE is normally from program failure. */ + SIGHUP, + /* SIGILL is normally from program failure. */ + /* SIGINFO is an alias for SIGPWR on Linux. */ + SIGINT, + /* SIGIO is an alias for SIGPOLL. */ + /* SIGKILL can't be caught. */ +#ifdef SIGLOST + SIGLOST, +#else +# define SIGLOST 0 +#endif + SIGPIPE, +#ifdef SIGPOLL /* Removed from POSIX.1-2024; still in Linux. */ + SIGPOLL, +#else +# define SIGPOLL 0 +#endif +#ifdef SIGPROF /* Removed from POSIX.1-2024; still in Linux. */ + SIGPROF, +#else +# define SIGPROF 0 +#endif +#ifdef SIGPWR + SIGPWR, +#else +# define SIGPWR 0 +#endif + SIGQUIT, + /* SIGSEGV is normally from program failure. */ + /* Linux SIGSTKFLT is unused. */ + /* SIGSTOP can't be caught. */ + /* SIGSYS is normally from program failure. */ + SIGTERM, + /* SIGTRAP is normally from program failure. */ +#ifdef SIGTSTP + SIGTSTP, +#else +# define SIGTSTP 0 +#endif +#ifdef SIGTTIN + SIGTTIN, +#else +# define SIGTTIN 0 +#endif +#ifdef SIGTTOU + SIGTTOU, +#else +# define SIGTTOU 0 +#endif +#ifdef SIGURG + SIGURG, +#else +# define SIGURG 0 +#endif +#ifdef SIGUSR1 + SIGUSR1, +#else +# define SIGUSR1 0 +#endif +#ifdef SIGUSR2 + SIGUSR2, +#else +# define SIGUSR2 0 +#endif +#ifdef SIGVTALRM + SIGVTALRM, +#else +# define SIGVTALRM 0 +#endif +#ifdef SIGWINCH + SIGWINCH, +#else +# define SIGWINCH 0 +#endif +#ifdef SIGXCPU + SIGXCPU, +#else +# define SIGXCPU 0 +#endif +#ifdef SIGXFSZ + SIGXFSZ, +#else +# define SIGXFSZ 0 +#endif + }; + +/* Number of pending signals received, for each signal type. */ +static sig_atomic_t volatile signal_count[] = + { + /* Explicitly initialize, so that the table is large enough. */ + [SIGALRM] = 0, + [SIGCHLD] = 0, + [SIGCONT] = 0, + [SIGHUP] = 0, + [SIGINT] = 0, + [SIGLOST] = 0, + [SIGPIPE] = 0, + [SIGPOLL] = 0, + [SIGPROF] = 0, + [SIGPWR] = 0, + [SIGQUIT] = 0, + [SIGTERM] = 0, + [SIGTSTP] = 0, + [SIGTTIN] = 0, + [SIGTTOU] = 0, + [SIGURG] = 0, + [SIGUSR1] = 0, + [SIGUSR2] = 0, + [SIGVTALRM] = 0, + [SIGWINCH] = 0, + [SIGXCPU] = 0, + [SIGXFSZ] = 0, + }; + +/* This acts as bool though its type is sig_atomic_t. + If true, signal_count might contain nonzero entries. + If false, signal_count is all zero. + This is to speed up syncsig_poll when it returns 0. */ +static sig_atomic_t volatile possible_signal_count; + +/* Actions before syncsig_install was called. */ +#if SA_NOCLDSTOP +static struct sigaction oldact[countof (signal_count)]; +#else +static void (*oldact[countof (signal_count)]) (int); +#endif + +/* Record an asynchronous signal. This function is async-signal-safe. */ +static void +sighandler (int sig) +{ +#if !SA_NOCLDSTOP + /* An unavoidable race here: the default action might mistakenly + be taken before 'signal' is called. */ + signal (sig, sighandler); +#endif + + possible_signal_count = true; + signal_count[sig]++; +} + +void +syncsig_install (int flags) +{ + for (int i = 0; i < countof (signal_count); i++) + signal_count[i] = 0; + possible_signal_count = false; + + sigemptyset (&caught_signals); + + for (int i = 0; i < countof (catchable); i++) + { + int sig = catchable[i]; + + if (((sig == SIGTSTP) & !(flags & SYNCSIG_TSTP)) + | ((sig == SIGTTIN) & !(flags & SYNCSIG_TTIN)) + | ((sig == SIGTTOU) & !(flags & SYNCSIG_TTOU))) + continue; + +#if SA_NOCLDSTOP + sigaction (sig, nullptr, &oldact[sig]); + bool catching_sig = oldact[sig].sa_handler != SIG_IGN; +#else + oldact[i] = signal (sig, SIG_IGN); + bool catching_sig = oldact[i] != SIG_IGN; + if (catching_sig) + { + /* An unavoidable race here: SIG might be mistakenly ignored + before 'signal' is called. */ + signal (sig, sighandler); + siginterrupt (sig, 0); + } +#endif + if (catching_sig) + sigaddset (&caught_signals, sig); + } + +#if SA_NOCLDSTOP + struct sigaction act; + act.sa_handler = sighandler; + act.sa_mask = caught_signals; + act.sa_flags = SA_RESTART; + + for (int i = 0; i < countof (catchable); i++) + { + int sig = catchable[i]; + if (sigismember (&caught_signals, sig)) + sigaction (sig, &act, nullptr); + } +#endif +} + +void +syncsig_uninstall (void) +{ + for (int i = 0; i < countof (catchable); i++) + { + int sig = catchable[i]; + if (sigismember (&caught_signals, sig)) + { +#if SA_NOCLDSTOP + sigaction (sig, &oldact[sig], nullptr); +#else + signal (sig, oldact[sig]); +#endif + } + } +} + +int +syncsig_poll (void) +{ + int sig = 0; + + if (possible_signal_count) + { + /* This module uses static rather than thread-local storage, + so it is useful only in single-threaded programs, + and it uses sigprocmask rather than pthread_sigmask. */ + sigset_t oldset; + sigprocmask (SIG_BLOCK, &caught_signals, &oldset); + + for (int i = 0; ; i++) + { + if (i == countof (catchable)) + { + possible_signal_count = false; + break; + } + int s = catchable[i]; + int c = signal_count[s]; + if (c) + { + signal_count[s] = c - 1; + sig = s; + break; + } + } + + sigprocmask (SIG_SETMASK, &oldset, nullptr); + } + + return sig; +} + +void +syncsig_deliver (int sig) +{ +#if SA_NOCLDSTOP + struct sigaction act; +#else + void (*act) (int); +#endif + + if (sig == SIGTSTP) + sig = SIGSTOP; + else + { +#if SA_NOCLDSTOP + sigaction (sig, &oldact[sig], &act); +#else + act = signal (sig, oldact[sig]); +#endif + + } + + raise (sig); + + if (sig != SIGSTOP) + { + /* The program did not exit due to the raised signal, so continue. */ +#if SA_NOCLDSTOP + sigaction (sig, &act, nullptr); +#else + signal (sig, act); +#endif + } +} diff --git a/src/syncsig.h b/src/syncsig.h new file mode 100644 index 0000000..fae2f8e --- /dev/null +++ b/src/syncsig.h @@ -0,0 +1,60 @@ +/* Synchronous signal handling + + Copyright 2025 Free Software Foundation, Inc. + + This file is part of GNU DIFF. + + 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 Paul Eggert. */ + +/* Flags for syncsig_install. */ +enum + { + /* Also catch SIGTSTP, SIGTTIN, SIGTTOU, which by default stop the process. + These flags have no effect on platforms lacking these signals. */ + SYNCSIG_TSTP = (1 << 0), + SYNCSIG_TTIN = (1 << 1), + SYNCSIG_TTOU = (1 << 2), + }; + +/* Set up asynchronous signal handling according to FLAGS. + syncsig_install fails only on unusual platforms where + valid calls to functions like sigaction can fail; + if it fails, signal handling is in a weird state + and neither syncsig_process nor syncsig_uninstall should be called. */ +extern void syncsig_install (int flags); + +/* Return a signal number if a signal has arrived, zero otherwise. + After syncsig_install, there should not be an unbounded amount of + time between calls to this function, and its result should be dealt + with promptly. */ +extern int syncsig_poll (void); + +/* Do the action for SIG that would have been done + had syncsig_install not been called. + SIG should have recently been returned by syncsig_poll. + + For example, if SIG is SIGTSTP stop the process and return after + SIGCONT is delivered. Another example: kill the process if SIG is + SIGINT and SIGINT handling is the default. + + This function should be called only after syncsig_install. */ +extern void syncsig_deliver (int sig); + +/* Stop doing asynchronous signal handling, undoing syncsig_install. + This function should be called only after syncsig_install. + To deal with signals arriving just before calling this function, + call syncsig_poll afterwards. */ +extern void syncsig_uninstall (void); diff --git a/src/util.c b/src/util.c index a3dc315..a586511 100644 --- a/src/util.c +++ b/src/util.c @@ -27,31 +27,11 @@ #include #include #include +#include #include #include #include -#include - -/* Use SA_NOCLDSTOP as a proxy for whether the sigaction machinery is - present. */ -#ifndef SA_NOCLDSTOP -# define SA_NOCLDSTOP 0 -# define sigprocmask(How, Set, Oset) 0 -# if ! HAVE_SIGINTERRUPT -# define siginterrupt(sig, flag) 0 -# endif -#endif - -#ifndef SA_RESTART -# define SA_RESTART 0 -#endif -#ifndef SIGSTOP -# define SIGSTOP 0 -#endif -#ifndef SIGTSTP -# define SIGTSTP 0 -#endif char const pr_program[] = PR_PROGRAM; @@ -184,78 +164,9 @@ print_message_queue (void) m = next; } } - + /* Signal handling, needed for restoring default colors. */ -static void -xsigaddset (sigset_t *set, int sig) -{ - if (sigaddset (set, sig) != 0) - pfatal_with_name ("sigaddset"); -} - -static bool -xsigismember (sigset_t const *set, int sig) -{ - int mem = sigismember (set, sig); - if (mem < 0) - pfatal_with_name ("sigismember"); - assume (mem <= 1); - return mem; -} - -typedef void (*signal_handler) (int); -static signal_handler -xsignal (int sig, signal_handler func) -{ - signal_handler h = signal (sig, func); - if (h == SIG_ERR) - pfatal_with_name ("signal"); - return h; -} - -static void -xsigprocmask (int how, sigset_t const *restrict set, sigset_t *restrict oset) -{ - if (sigprocmask (how, set, oset) != 0) - pfatal_with_name ("sigprocmask"); -} - -/* If true, some signals are caught. This is separate from - 'caught_signals' because POSIX doesn't require an all-zero sigset_t - to be valid. */ -static bool some_signals_caught; - -/* The set of signals that are caught. */ -static sigset_t caught_signals; - -/* If nonzero, the value of the pending fatal signal. */ -static sig_atomic_t volatile interrupt_signal; - -/* A count of the number of pending stop signals that have been received. */ -static sig_atomic_t volatile stop_signal_count; - -/* An ordinary signal was received; arrange for the program to exit. */ - -static void -sighandler (int sig) -{ - if (! SA_NOCLDSTOP) - signal (sig, SIG_IGN); - if (! interrupt_signal) - interrupt_signal = sig; -} - -/* A SIGTSTP was received; arrange for the program to suspend itself. */ - -static void -stophandler (int sig) -{ - if (! SA_NOCLDSTOP) - signal (sig, stophandler); - if (! interrupt_signal) - stop_signal_count++; -} /* Process any pending signals. If signals are caught, this function should be called periodically. Ideally there should never be an unbounded amount of time when signals are not being processed. @@ -265,131 +176,18 @@ stophandler (int sig) static void process_signals (void) { - while (interrupt_signal | stop_signal_count) + for (int sig; (sig = syncsig_poll ()); ) { set_color_context (RESET_CONTEXT); fflush (stdout); - - sigset_t oldset; - xsigprocmask (SIG_BLOCK, &caught_signals, &oldset); - - /* Reload stop_signal_count and (if needed) interrupt_signal, in - case a new signal was handled before sigprocmask took effect. */ - int stops = stop_signal_count, sig; - - /* SIGTSTP is special, since the application can receive that signal - more than once. In this case, don't set the signal handler to the - default. Instead, just raise the uncatchable SIGSTOP. */ - if (stops) - { - stop_signal_count = stops - 1; - sig = SIGSTOP; - } - else - { - sig = interrupt_signal; - xsignal (sig, SIG_DFL); - } - - /* Exit or suspend the program. */ - if (raise (sig) != 0) - pfatal_with_name ("raise"); - xsigprocmask (SIG_SETMASK, &oldset, nullptr); - - /* If execution reaches here, then the program has been - continued (after being suspended). */ + syncsig_deliver (sig); } } -/* The signals that can be caught, the number of such signals, - and which of them are actually caught. */ -static int const sig[] = - { -#if SIGTSTP - /* This one is handled specially; see is_tstp_index. */ - SIGTSTP, -#endif - - /* The usual suspects. */ -#ifdef SIGALRM - SIGALRM, -#endif -#ifdef SIGHUP - SIGHUP, -#endif - SIGINT, -#ifdef SIGPIPE - SIGPIPE, -#endif -#ifdef SIGQUIT - SIGQUIT, -#endif - SIGTERM, -#ifdef SIGPOLL - SIGPOLL, -#endif -#ifdef SIGPROF - SIGPROF, -#endif -#ifdef SIGVTALRM - SIGVTALRM, -#endif -#ifdef SIGXCPU - SIGXCPU, -#endif -#ifdef SIGXFSZ - SIGXFSZ, -#endif - }; -enum { nsigs = sizeof (sig) / sizeof *(sig) }; - -/* True if sig[j] == SIGTSTP. */ -static bool -is_tstp_index (int j) -{ - return SIGTSTP && j == 0; -} - static void install_signal_handlers (void) { - if (sigemptyset (&caught_signals) != 0) - pfatal_with_name ("sigemptyset"); - -#if SA_NOCLDSTOP - for (int j = 0; j < nsigs; j++) - { - struct sigaction actj; - if (sigaction (sig[j], nullptr, &actj) == 0 && actj.sa_handler != SIG_IGN) - xsigaddset (&caught_signals, sig[j]); - } - - struct sigaction act; - act.sa_mask = caught_signals; - act.sa_flags = SA_RESTART; - - for (int j = 0; j < nsigs; j++) - if (xsigismember (&caught_signals, sig[j])) - { - act.sa_handler = is_tstp_index (j) ? stophandler : sighandler; - if (sigaction (sig[j], &act, nullptr) != 0) - pfatal_with_name ("sigaction"); - some_signals_caught = true; - } -#else - for (int j = 0; j < nsigs; j++) - { - signal_handler h = signal (sig[j], SIG_IGN); - if (h != SIG_IGN && h != SIG_ERR) - { - xsigaddset (&caught_signals, sig[j]); - xsignal (sig[j], is_tstp_index (j) ? stophandler : sighandler); - some_signals_caught = true; - if (siginterrupt (sig[j], 0) != 0) - pfatal_with_name ("siginterrupt"); - } - } -#endif + syncsig_install (SYNCSIG_TSTP); } /* Clean up signal handlers just before exiting the program. Do this @@ -398,14 +196,11 @@ install_signal_handlers (void) void cleanup_signal_handlers (void) { - if (some_signals_caught) - { - for (int j = 0; j < nsigs; j++) - if (xsigismember (&caught_signals, sig[j])) - xsignal (sig[j], SIG_DFL); - process_signals (); - } + syncsig_uninstall (); + process_signals (); } + +/* Color handling. */ static char const *current_name[2]; static bool currently_recursive;