xref: /netbsd/lib/librefuse/refuse_signals.c (revision fc35b32c)
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