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