Search code examples
fuse

libfuse: exiting fuse_session_loop


Context: Ubuntu 11.10 and libfuse 2.8.4-1.4ubuntu1 Linux 3.0.0-14-generic #23-Ubuntu SMP Mon Nov 21 20:28:43 UTC 2011 x86_64 x86_64 x86_64 GNU/Linux

I'm trying to use libfuse. I want to cause fuse_session_loop to exit (from a signal handler or a different thread), but when I call fuse_session_exit nothing happens until the session receives a new request.

fuse_session_exit sets a flag that is read by fuse_session_exited. Debugging into fuse_session_loop it appears to block on fuse_chan_recv, so it doesn't check fuse_session_exited again until the top of the loop...

int fuse_session_loop(struct fuse_session *se)
{
    int res = 0;
    struct fuse_chan *ch = fuse_session_next_chan(se, NULL);
    size_t bufsize = fuse_chan_bufsize(ch);
    char *buf = (char *) malloc(bufsize);
    if (!buf) {
        fprintf(stderr, "fuse: failed to allocate read buffer\n");
        return -1;
    }

    while (!fuse_session_exited(se)) {
        struct fuse_chan *tmpch = ch;
        res = fuse_chan_recv(&tmpch, buf, bufsize); <--- BLOCKING
        if (res == -EINTR)
            continue;
        if (res <= 0)
            break;
        fuse_session_process(se, buf, res, tmpch);
    }

    free(buf);
    fuse_session_reset(se);
    return res < 0 ? -1 : 0;
}

fuse_chan_recv calls fuse_kern_chan_receive which blocks on the "read" syscall of the "/dev/fuse" device, so even though the fuse_session_exited flag is set nothing happens yet.

static int fuse_kern_chan_receive(struct fuse_chan **chp, char *buf,
                  size_t size)
{
    struct fuse_chan *ch = *chp;
    int err;
    ssize_t res;
    struct fuse_session *se = fuse_chan_session(ch);
    assert(se != NULL);

restart:
    res = read(fuse_chan_fd(ch), buf, size); <--- BLOCKING
    err = errno;

    if (fuse_session_exited(se))
        return 0;
    if (res == -1) {
        /* ENOENT means the operation was interrupted, it's safe
           to restart */
        if (err == ENOENT)
            goto restart;

        if (err == ENODEV) {
            fuse_session_exit(se);
            return 0;
        }
        /* Errors occuring during normal operation: EINTR (read
           interrupted), EAGAIN (nonblocking I/O), ENODEV (filesystem
           umounted) */
        if (err != EINTR && err != EAGAIN)
            perror("fuse: reading device");
        return -err;
    }
    if ((size_t) res < sizeof(struct fuse_in_header)) {
        fprintf(stderr, "short read on fuse device\n");
        return -EIO;
    }
    return res;
}

This problem seems to effect the hello_ll.c example provided with libfuse as well as my program. It makes me think that perhaps there is some mechanism that is not working that should. Perhaps fuse_session_exit is supposed to be also doing something that interrupts the read call, that for some reason is not working on my system.

Any ideas?


Solution

  • Normally if a signal handler is executed while a system call (such as read(2)) is blocking, the system call returns immediately (after the signal handler is completed executing) with an EINTR. This is clearly the behaviour that fuse_session_loop and fuse_session_exit were designed for.

    However if the signal handler is installed with the SA_RESTART flag set (see sigaction(2)) the system call will not return with EINTR after the signal handler has executed. The system call will resume blocking instead.

    For some reason on my system (Ubuntu 11.10 x86_64) the default behaviour of signal(2) is to install the signal handler with the SA_RESTART flag.

    ie the strace of the following program...

    #include <stdlib.h>
    #include <signal.h>
    
    void f(int signum) {}
    
    int main()
    {
        signal(SIGINT,f);
        return EXIT_SUCCESS;
    }
    

    ...is as follows...

    rt_sigaction(SIGINT, {0x400524, [INT], SA_RESTORER|SA_RESTART, 0x7f4997e1f420}, {SIG_DFL, [], 0}, 8) = 0
    

    For this reason the signals (in the examples provided with fuse and my own program) did not interrupt the blocking read in fuse_kern_chan_receive as their authors expected them too.

    The fix was to use sigaction(2) (and leave SA_RESTART bit zeroed) to install the handler (instead of signal(2)).

    An open question that still remains is why does a call to signal(2) have the SA_RESTART flag on by default? I would expect interrupting (not restarting) is the expected default behavior.