summaryrefslogtreecommitdiffstats
path: root/src/xz/signals.c
blob: 20f03beee8a502cddfe09db2cab25bb1b63a95c7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
// SPDX-License-Identifier: 0BSD

///////////////////////////////////////////////////////////////////////////////
//
/// \file       signals.c
/// \brief      Handling signals to abort operation
//
//  Author:     Lasse Collin
//
///////////////////////////////////////////////////////////////////////////////

#include "private.h"


volatile sig_atomic_t user_abort = false;


#if !(defined(_WIN32) && !defined(__CYGWIN__))

/// If we were interrupted by a signal, we store the signal number so that
/// we can raise that signal to kill the program when all cleanups have
/// been done.
static volatile sig_atomic_t exit_signal = 0;

/// Mask of signals for which we have established a signal handler to set
/// user_abort to true.
static sigset_t hooked_signals;

/// True once signals_init() has finished. This is used to skip blocking
/// signals (with uninitialized hooked_signals) if signals_block() and
/// signals_unblock() are called before signals_init() has been called.
static bool signals_are_initialized = false;

/// signals_block() and signals_unblock() can be called recursively.
static size_t signals_block_count = 0;


static void
signal_handler(int sig)
{
	exit_signal = sig;
	user_abort = true;

#ifndef TUKLIB_DOSLIKE
	io_write_to_user_abort_pipe();
#endif

	return;
}


#ifdef __APPLE__
#	pragma GCC diagnostic push
#	pragma GCC diagnostic ignored "-Wsign-conversion"
#endif
extern void
signals_init(void)
{
	// List of signals for which we establish the signal handler.
	static const int sigs[] = {
		SIGINT,
		SIGTERM,
#ifdef SIGHUP
		SIGHUP,
#endif
#ifdef SIGPIPE
		SIGPIPE,
#endif
#ifdef SIGXCPU
		SIGXCPU,
#endif
#ifdef SIGXFSZ
		SIGXFSZ,
#endif
	};

	// Mask of the signals for which we have established a signal handler.
	sigemptyset(&hooked_signals);
	for (size_t i = 0; i < ARRAY_SIZE(sigs); ++i)
		sigaddset(&hooked_signals, sigs[i]);

#ifdef SIGALRM
	// Add also the signals from message.c to hooked_signals.
	for (size_t i = 0; message_progress_sigs[i] != 0; ++i)
		sigaddset(&hooked_signals, message_progress_sigs[i]);
#endif

#ifdef USE_SIGTSTP_HANDLER
	// Add the SIGTSTP handler from mytime.c to hooked_signals.
	sigaddset(&hooked_signals, SIGTSTP);
#endif

	// Using "my_sa" because "sa" may conflict with a sockaddr variable
	// from system headers on Solaris.
	struct sigaction my_sa;

	// All the signals that we handle we also blocked while the signal
	// handler runs.
	my_sa.sa_mask = hooked_signals;

	// Don't set SA_RESTART, because we want EINTR so that we can check
	// for user_abort and cleanup before exiting. We block the signals
	// for which we have established a handler when we don't want EINTR.
	my_sa.sa_flags = 0;
	my_sa.sa_handler = &signal_handler;

	struct sigaction old;

	for (size_t i = 0; i < ARRAY_SIZE(sigs); ++i) {
		// If the parent process has left some signals ignored,
		// we don't unignore them.
		if (sigaction(sigs[i], NULL, &old) == 0
				&& old.sa_handler == SIG_IGN)
			continue;

		// Establish the signal handler.
		if (sigaction(sigs[i], &my_sa, NULL))
			message_signal_handler();
	}

#ifdef USE_SIGTSTP_HANDLER
	if (!(sigaction(SIGTSTP, NULL, &old) == 0
				&& old.sa_handler == SIG_IGN)) {
		my_sa.sa_handler = &mytime_sigtstp_handler;
		if (sigaction(SIGTSTP, &my_sa, NULL))
			message_signal_handler();
	}
#endif

	signals_are_initialized = true;

	return;
}
#ifdef __APPLE__
#	pragma GCC diagnostic pop
#endif


#ifndef __VMS
extern void
signals_block(void)
{
	if (signals_are_initialized) {
		if (signals_block_count++ == 0) {
			const int saved_errno = errno;
			mythread_sigmask(SIG_BLOCK, &hooked_signals, NULL);
			errno = saved_errno;
		}
	}

	return;
}


extern void
signals_unblock(void)
{
	if (signals_are_initialized) {
		assert(signals_block_count > 0);

		if (--signals_block_count == 0) {
			const int saved_errno = errno;
			mythread_sigmask(SIG_UNBLOCK, &hooked_signals, NULL);
			errno = saved_errno;
		}
	}

	return;
}
#endif


extern void
signals_exit(void)
{
	const int sig = (int)exit_signal;

	if (sig != 0) {
#if defined(TUKLIB_DOSLIKE) || defined(__VMS)
		// Don't raise(), set only exit status. This avoids
		// printing unwanted message about SIGINT when the user
		// presses C-c.
		set_exit_status(E_ERROR);
#else
		struct sigaction sa;
		sa.sa_handler = SIG_DFL;
		sigfillset(&sa.sa_mask);
		sa.sa_flags = 0;
		sigaction(sig, &sa, NULL);
		raise(sig);
#endif
	}

	return;
}

#else

// While Windows has some very basic signal handling functions as required
// by C89, they are not really used, and e.g. SIGINT doesn't work exactly
// the way it does on POSIX (Windows creates a new thread for the signal
// handler). Instead, we use SetConsoleCtrlHandler() to catch user
// pressing C-c, because that seems to be the recommended way to do it.
//
// NOTE: This doesn't work under MSYS. Trying with SIGINT doesn't work
// either even if it appeared to work at first. So test using Windows
// console window.

static BOOL WINAPI
signal_handler(DWORD type lzma_attribute((__unused__)))
{
	// Since we don't get a signal number which we could raise() at
	// signals_exit() like on POSIX, just set the exit status to
	// indicate an error, so that we cannot return with zero exit status.
	set_exit_status(E_ERROR);
	user_abort = true;
	return TRUE;
}


extern void
signals_init(void)
{
	if (!SetConsoleCtrlHandler(&signal_handler, TRUE))
		message_signal_handler();

	return;
}

#endif