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 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 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 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 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 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 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 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