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 
16 volatile 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.
24 static 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.
28 static 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.
33 static bool signals_are_initialized = false;
34 
35 /// signals_block() and signals_unblock() can be called recursively.
36 static size_t signals_block_count = 0;
37 
38 
39 static void
signal_handler(int sig)40 signal_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 
53 extern void
signals_init(void)54 signals_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
119 extern void
signals_block(void)120 signals_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 
134 extern void
signals_unblock(void)135 signals_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 
152 extern void
signals_exit(void)153 signals_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 
188 static BOOL WINAPI
signal_handler(DWORD type lzma_attribute ((__unused__)))189 signal_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 
200 extern void
signals_init(void)201 signals_init(void)
202 {
203           if (!SetConsoleCtrlHandler(&signal_handler, TRUE))
204                     message_signal_handler();
205 
206           return;
207 }
208 
209 #endif
210