summaryrefslogtreecommitdiff
path: root/signal.c
blob: 072c81fd0a1df43147a651773cd8e2e05f2bc8a9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
/* SPDX-License-Identifier: GPL-2.0 */
/** \file signal.c Signal handling functions. */

#include <signal.h>
#include <sys/types.h>

#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;
}