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;