diff: refactor and improve signal handling

This is so we can improve sdiff signal handling.
* bootstrap.conf: Add stdcountof-h.
* src/Makefile.am (diff_SOURCES): Add syncsig.c.
(noinst_HEADERS): Add syncsig.h.
* src/diff.c, src/util.c: Include syncsig.h.
* src/util.c: Move signal-related stuff from here ...
* src/syncsig.c: ... to here.
(syncsig_install, syncsig_cleanup):
Rename from install_signal_handlers, cleanup_signal_handlers.
All uses changed.  Handle some more signals.
Add an option to not handle stop-related signals.
(syncsig_poll, syncsig_deliver):
New functions, which are like the old process_signals
but in two pieces not one.  All uses changed.
(syncsig_install): New args FUN and ARG.
Return int on failure.  All callers changed.
* src/syncsig.h: New file.
This commit is contained in:
Paul Eggert 2025-09-02 16:15:27 -07:00
parent 6b9c726076
commit b4524ba9a6
5 changed files with 423 additions and 217 deletions

View File

@ -124,8 +124,9 @@ stat
stat-macros
stat-size
stat-time
stdckdint-h
stdc_bit_width
stdckdint-h
stdcountof-h
stdint-h
stpcpy
strcase

View File

@ -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

350
src/syncsig.c Normal file
View File

@ -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 <http://www.gnu.org/licenses/>. */
#include <config.h>
#include "syncsig.h"
#include "minmax.h"
#include <signal.h>
#include <stdcountof.h>
/* 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
}
}

60
src/syncsig.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>. */
/* 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);

View File

@ -27,31 +27,11 @@
#include <flexmember.h>
#include <mcel.h>
#include <quotearg.h>
#include <syncsig.h>
#include <system-quote.h>
#include <xalloc.h>
#include <stdarg.h>
#include <signal.h>
/* 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;