signals.c revision 292588
1///////////////////////////////////////////////////////////////////////////////
2//
3/// \file       signals.c
4/// \brief      Handling signals to abort operation
5//
6//  Author:     Lasse Collin
7//
8//  This file has been put into the public domain.
9//  You can do whatever you want with this file.
10//
11///////////////////////////////////////////////////////////////////////////////
12
13#include "private.h"
14
15
16volatile sig_atomic_t user_abort = false;
17
18
19#if !(defined(_WIN32) && !defined(__CYGWIN__))
20
21/// If we were interrupted by a signal, we store the signal number so that
22/// we can raise that signal to kill the program when all cleanups have
23/// been done.
24static volatile sig_atomic_t exit_signal = 0;
25
26/// Mask of signals for which have have established a signal handler to set
27/// user_abort to true.
28static sigset_t hooked_signals;
29
30/// True once signals_init() has finished. This is used to skip blocking
31/// signals (with uninitialized hooked_signals) if signals_block() and
32/// signals_unblock() are called before signals_init() has been called.
33static bool signals_are_initialized = false;
34
35/// signals_block() and signals_unblock() can be called recursively.
36static size_t signals_block_count = 0;
37
38
39static void
40signal_handler(int sig)
41{
42	exit_signal = sig;
43	user_abort = true;
44
45#ifndef TUKLIB_DOSLIKE
46	io_write_to_user_abort_pipe();
47#endif
48
49	return;
50}
51
52
53extern void
54signals_init(void)
55{
56	// List of signals for which we establish the signal handler.
57	static const int sigs[] = {
58		SIGINT,
59		SIGTERM,
60#ifdef SIGHUP
61		SIGHUP,
62#endif
63#ifdef SIGPIPE
64		SIGPIPE,
65#endif
66#ifdef SIGXCPU
67		SIGXCPU,
68#endif
69#ifdef SIGXFSZ
70		SIGXFSZ,
71#endif
72	};
73
74	// Mask of the signals for which we have established a signal handler.
75	sigemptyset(&hooked_signals);
76	for (size_t i = 0; i < ARRAY_SIZE(sigs); ++i)
77		sigaddset(&hooked_signals, sigs[i]);
78
79#ifdef SIGALRM
80	// Add also the signals from message.c to hooked_signals.
81	for (size_t i = 0; message_progress_sigs[i] != 0; ++i)
82		sigaddset(&hooked_signals, message_progress_sigs[i]);
83#endif
84
85	// Using "my_sa" because "sa" may conflict with a sockaddr variable
86	// from system headers on Solaris.
87	struct sigaction my_sa;
88
89	// All the signals that we handle we also blocked while the signal
90	// handler runs.
91	my_sa.sa_mask = hooked_signals;
92
93	// Don't set SA_RESTART, because we want EINTR so that we can check
94	// for user_abort and cleanup before exiting. We block the signals
95	// for which we have established a handler when we don't want EINTR.
96	my_sa.sa_flags = 0;
97	my_sa.sa_handler = &signal_handler;
98
99	for (size_t i = 0; i < ARRAY_SIZE(sigs); ++i) {
100		// If the parent process has left some signals ignored,
101		// we don't unignore them.
102		struct sigaction old;
103		if (sigaction(sigs[i], NULL, &old) == 0
104				&& old.sa_handler == SIG_IGN)
105			continue;
106
107		// Establish the signal handler.
108		if (sigaction(sigs[i], &my_sa, NULL))
109			message_signal_handler();
110	}
111
112	signals_are_initialized = true;
113
114	return;
115}
116
117
118#ifndef __VMS
119extern void
120signals_block(void)
121{
122	if (signals_are_initialized) {
123		if (signals_block_count++ == 0) {
124			const int saved_errno = errno;
125			mythread_sigmask(SIG_BLOCK, &hooked_signals, NULL);
126			errno = saved_errno;
127		}
128	}
129
130	return;
131}
132
133
134extern void
135signals_unblock(void)
136{
137	if (signals_are_initialized) {
138		assert(signals_block_count > 0);
139
140		if (--signals_block_count == 0) {
141			const int saved_errno = errno;
142			mythread_sigmask(SIG_UNBLOCK, &hooked_signals, NULL);
143			errno = saved_errno;
144		}
145	}
146
147	return;
148}
149#endif
150
151
152extern void
153signals_exit(void)
154{
155	const int sig = exit_signal;
156
157	if (sig != 0) {
158#if defined(TUKLIB_DOSLIKE) || defined(__VMS)
159		// Don't raise(), set only exit status. This avoids
160		// printing unwanted message about SIGINT when the user
161		// presses C-c.
162		set_exit_status(E_ERROR);
163#else
164		struct sigaction sa;
165		sa.sa_handler = SIG_DFL;
166		sigfillset(&sa.sa_mask);
167		sa.sa_flags = 0;
168		sigaction(sig, &sa, NULL);
169		raise(exit_signal);
170#endif
171	}
172
173	return;
174}
175
176#else
177
178// While Windows has some very basic signal handling functions as required
179// by C89, they are not really used, and e.g. SIGINT doesn't work exactly
180// the way it does on POSIX (Windows creates a new thread for the signal
181// handler). Instead, we use SetConsoleCtrlHandler() to catch user
182// pressing C-c, because that seems to be the recommended way to do it.
183//
184// NOTE: This doesn't work under MSYS. Trying with SIGINT doesn't work
185// either even if it appeared to work at first. So test using Windows
186// console window.
187
188static BOOL WINAPI
189signal_handler(DWORD type lzma_attribute((__unused__)))
190{
191	// Since we don't get a signal number which we could raise() at
192	// signals_exit() like on POSIX, just set the exit status to
193	// indicate an error, so that we cannot return with zero exit status.
194	set_exit_status(E_ERROR);
195	user_abort = true;
196	return TRUE;
197}
198
199
200extern void
201signals_init(void)
202{
203	if (!SetConsoleCtrlHandler(&signal_handler, TRUE))
204		message_signal_handler();
205
206	return;
207}
208
209#endif
210