Home | History | Annotate | Line # | Download | only in librefuse
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