1 /* $NetBSD: refuse_signals.c,v 1.1 2022/01/22 07:53:06 pho Exp $ */
2
3 /*
4 * Copyright (c) 2021 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. The name of the author may not be used to endorse or promote
16 * products derived from this software without specific prior written
17 * permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
20 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
23 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
25 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
27 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32 #include <sys/cdefs.h>
33 #if !defined(lint)
34 __RCSID("$NetBSD: refuse_signals.c,v 1.1 2022/01/22 07:53:06 pho Exp $");
35 #endif /* !lint */
36
37 #include <assert.h>
38 #include <fuse_internal.h>
39 #if defined(MULTITHREADED_REFUSE)
40 # include <pthread.h>
41 #endif
42 #include <signal.h>
43 #include <stdlib.h>
44 #include <string.h>
45
46 /* Signal handling routines
47 *
48 * FUSE only supports running a single filesystem per process. ReFUSE
49 * is going to allow a process to run a filesystem per thread. In
50 * order to support this, our implementation of
51 * fuse_set_signal_handlers() installs a set of signal handlers which,
52 * when invoked, terminates all the filesystems that called the
53 * function. This means our fuse_remove_signal_handlers() must not
54 * actually remove the signal handlers until the last thread calls the
55 * function.
56 *
57 * FUSE installs a signal handler for a signal only if its sa_handler
58 * is set to SIG_DFL. This obviously has a bad consequence: if the
59 * caller process already has a non-default signal handler for SIGINT,
60 * Ctrl-C will not stop the main loop of FUSE. See
61 * https://stackoverflow.com/q/5044375/3571336
62 *
63 * Maybe we should do the same knowing it's bad, but it's probably
64 * better to call our handler along with the old one. We may change
65 * this behavior if this turns out to cause serious compatibility
66 * issues.
67 *
68 * Also note that it is tempting to use puffs_unmountonsignal(3) but
69 * we can't, because there is no way to revert its effect.
70 */
71
72 #if defined(MULTITHREADED_REFUSE)
73 /* A mutex to protect the global state regarding signal handlers. When
74 * a thread is going to lock this, it must block all the signals (with
75 * pthread_sigmask(3)) that we install a handler for, or otherwise it
76 * may deadlock for trying to acquire a lock that is already held by
77 * itself. */
78 static pthread_mutex_t signal_mutex = PTHREAD_MUTEX_INITIALIZER;
79 #endif
80
81 /* Saved sigaction for each signal before we modify them. */
82 static struct sigaction* saved_actions[NSIG];
83
84 /* A linked list of "struct fuse*" which should be terminated upon
85 * receiving a signal. */
86 struct refuse_obj_elem {
87 struct fuse* fuse;
88 struct refuse_obj_elem* next;
89 };
90 static struct refuse_obj_elem* fuse_head;
91
92 #if defined(MULTITHREADED_REFUSE)
93 static int
block_signals(sigset_t * oset)94 block_signals(sigset_t* oset) {
95 sigset_t set;
96
97 if (sigemptyset(&set) != 0)
98 return -1;
99
100 if (sigaddset(&set, SIGHUP) != 0)
101 return -1;
102
103 if (sigaddset(&set, SIGINT) != 0)
104 return -1;
105
106 if (sigaddset(&set, SIGTERM) != 0)
107 return -1;
108
109 return pthread_sigmask(SIG_BLOCK, &set, oset);
110 }
111
112 static int
unblock_signals(const sigset_t * oset)113 unblock_signals(const sigset_t* oset) {
114 return pthread_sigmask(SIG_SETMASK, oset, NULL);
115 }
116 #endif /* defined(MULTITHREADED_REFUSE) */
117
118 /* handler == NULL means the signal should be ignored. */
119 static int
set_signal_handler(int sig,void (* handler)(int,siginfo_t *,void *))120 set_signal_handler(int sig, void (*handler)(int, siginfo_t*, void*)) {
121 struct sigaction* saved;
122 struct sigaction act;
123
124 saved = malloc(sizeof(*saved));
125 if (!saved)
126 return -1;
127
128 if (sigaction(sig, NULL, saved) != 0) {
129 free(saved);
130 return -1;
131 }
132
133 saved_actions[sig] = saved;
134
135 memset(&act, 0, sizeof(act));
136 if (handler) {
137 act.sa_sigaction = handler;
138 act.sa_flags = SA_SIGINFO;
139 }
140 else {
141 /* Ignore the signal only if the signal doesn't have a
142 * handler. */
143 if (!(saved->sa_flags & SA_SIGINFO) && saved->sa_handler == SIG_DFL)
144 act.sa_handler = SIG_IGN;
145 else
146 return 0;
147 }
148
149 if (sigemptyset(&act.sa_mask) != 0) {
150 free(saved);
151 saved_actions[sig] = NULL;
152 return -1;
153 }
154
155 return sigaction(sig, &act, NULL);
156 }
157
158 static int
restore_signal_handler(int sig,void (* handler)(int,siginfo_t *,void *))159 restore_signal_handler(int sig, void (*handler)(int, siginfo_t*, void*)) {
160 struct sigaction oact;
161 struct sigaction* saved;
162
163 saved = saved_actions[sig];
164 assert(saved != NULL);
165
166 if (sigaction(sig, NULL, &oact) != 0)
167 return -1;
168
169 /* Has the sigaction changed since we installed our handler? Do
170 * nothing if so. */
171 if (handler) {
172 if (!(oact.sa_flags & SA_SIGINFO) || oact.sa_sigaction != handler)
173 goto done;
174 }
175 else {
176 if (oact.sa_handler != SIG_IGN)
177 goto done;
178 }
179
180 if (sigaction(sig, saved, NULL) != 0)
181 return -1;
182
183 done:
184 free(saved);
185 saved_actions[sig] = NULL;
186 return 0;
187 }
188
189 static void
exit_handler(int sig,siginfo_t * info,void * ctx)190 exit_handler(int sig, siginfo_t* info, void* ctx) {
191 struct refuse_obj_elem* elem;
192 struct sigaction* saved;
193 #if defined(MULTITHREADED_REFUSE)
194 int rv;
195
196 /* pthread_mutex_lock(3) is NOT an async-signal-safe function. We
197 * assume it's okay, as the thread running this handler shouldn't
198 * be locking this mutex. */
199 rv = pthread_mutex_lock(&signal_mutex);
200 assert(rv == 0);
201 #endif
202
203 for (elem = fuse_head; elem != NULL; elem = elem->next)
204 fuse_exit(elem->fuse);
205
206 #if defined(MULTITHREADED_REFUSE)
207 rv = pthread_mutex_unlock(&signal_mutex);
208 assert(rv == 0);
209 #endif
210
211 saved = saved_actions[sig];
212 assert(saved != NULL);
213
214 if (saved->sa_handler != SIG_DFL && saved->sa_handler != SIG_IGN) {
215 if (saved->sa_flags & SA_SIGINFO)
216 saved->sa_sigaction(sig, info, ctx);
217 else
218 saved->sa_handler(sig);
219 }
220 }
221
222 /* The original function appeared on FUSE 2.5 takes a pointer to
223 * "struct fuse_session" instead of "struct fuse". We have no such
224 * things as fuse sessions.
225 */
226 int
__fuse_set_signal_handlers(struct fuse * fuse)227 __fuse_set_signal_handlers(struct fuse* fuse) {
228 int ret = 0;
229 struct refuse_obj_elem* elem;
230 #if defined(MULTITHREADED_REFUSE)
231 int rv;
232 sigset_t oset;
233
234 rv = block_signals(&oset);
235 assert(rv == 0);
236
237 rv = pthread_mutex_lock(&signal_mutex);
238 assert(rv == 0);
239 #endif
240
241 /* Have we already installed our signal handlers? If the list is
242 * empty, it means we have not. */
243 if (fuse_head == NULL) {
244 if (set_signal_handler(SIGHUP, exit_handler) != 0 ||
245 set_signal_handler(SIGINT, exit_handler) != 0 ||
246 set_signal_handler(SIGTERM, exit_handler) != 0 ||
247 set_signal_handler(SIGPIPE, NULL) != 0) {
248
249 ret = -1;
250 goto done;
251 }
252 }
253
254 /* Add ourselves to the list of filesystems that want to be
255 * terminated upon receiving a signal. But only if we aren't
256 * already in the list. */
257 for (elem = fuse_head; elem != NULL; elem = elem->next) {
258 if (elem->fuse == fuse)
259 goto done;
260 }
261
262 elem = malloc(sizeof(*elem));
263 if (!elem) {
264 ret = -1;
265 goto done;
266 }
267 elem->fuse = fuse;
268 elem->next = fuse_head;
269 fuse_head = elem;
270 done:
271
272 #if defined(MULTITHREADED_REFUSE)
273 rv = pthread_mutex_unlock(&signal_mutex);
274 assert(rv == 0);
275
276 rv = unblock_signals(&oset);
277 assert(rv == 0);
278 #endif
279 return ret;
280 }
281
282 int
__fuse_remove_signal_handlers(struct fuse * fuse)283 __fuse_remove_signal_handlers(struct fuse* fuse) {
284 int ret = 0;
285 struct refuse_obj_elem* prev;
286 struct refuse_obj_elem* elem;
287 #if defined(MULTITHREADED_REFUSE)
288 int rv;
289 sigset_t oset;
290
291 rv = block_signals(&oset);
292 assert(rv == 0);
293
294 rv = pthread_mutex_lock(&signal_mutex);
295 assert(rv == 0);
296 #endif
297
298 /* Remove ourselves from the list. */
299 for (prev = NULL, elem = fuse_head;
300 elem != NULL;
301 prev = elem, elem = elem->next) {
302
303 if (elem->fuse == fuse) {
304 if (prev)
305 prev->next = elem->next;
306 else
307 fuse_head = elem->next;
308 free(elem);
309 }
310 }
311
312 /* Restore handlers if we were the last one. */
313 if (fuse_head == NULL) {
314 if (restore_signal_handler(SIGHUP, exit_handler) == -1 ||
315 restore_signal_handler(SIGINT, exit_handler) == -1 ||
316 restore_signal_handler(SIGTERM, exit_handler) == -1 ||
317 restore_signal_handler(SIGPIPE, NULL) == -1) {
318
319 ret = -1;
320 }
321 }
322
323 #if defined(MULTITHREADED_REFUSE)
324 rv = pthread_mutex_unlock(&signal_mutex);
325 assert(rv == 0);
326
327 rv = unblock_signals(&oset);
328 assert(rv == 0);
329 #endif
330 return ret;
331 }
332