summaryrefslogtreecommitdiffstats
path: root/libfreerdp/utils/signal.c
blob: 487b9b0f89ffd7cbebd7eb7ce91d648fbcdb250e (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
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
/**
 * FreeRDP: A Remote Desktop Protocol Implementation
 * Signal handling
 *
 * Copyright 2011 Shea Levy <shea@shealevy.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <freerdp/config.h>

#include <stddef.h>
#include <errno.h>
#include <string.h>

#include <winpr/crt.h>

#include <freerdp/utils/signal.h>
#include <freerdp/log.h>

#ifndef _WIN32
#include <signal.h>
#include <termios.h>
#endif

#define TAG FREERDP_TAG("utils.signal")

#ifdef _WIN32

int freerdp_handle_signals(void)
{
	errno = ENOSYS;
	return -1;
}

BOOL freerdp_add_signal_cleanup_handler(void* context, freerdp_signal_handler_t fkt)
{
	return FALSE;
}

BOOL freerdp_del_signal_cleanup_handler(void* context, freerdp_signal_handler_t fkt)
{
	return FALSE;
}
#else

#include <pthread.h>
#include <winpr/debug.h>

static BOOL handlers_registered = FALSE;
static pthread_mutex_t signal_handler_lock = PTHREAD_MUTEX_INITIALIZER;

typedef struct
{
	void* context;
	freerdp_signal_handler_t handler;
} cleanup_handler_t;

static size_t cleanup_handler_count = 0;
static cleanup_handler_t cleanup_handlers[20] = { 0 };

static void lock(void)
{
	const int rc = pthread_mutex_lock(&signal_handler_lock);
	if (rc != 0)
	{
		char ebuffer[256] = { 0 };
		WLog_ERR(TAG, "[pthread_mutex_lock] failed with %s [%d]",
		         winpr_strerror(rc, ebuffer, sizeof(ebuffer)), rc);
	}
}

static void unlock(void)
{
	const int rc = pthread_mutex_unlock(&signal_handler_lock);
	if (rc != 0)
	{
		char ebuffer[256] = { 0 };
		WLog_ERR(TAG, "[pthread_mutex_lock] failed with %s [%d]",
		         winpr_strerror(rc, ebuffer, sizeof(ebuffer)), rc);
	}
}

static void term_handler(int signum)
{
	static BOOL recursive = FALSE;

	if (!recursive)
	{
		recursive = TRUE;
		WLog_ERR(TAG, "Caught signal '%s' [%d]", strsignal(signum), signum);
	}

	lock();
	for (size_t x = 0; x < cleanup_handler_count; x++)
	{
		const cleanup_handler_t empty = { 0 };
		cleanup_handler_t* cur = &cleanup_handlers[x];
		if (cur->handler)
			cur->handler(signum, strsignal(signum), cur->context);
		*cur = empty;
	}
	cleanup_handler_count = 0;
	unlock();
}

static void fatal_handler(int signum)
{
	struct sigaction default_sigaction;
	sigset_t this_mask;
	static BOOL recursive = FALSE;

	if (!recursive)
	{
		recursive = TRUE;
		WLog_ERR(TAG, "Caught signal '%s' [%d]", strsignal(signum), signum);

		winpr_log_backtrace(TAG, WLOG_ERROR, 20);
	}

	default_sigaction.sa_handler = SIG_DFL;
	sigfillset(&(default_sigaction.sa_mask));
	default_sigaction.sa_flags = 0;
	sigaction(signum, &default_sigaction, NULL);
	sigemptyset(&this_mask);
	sigaddset(&this_mask, signum);
	pthread_sigmask(SIG_UNBLOCK, &this_mask, NULL);
	raise(signum);
}

static const int term_signals[] = { SIGINT, SIGKILL, SIGQUIT, SIGSTOP, SIGTERM };

static const int fatal_signals[] = { SIGABRT,   SIGALRM, SIGBUS,  SIGFPE,  SIGHUP,  SIGILL,
	                                 SIGSEGV,   SIGTSTP, SIGTTIN, SIGTTOU, SIGUSR1, SIGUSR2,
#ifdef SIGPOLL
	                                 SIGPOLL,
#endif
#ifdef SIGPROF
	                                 SIGPROF,
#endif
#ifdef SIGSYS
	                                 SIGSYS,
#endif
	                                 SIGTRAP,
#ifdef SIGVTALRM
	                                 SIGVTALRM,
#endif
	                                 SIGXCPU,   SIGXFSZ };

static BOOL register_handlers(const int* signals, size_t count, void (*handler)(int))
{
	WINPR_ASSERT(signals || (count == 0));
	WINPR_ASSERT(handler);

	sigset_t orig_set = { 0 };
	struct sigaction saction = { 0 };

	pthread_sigmask(SIG_BLOCK, &(saction.sa_mask), &orig_set);

	sigfillset(&(saction.sa_mask));
	sigdelset(&(saction.sa_mask), SIGCONT);

	saction.sa_handler = handler;
	saction.sa_flags = 0;

	for (size_t x = 0; x < count; x++)
	{
		struct sigaction orig_sigaction = { 0 };
		if (sigaction(signals[x], NULL, &orig_sigaction) == 0)
		{
			if (orig_sigaction.sa_handler != SIG_IGN)
			{
				sigaction(signals[x], &saction, NULL);
			}
		}
	}

	pthread_sigmask(SIG_SETMASK, &orig_set, NULL);

	return TRUE;
}

int freerdp_handle_signals(void)
{
	int rc = -1;

	lock();

	WLog_DBG(TAG, "Registering signal hook...");

	if (!register_handlers(fatal_signals, ARRAYSIZE(fatal_signals), fatal_handler))
		goto fail;
	if (!register_handlers(term_signals, ARRAYSIZE(term_signals), term_handler))
		goto fail;

	/* Ignore SIGPIPE signal. */
	signal(SIGPIPE, SIG_IGN);
	handlers_registered = TRUE;
	rc = 0;
fail:
	unlock();
	return rc;
}

BOOL freerdp_add_signal_cleanup_handler(void* context, freerdp_signal_handler_t handler)
{
	BOOL rc = FALSE;
	lock();
	if (handlers_registered)
	{
		if (cleanup_handler_count < ARRAYSIZE(cleanup_handlers))
		{
			cleanup_handler_t* cur = &cleanup_handlers[cleanup_handler_count++];
			cur->context = context;
			cur->handler = handler;
		}
		else
			WLog_WARN(TAG, "Failed to register cleanup handler, only %" PRIuz " handlers supported",
			          ARRAYSIZE(cleanup_handlers));
	}
	rc = TRUE;
	unlock();
	return rc;
}

BOOL freerdp_del_signal_cleanup_handler(void* context, freerdp_signal_handler_t handler)
{
	BOOL rc = FALSE;
	lock();
	if (handlers_registered)
	{
		for (size_t x = 0; x < cleanup_handler_count; x++)
		{
			cleanup_handler_t* cur = &cleanup_handlers[x];
			if ((cur->context == context) && (cur->handler == handler))
			{
				const cleanup_handler_t empty = { 0 };
				for (size_t y = x + 1; y < cleanup_handler_count - 1; y++)
				{
					*cur++ = cleanup_handlers[y];
				}

				*cur = empty;
				cleanup_handler_count--;
				break;
			}
		}
	}
	rc = TRUE;
	unlock();
	return rc;
}

#endif