root/sys/vfs/fuse/fuse_ipc.c
/*-
 * Copyright (c) 2019 Tomohiro Kusumi <tkusumi@netbsd.org>
 * Copyright (c) 2019 The DragonFly Project
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "fuse.h"

#include <sys/signalvar.h>
#include <sys/kern_syscall.h>

static MALLOC_DEFINE(M_FUSE_BUF, "fuse_buf", "FUSE buf");
static MALLOC_DEFINE(M_FUSE_IPC, "fuse_ipc", "FUSE ipc");

static struct objcache *fuse_ipc_objcache = NULL;
static struct objcache_malloc_args fuse_ipc_args = {
        sizeof(struct fuse_ipc), M_FUSE_IPC,
};

#if 0
static int
fuse_block_sigs(sigset_t *oldset)
{
        if (curproc) {
                sigset_t newset;
                int error;

                SIGFILLSET(newset);
                SIGDELSET(newset, SIGKILL);

                error = kern_sigprocmask(SIG_BLOCK, &newset, oldset);
                KKASSERT(!error);
                return error;
        }

        return -1;
}

static int
fuse_restore_sigs(sigset_t *oldset)
{
        if (curproc) {
                int error = kern_sigprocmask(SIG_SETMASK, oldset, NULL);
                KKASSERT(!error);
                return error;
        }

        return -1;
}
#endif

void
fuse_buf_alloc(struct fuse_buf *fbp, size_t len)
{
        fbp->buf = kmalloc(len, M_FUSE_BUF, M_WAITOK | M_ZERO);
        KKASSERT(fbp->buf);
        fbp->len = len;
}

void
fuse_buf_free(struct fuse_buf *fbp)
{
        if (fbp->buf) {
                kfree(fbp->buf, M_FUSE_BUF);
                fbp->buf = NULL;
        }
        fbp->len = 0;
}

struct fuse_ipc*
fuse_ipc_get(struct fuse_mount *fmp, size_t len)
{
        struct fuse_ipc *fip;

        fip = objcache_get(fuse_ipc_objcache, M_WAITOK);
        bzero(fip, sizeof(*fip));
        refcount_init(&fip->refcnt, 1);
        fip->fmp = fmp;
        fip->unique = atomic_fetchadd_long(&fmp->unique, 1);
        fip->done = 0;

        fuse_buf_alloc(&fip->request, sizeof(struct fuse_in_header) + len);
        fip->reply.buf = NULL;

        return fip;
}

void
fuse_ipc_put(struct fuse_ipc *fip)
{
        if (refcount_release(&fip->refcnt)) {
                fuse_buf_free(&fip->request);
                fuse_buf_free(&fip->reply);
                objcache_put(fuse_ipc_objcache, fip);
        }
}

static void
fuse_ipc_remove(struct fuse_ipc *fip)
{
        struct fuse_mount *fmp = fip->fmp;
        struct fuse_ipc *p;

        mtx_lock(&fmp->ipc_lock);
        TAILQ_FOREACH(p, &fmp->request_head, request_entry) {
                if (fip == p) {
                        TAILQ_REMOVE(&fmp->request_head, p, request_entry);
                        if (atomic_swap_int(&fip->sent, 1) == -1)
                                wakeup(fip);
                        break;
                }
        }
        TAILQ_FOREACH(p, &fmp->reply_head, reply_entry) {
                if (fip == p) {
                        TAILQ_REMOVE(&fmp->reply_head, p, reply_entry);
                        break;
                }
        }
        mtx_unlock(&fmp->ipc_lock);
}

void*
fuse_ipc_fill(struct fuse_ipc *fip, int op, uint64_t ino, struct ucred *cred)
{
        if (!cred)
                cred = curthread->td_ucred;

        fuse_fill_in_header(fuse_in(fip), fuse_in_size(fip), op, fip->unique,
            ino, cred->cr_uid, cred->cr_rgid,
            curthread->td_proc ? curthread->td_proc->p_pid : 0);

        fuse_dbgipc(fip, 0, "");

        return fuse_in_data(fip);
}

static int
fuse_ipc_wait(struct fuse_ipc *fip)
{
        int error, retry = 0;

        if (fuse_test_dead(fip->fmp)) {
                KKASSERT(!fuse_ipc_test_replied(fip));
                fuse_ipc_set_replied(fip);
                return ENOTCONN;
        }

        if (fuse_ipc_test_replied(fip))
                return 0;
again:
        tsleep_interlock(fip, 0);
        if (fuse_ipc_test_replied(fip))
                return 0;
        error = tsleep(fip, PINTERLOCKED, "ftxp", 5 * hz);
        if (!error)
                KKASSERT(fuse_ipc_test_replied(fip));

        if (error == EWOULDBLOCK) {
                if (!fuse_ipc_test_replied(fip)) {
                        if (!retry)
                                fuse_print("timeout/retry\n");
                        if (retry++ < 6)
                                goto again;
                        fuse_print("timeout\n");
                        fuse_ipc_remove(fip);
                        fuse_ipc_set_replied(fip);
                        return ETIMEDOUT;
                } else
                        fuse_dbg("EWOULDBLOCK lost race\n");
        } else if (error) {
                fuse_print("error=%d\n", error);
                fuse_ipc_remove(fip);
                fuse_ipc_set_replied(fip);
                return error;
        }

        if (fuse_test_dead(fip->fmp)) {
                KKASSERT(fuse_ipc_test_replied(fip));
                return ENOTCONN;
        }

        return 0;
}

static int
fuse_ipc_wait_sent(struct fuse_ipc *fip)
{
        int error, retry = 0;

        if (fuse_test_dead(fip->fmp)) {
                KKASSERT(!fuse_ipc_test_replied(fip));
                fuse_ipc_remove(fip);
                fuse_ipc_set_replied(fip);
                return ENOTCONN;
        }

        error = 0;

        for (;;) {
                tsleep_interlock(fip, 0);
                if (atomic_swap_int(&fip->sent, -1) == 1) {
                        error = 0;
                        break;
                }
                error = tsleep(fip, PINTERLOCKED, "ftxp", 5 * hz);
                if (error == EWOULDBLOCK) {
                        ++retry;
                        if (retry == 6) {
                                fuse_print("timeout\n");
                                error = ETIMEDOUT;
                                break;
                        }
                        fuse_print("timeout/retry\n");
                }
        }
        if (fuse_test_dead(fip->fmp))
                error = ENOTCONN;
        if (error) {
                fuse_ipc_remove(fip);
                fuse_ipc_set_replied(fip);
        }
        return error;
}

int
fuse_ipc_tx(struct fuse_ipc *fip)
{
        struct fuse_mount *fmp = fip->fmp;
        struct fuse_out_header *ohd;
        int error;

        if (fuse_test_dead(fmp)) {
                fuse_ipc_put(fip);
                return ENOTCONN;
        }

        mtx_lock(&fmp->ipc_lock);
        TAILQ_INSERT_TAIL(&fmp->reply_head, fip, reply_entry);
        TAILQ_INSERT_TAIL(&fmp->request_head, fip, request_entry);
        mtx_unlock(&fmp->ipc_lock);

        wakeup(fmp);
        KNOTE(&fmp->kq.ki_note, 0);

        error = fuse_ipc_wait(fip);
        KKASSERT(fuse_ipc_test_replied(fip));
        if (error) {
                fuse_dbgipc(fip, error, "ipc_wait");
                fuse_ipc_put(fip);
                return error;
        }

        ohd = fuse_out(fip);
        KKASSERT(ohd);
        error = ohd->error;
        if (error) {
                fuse_dbgipc(fip, error, "ipc_error");
                fuse_ipc_put(fip);
                if (error < 0)
                        error = -error;
                return error;
        }
        fuse_dbgipc(fip, 0, "done");

        return 0;
}

int
fuse_ipc_tx_noreply(struct fuse_ipc *fip)
{
        struct fuse_mount *fmp = fip->fmp;
        int error;

        if (fuse_test_dead(fmp)) {
                fuse_ipc_put(fip);
                return ENOTCONN;
        }

        mtx_lock(&fmp->ipc_lock);
        TAILQ_INSERT_TAIL(&fmp->request_head, fip, request_entry);
        mtx_unlock(&fmp->ipc_lock);

        wakeup(fmp);
        KNOTE(&fmp->kq.ki_note, 0);

        error = fuse_ipc_wait_sent(fip);
        if (error)
                fuse_ipc_put(fip);
        return error;
}

void
fuse_ipc_init(void)
{
        fuse_ipc_objcache = objcache_create("fuse_ipc", 0, 0,
            NULL, NULL, NULL,
            objcache_malloc_alloc_zero, objcache_malloc_free, &fuse_ipc_args);
}

void
fuse_ipc_cleanup(void)
{
        objcache_destroy(fuse_ipc_objcache);
}