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