1207753Smm///////////////////////////////////////////////////////////////////////////////
2207753Smm//
3207753Smm/// \file       signals.c
4207753Smm/// \brief      Handling signals to abort operation
5207753Smm//
6207753Smm//  Author:     Lasse Collin
7207753Smm//
8207753Smm//  This file has been put into the public domain.
9207753Smm//  You can do whatever you want with this file.
10207753Smm//
11207753Smm///////////////////////////////////////////////////////////////////////////////
12207753Smm
13207753Smm#include "private.h"
14207753Smm
15207753Smm
16207753Smmvolatile sig_atomic_t user_abort = false;
17207753Smm
18207753Smm
19213700Smm#if !(defined(_WIN32) && !defined(__CYGWIN__))
20207753Smm
21207753Smm/// If we were interrupted by a signal, we store the signal number so that
22207753Smm/// we can raise that signal to kill the program when all cleanups have
23207753Smm/// been done.
24207753Smmstatic volatile sig_atomic_t exit_signal = 0;
25207753Smm
26207753Smm/// Mask of signals for which have have established a signal handler to set
27207753Smm/// user_abort to true.
28207753Smmstatic sigset_t hooked_signals;
29207753Smm
30207753Smm/// True once signals_init() has finished. This is used to skip blocking
31207753Smm/// signals (with uninitialized hooked_signals) if signals_block() and
32207753Smm/// signals_unblock() are called before signals_init() has been called.
33207753Smmstatic bool signals_are_initialized = false;
34207753Smm
35207753Smm/// signals_block() and signals_unblock() can be called recursively.
36207753Smmstatic size_t signals_block_count = 0;
37207753Smm
38207753Smm
39207753Smmstatic void
40207753Smmsignal_handler(int sig)
41207753Smm{
42207753Smm	exit_signal = sig;
43207753Smm	user_abort = true;
44292588Sdelphij
45292588Sdelphij#ifndef TUKLIB_DOSLIKE
46292588Sdelphij	io_write_to_user_abort_pipe();
47292588Sdelphij#endif
48292588Sdelphij
49207753Smm	return;
50207753Smm}
51207753Smm
52207753Smm
53207753Smmextern void
54207753Smmsignals_init(void)
55207753Smm{
56207753Smm	// List of signals for which we establish the signal handler.
57207753Smm	static const int sigs[] = {
58207753Smm		SIGINT,
59207753Smm		SIGTERM,
60207753Smm#ifdef SIGHUP
61207753Smm		SIGHUP,
62207753Smm#endif
63207753Smm#ifdef SIGPIPE
64207753Smm		SIGPIPE,
65207753Smm#endif
66207753Smm#ifdef SIGXCPU
67207753Smm		SIGXCPU,
68207753Smm#endif
69207753Smm#ifdef SIGXFSZ
70207753Smm		SIGXFSZ,
71207753Smm#endif
72207753Smm	};
73207753Smm
74207753Smm	// Mask of the signals for which we have established a signal handler.
75207753Smm	sigemptyset(&hooked_signals);
76207753Smm	for (size_t i = 0; i < ARRAY_SIZE(sigs); ++i)
77207753Smm		sigaddset(&hooked_signals, sigs[i]);
78207753Smm
79215187Smm#ifdef SIGALRM
80215187Smm	// Add also the signals from message.c to hooked_signals.
81215187Smm	for (size_t i = 0; message_progress_sigs[i] != 0; ++i)
82215187Smm		sigaddset(&hooked_signals, message_progress_sigs[i]);
83215187Smm#endif
84215187Smm
85274261Sdelphij	// Using "my_sa" because "sa" may conflict with a sockaddr variable
86274261Sdelphij	// from system headers on Solaris.
87274261Sdelphij	struct sigaction my_sa;
88207753Smm
89207753Smm	// All the signals that we handle we also blocked while the signal
90207753Smm	// handler runs.
91274261Sdelphij	my_sa.sa_mask = hooked_signals;
92207753Smm
93207753Smm	// Don't set SA_RESTART, because we want EINTR so that we can check
94207753Smm	// for user_abort and cleanup before exiting. We block the signals
95207753Smm	// for which we have established a handler when we don't want EINTR.
96274261Sdelphij	my_sa.sa_flags = 0;
97274261Sdelphij	my_sa.sa_handler = &signal_handler;
98207753Smm
99207753Smm	for (size_t i = 0; i < ARRAY_SIZE(sigs); ++i) {
100207753Smm		// If the parent process has left some signals ignored,
101207753Smm		// we don't unignore them.
102207753Smm		struct sigaction old;
103207753Smm		if (sigaction(sigs[i], NULL, &old) == 0
104207753Smm				&& old.sa_handler == SIG_IGN)
105207753Smm			continue;
106207753Smm
107207753Smm		// Establish the signal handler.
108274261Sdelphij		if (sigaction(sigs[i], &my_sa, NULL))
109207753Smm			message_signal_handler();
110207753Smm	}
111207753Smm
112207753Smm	signals_are_initialized = true;
113207753Smm
114207753Smm	return;
115207753Smm}
116207753Smm
117207753Smm
118207753Smm#ifndef __VMS
119207753Smmextern void
120207753Smmsignals_block(void)
121207753Smm{
122207753Smm	if (signals_are_initialized) {
123207753Smm		if (signals_block_count++ == 0) {
124207753Smm			const int saved_errno = errno;
125207753Smm			mythread_sigmask(SIG_BLOCK, &hooked_signals, NULL);
126207753Smm			errno = saved_errno;
127207753Smm		}
128207753Smm	}
129207753Smm
130207753Smm	return;
131207753Smm}
132207753Smm
133207753Smm
134207753Smmextern void
135207753Smmsignals_unblock(void)
136207753Smm{
137207753Smm	if (signals_are_initialized) {
138207753Smm		assert(signals_block_count > 0);
139207753Smm
140207753Smm		if (--signals_block_count == 0) {
141207753Smm			const int saved_errno = errno;
142207753Smm			mythread_sigmask(SIG_UNBLOCK, &hooked_signals, NULL);
143207753Smm			errno = saved_errno;
144207753Smm		}
145207753Smm	}
146207753Smm
147207753Smm	return;
148207753Smm}
149207753Smm#endif
150207753Smm
151207753Smm
152207753Smmextern void
153207753Smmsignals_exit(void)
154207753Smm{
155207753Smm	const int sig = exit_signal;
156207753Smm
157207753Smm	if (sig != 0) {
158215187Smm#if defined(TUKLIB_DOSLIKE) || defined(__VMS)
159213700Smm		// Don't raise(), set only exit status. This avoids
160213700Smm		// printing unwanted message about SIGINT when the user
161213700Smm		// presses C-c.
162213700Smm		set_exit_status(E_ERROR);
163213700Smm#else
164207753Smm		struct sigaction sa;
165207753Smm		sa.sa_handler = SIG_DFL;
166207753Smm		sigfillset(&sa.sa_mask);
167207753Smm		sa.sa_flags = 0;
168207753Smm		sigaction(sig, &sa, NULL);
169207753Smm		raise(exit_signal);
170213700Smm#endif
171207753Smm	}
172207753Smm
173207753Smm	return;
174207753Smm}
175207753Smm
176207753Smm#else
177207753Smm
178207753Smm// While Windows has some very basic signal handling functions as required
179213700Smm// by C89, they are not really used, and e.g. SIGINT doesn't work exactly
180213700Smm// the way it does on POSIX (Windows creates a new thread for the signal
181213700Smm// handler). Instead, we use SetConsoleCtrlHandler() to catch user
182213700Smm// pressing C-c, because that seems to be the recommended way to do it.
183213700Smm//
184213700Smm// NOTE: This doesn't work under MSYS. Trying with SIGINT doesn't work
185213700Smm// either even if it appeared to work at first. So test using Windows
186213700Smm// console window.
187207753Smm
188207753Smmstatic BOOL WINAPI
189223935Smmsignal_handler(DWORD type lzma_attribute((__unused__)))
190207753Smm{
191207753Smm	// Since we don't get a signal number which we could raise() at
192207753Smm	// signals_exit() like on POSIX, just set the exit status to
193207753Smm	// indicate an error, so that we cannot return with zero exit status.
194207753Smm	set_exit_status(E_ERROR);
195207753Smm	user_abort = true;
196207753Smm	return TRUE;
197207753Smm}
198207753Smm
199207753Smm
200207753Smmextern void
201207753Smmsignals_init(void)
202207753Smm{
203207753Smm	if (!SetConsoleCtrlHandler(&signal_handler, TRUE))
204207753Smm		message_signal_handler();
205207753Smm
206207753Smm	return;
207207753Smm}
208207753Smm
209207753Smm#endif
210