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