/* SPDX-License-Identifier: GPL-2.0 */ /** \file signal.c Signal handling functions. */ #include #include #include "para.h" #include "error.h" #include "fd.h" #include "list.h" #include "string.h" #include "sched.h" #include "signal.h" static int signal_pipe[2]; /** * Initialize the paraslash signal subsystem. * * This function creates the signal pipe to deliver pending signals to * the application (Bernstein's trick). It is usually called at startup, * followed by subsequent calls to \ref para_install_sighandler() for each * signal that should be caught. * * The same generic signal handler is installed for all signals. When a * signal arrives, the handler writes the signal number to the write end of * the pipe. By checking the read end the application can tell which signal, * if any, has arrived. * * \return This function either succeeds or calls exit(3) to terminate the * current process. On success, the function returns the descriptor of the * read end of the pipe. */ int signal_init(void) { int ret; PARA_NOTICE_LOG("setting up signal handling\n"); if (pipe(signal_pipe) < 0) { ret = -ERRNO_TO_PARA_ERROR(errno); goto err_out; } ret = mark_fd_nonblocking(signal_pipe[0]); if (ret < 0) goto err_out; ret = mark_fd_nonblocking(signal_pipe[1]); if (ret < 0) goto err_out; return signal_pipe[0]; err_out: PARA_EMERG_LOG("%s\n", para_strerror(-ret)); exit(EXIT_FAILURE); } /* Write the signal number to signal pipe. */ static void generic_signal_handler(int s) { /* * Signal handlers that make system calls must save a copy of errno on * entry to the handler and restore it on exit, to prevent the * possibility of overwriting a errno value that had previously been * set in the main program. */ int save_errno = errno; ssize_t ret = write(signal_pipe[1], &s, sizeof(int)); if (ret == sizeof(int)) { errno = save_errno; return; } /* * This is a fatal error which should never happen. We must not call * PARA_XXX_LOG() here because this might acquire the log mutex which * is already taken by the main program if the interrupt occurs while a * log message is being printed. The mutex will not be released as long * as this signal handler is running, so a deadlock ensues. */ exit(EXIT_FAILURE); } /** * Install a signal handler, setting the SA_RESTART flag. * * \param sig The number of the signal to catch. * \param handler to be installed, can be SIG_IGN or SIG_DFL. * * This either succeeds or calls exit(EXIT_FAILURE). * * \sa sigaction(2), signal(7). */ void para_sigaction(int sig, void (*handler)(int)) { /* Set SA_RESTART to be compatible with BSD signal semantics on Linux */ struct sigaction act = {.sa_handler = handler, .sa_flags = SA_RESTART}; PARA_DEBUG_LOG("catching signal %d\n", sig); sigemptyset(&act.sa_mask); if (sigaction(sig, &act, NULL) >= 0) return; PARA_EMERG_LOG("failed to install signal handler for signal %d\n", sig); exit(EXIT_FAILURE); } /** * Install the generic signal handler for the given signal number. * * \param sig The number of the signal to catch. * * \sa signal(2), sigaction(2). */ void para_install_sighandler(int sig) { para_sigaction(sig, &generic_signal_handler); } /** * Block a signal for the caller. * * \param sig The signal to block. * * This sets the given signal in the current signal mask of the calling process * to prevent this signal from delivery. * * \sa \ref para_unblock_signal(), sigprocmask(2), sigaddset(3). */ void para_block_signal(int sig) { sigset_t set; PARA_DEBUG_LOG("blocking signal %d\n", sig); sigemptyset(&set); sigaddset(&set, sig); sigprocmask(SIG_BLOCK, &set, NULL); } /** * Unblock a signal. * * \param sig The signal to unblock. * * This function removes the given signal from the current set of blocked * signals. * * \sa \ref para_block_signal(), sigprocmask(2), sigaddset(3). */ void para_unblock_signal(int sig) { sigset_t set; PARA_DEBUG_LOG("unblocking signal %d\n", sig); sigemptyset(&set); sigaddset(&set, sig); sigprocmask(SIG_UNBLOCK, &set, NULL); } /** * Return the number of the next pending signal. * * \return On success, the number of the received signal is returned. If there * is no signal currently pending, the function returns zero. On read errors * from the signal pipe, the process is terminated. */ int para_next_signal(void) { size_t n; int s, ret = read_nonblock(signal_pipe[0], &s, sizeof(s), &n); if (ret < 0) { PARA_EMERG_LOG("%s\n", para_strerror(-ret)); exit(EXIT_FAILURE); } if (n == 0) return 0; assert(n == sizeof(s)); PARA_DEBUG_LOG("next signal: %d\n", s); return s; }