#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);
}