#include <sys/param.h>
#include <sys/systm.h>
#include <sys/fcntl.h>
#include <sys/event.h>
#include <sys/malloc.h>
#include <sys/mount.h>
#include <sys/refcnt.h>
#include <sys/rwlock.h>
#include <sys/stat.h>
#include <sys/statvfs.h>
#include <sys/vnode.h>
#include <sys/fusebuf.h>
#include "fusefs_node.h"
#include "fusefs.h"
SIMPLEQ_HEAD(fusebuf_head, fusebuf);
struct fuse_d {
struct rwlock fd_lock;
struct refcnt fd_refcnt;
struct fusefs_mnt *fd_fmp;
int fd_unit;
struct fusebuf_head fd_fbufs_in;
struct fusebuf_head fd_fbufs_wait;
struct klist fd_rklist;
LIST_ENTRY(fuse_d) fd_list;
};
int stat_fbufs_in = 0;
int stat_fbufs_wait = 0;
int stat_opened_fusedev = 0;
LIST_HEAD(, fuse_d) fuse_d_list;
struct fuse_d *fuse_lookup(int);
void fuseattach(int);
int fuseopen(dev_t, int, int, struct proc *);
int fuseclose(dev_t, int, int, struct proc *);
int fuseread(dev_t, struct uio *, int);
int fusewrite(dev_t, struct uio *, int);
int fusekqfilter(dev_t dev, struct knote *kn);
int filt_fuse_read(struct knote *, long);
void filt_fuse_rdetach(struct knote *);
int filt_fuse_modify(struct kevent *, struct knote *);
int filt_fuse_process(struct knote *, struct kevent *);
static const struct filterops fuse_rd_filtops = {
.f_flags = FILTEROP_ISFD | FILTEROP_MPSAFE,
.f_attach = NULL,
.f_detach = filt_fuse_rdetach,
.f_event = filt_fuse_read,
.f_modify = filt_fuse_modify,
.f_process = filt_fuse_process,
};
struct fuse_d *
fuse_lookup(int unit)
{
struct fuse_d *fd;
LIST_FOREACH(fd, &fuse_d_list, fd_list)
if (fd->fd_unit == unit) {
refcnt_take(&fd->fd_refcnt);
return (fd);
}
return (NULL);
}
void
fuse_device_cleanup(dev_t dev)
{
struct fuse_d *fd;
struct fusebuf *f, *ftmp, *lprev;
fd = fuse_lookup(minor(dev));
if (fd == NULL)
return;
lprev = NULL;
rw_enter_write(&fd->fd_lock);
SIMPLEQ_FOREACH_SAFE(f, &fd->fd_fbufs_in, fb_next, ftmp) {
DPRINTF("cleanup unprocessed msg in sc_fbufs_in\n");
if (lprev == NULL)
SIMPLEQ_REMOVE_HEAD(&fd->fd_fbufs_in, fb_next);
else
SIMPLEQ_REMOVE_AFTER(&fd->fd_fbufs_in, lprev,
fb_next);
stat_fbufs_in--;
f->fb_err = ENXIO;
wakeup(f);
lprev = f;
}
knote_locked(&fd->fd_rklist, 0);
rw_exit_write(&fd->fd_lock);
lprev = NULL;
SIMPLEQ_FOREACH_SAFE(f, &fd->fd_fbufs_wait, fb_next, ftmp) {
DPRINTF("umount unprocessed msg in sc_fbufs_wait\n");
if (lprev == NULL)
SIMPLEQ_REMOVE_HEAD(&fd->fd_fbufs_wait, fb_next);
else
SIMPLEQ_REMOVE_AFTER(&fd->fd_fbufs_wait, lprev,
fb_next);
stat_fbufs_wait--;
f->fb_err = ENXIO;
wakeup(f);
lprev = f;
}
refcnt_rele_wake(&fd->fd_refcnt);
}
void
fuse_device_queue_fbuf(dev_t dev, struct fusebuf *fbuf)
{
struct fuse_d *fd;
fd = fuse_lookup(minor(dev));
if (fd == NULL)
return;
rw_enter_write(&fd->fd_lock);
SIMPLEQ_INSERT_TAIL(&fd->fd_fbufs_in, fbuf, fb_next);
knote_locked(&fd->fd_rklist, 0);
rw_exit_write(&fd->fd_lock);
stat_fbufs_in++;
wakeup_one(&fd->fd_fbufs_in);
refcnt_rele_wake(&fd->fd_refcnt);
}
void
fuse_device_set_fmp(struct fusefs_mnt *fmp, int set)
{
struct fuse_d *fd;
fd = fuse_lookup(minor(fmp->dev));
if (fd == NULL)
return;
if (set)
fd->fd_fmp = fmp;
else {
fd->fd_fmp = NULL;
wakeup(&fd->fd_fbufs_in);
}
refcnt_rele_wake(&fd->fd_refcnt);
}
void
fuseattach(int num)
{
LIST_INIT(&fuse_d_list);
}
int
fuseopen(dev_t dev, int flags, int fmt, struct proc * p)
{
struct fuse_d *fd;
int unit = minor(dev);
if (flags & O_EXCL)
return (EBUSY);
if ((fd = fuse_lookup(unit)) != NULL) {
refcnt_rele_wake(&fd->fd_refcnt);
return (EBUSY);
}
fd = malloc(sizeof(*fd), M_DEVBUF, M_WAITOK | M_ZERO);
fd->fd_unit = unit;
SIMPLEQ_INIT(&fd->fd_fbufs_in);
SIMPLEQ_INIT(&fd->fd_fbufs_wait);
rw_init(&fd->fd_lock, "fusedlk");
klist_init_rwlock(&fd->fd_rklist, &fd->fd_lock);
refcnt_init(&fd->fd_refcnt);
LIST_INSERT_HEAD(&fuse_d_list, fd, fd_list);
stat_opened_fusedev++;
return (0);
}
int
fuseclose(dev_t dev, int flags, int fmt, struct proc *p)
{
struct fuse_d *fd;
fd = fuse_lookup(minor(dev));
if (fd == NULL)
return (EBADF);
fuse_device_cleanup(dev);
if (fd->fd_fmp) {
fd->fd_fmp->sess_init = 0;
fuse_device_set_fmp(fd->fd_fmp, 0);
}
LIST_REMOVE(fd, fd_list);
refcnt_rele(&fd->fd_refcnt);
refcnt_finalize(&fd->fd_refcnt, "fusedfd");
free(fd, M_DEVBUF, sizeof(*fd));
stat_opened_fusedev--;
return (0);
}
int
fuseread(dev_t dev, struct uio *uio, int ioflag)
{
struct fuse_d *fd;
struct fusebuf *fbuf;
int error = 0;
fd = fuse_lookup(minor(dev));
if (fd == NULL)
return (ENODEV);
if (fd->fd_fmp == NULL) {
refcnt_rele(&fd->fd_refcnt);
return (ENODEV);
}
rw_enter_write(&fd->fd_lock);
fbuf = SIMPLEQ_FIRST(&fd->fd_fbufs_in);
while (fbuf == NULL) {
if (ioflag & IO_NDELAY) {
error = EAGAIN;
goto end;
}
error = rwsleep_nsec(&fd->fd_fbufs_in, &fd->fd_lock,
PWAIT | PCATCH, "fusedr", INFSLP);
if (fd->fd_fmp == NULL) {
error = ENODEV;
goto end;
}
if (error == EINTR || error == ERESTART) {
error = EINTR;
goto end;
}
fbuf = SIMPLEQ_FIRST(&fd->fd_fbufs_in);
}
if (uio->uio_resid < sizeof(fbuf->hdr) + fbuf->op_in_len +
fbuf->fb_len) {
error = EINVAL;
goto end;
}
error = uiomove(&fbuf->hdr, sizeof(fbuf->hdr), uio);
if (error)
goto end;
error = uiomove(&fbuf->op, fbuf->op_in_len, uio);
if (error)
goto end;
if (fbuf->fb_len > 0) {
error = uiomove(fbuf->fb_dat, fbuf->fb_len, uio);
if (error)
goto end;
}
free(fbuf->fb_dat, M_FUSEFS, fbuf->fb_len);
fbuf->fb_dat = NULL;
SIMPLEQ_REMOVE_HEAD(&fd->fd_fbufs_in, fb_next);
stat_fbufs_in--;
if (fbuf->fb_type == FUSE_FORGET) {
fb_delete(fbuf);
goto end;
}
SIMPLEQ_INSERT_TAIL(&fd->fd_fbufs_wait, fbuf, fb_next);
stat_fbufs_wait++;
end:
rw_exit_write(&fd->fd_lock);
refcnt_rele_wake(&fd->fd_refcnt);
return (error);
}
int
fusewrite(dev_t dev, struct uio *uio, int ioflag)
{
struct fusebuf *lastfbuf;
struct fuse_d *fd;
struct fusebuf *fbuf;
struct fuse_out_header hdr;
int error = 0;
fd = fuse_lookup(minor(dev));
if (fd == NULL)
return (ENODEV);
if (uio->uio_resid < sizeof(hdr)) {
error = EINVAL;
goto out;
}
if ((error = uiomove(&hdr, sizeof(hdr), uio)) != 0)
goto out;
if (hdr.unique == 0) {
error = 0;
goto out;
}
SIMPLEQ_FOREACH(fbuf, &fd->fd_fbufs_wait, fb_next) {
if (fbuf->fb_uuid == hdr.unique)
break;
lastfbuf = fbuf;
}
if (fbuf == NULL) {
error = ENOENT;
goto out;
}
fbuf->fb_err = -hdr.error;
if (fbuf->fb_err) {
if (uio->uio_resid > 0) {
error = EINVAL;
fbuf->fb_err = EIO;
}
goto end;
}
if (fbuf->op_out_len > 0) {
if ((error = uiomove(&fbuf->op, fbuf->op_out_len, uio)) != 0) {
fbuf->fb_err = error;
goto end;
}
}
if (fbuf->op_out_buf) {
fbuf->fb_len = hdr.len - sizeof(hdr) - fbuf->op_out_len;
if (fbuf->fb_len > fd->fd_fmp->max_read || fbuf->fb_len < 0) {
DPRINTF("invalid fusebuf read size: %llu opcode=%d\n",
fbuf->fb_len, fbuf->fb_type);
error = EINVAL;
fbuf->fb_err = EIO;
goto end;
}
} else
fbuf->fb_len = 0;
if (uio->uio_resid != fbuf->fb_len) {
error = EINVAL;
fbuf->fb_err = EIO;
goto end;
}
if (fbuf->fb_len > 0) {
fbuf->fb_dat = malloc(fbuf->fb_len, M_FUSEFS, M_WAITOK);
if ((error = uiomove(fbuf->fb_dat, fbuf->fb_len, uio)) != 0) {
free(fbuf->fb_dat, M_FUSEFS, fbuf->fb_len);
fbuf->fb_dat = NULL;
fbuf->fb_err = error;
goto end;
}
}
if (fbuf->fb_type == FUSE_INIT && fbuf->fb_err == 0) {
if (fbuf->op.out.init.major != FUSE_KERNEL_VERSION) {
DPRINTF("unsupported major version: %d.%d\n",
fbuf->op.out.init.major, fbuf->op.out.init.minor);
error = EINVAL;
goto end;
}
if (fbuf->op.out.init.minor < 9) {
DPRINTF("unsupported minor version: %d.%d\n",
fbuf->op.out.init.major, fbuf->op.out.init.minor);
error = EINVAL;
goto end;
}
fd->fd_fmp->max_write = MIN(fbuf->op.out.init.max_write,
FUSEBUFMAXSIZE);
if (fd->fd_fmp->max_write == 0)
fd->fd_fmp->max_write = FUSEBUFMAXSIZE;
fd->fd_fmp->sess_init = 1;
}
end:
if (fbuf == SIMPLEQ_FIRST(&fd->fd_fbufs_wait))
SIMPLEQ_REMOVE_HEAD(&fd->fd_fbufs_wait, fb_next);
else
SIMPLEQ_REMOVE_AFTER(&fd->fd_fbufs_wait, lastfbuf,
fb_next);
stat_fbufs_wait--;
if (fbuf->fb_type == FUSE_INIT)
fb_delete(fbuf);
else
wakeup(fbuf);
out:
refcnt_rele_wake(&fd->fd_refcnt);
return (error);
}
int
fusekqfilter(dev_t dev, struct knote *kn)
{
struct fuse_d *fd;
struct klist *klist;
int error = 0;
fd = fuse_lookup(minor(dev));
if (fd == NULL)
return (EINVAL);
switch (kn->kn_filter) {
case EVFILT_READ:
klist = &fd->fd_rklist;
kn->kn_fop = &fuse_rd_filtops;
break;
case EVFILT_WRITE:
error = seltrue_kqfilter(dev, kn);
goto end;
default:
error = EINVAL;
goto end;
}
kn->kn_hook = fd;
klist_insert(klist, kn);
end:
refcnt_rele_wake(&fd->fd_refcnt);
return (error);
}
void
filt_fuse_rdetach(struct knote *kn)
{
struct fuse_d *fd = kn->kn_hook;
struct klist *klist = &fd->fd_rklist;
klist_remove(klist, kn);
}
int
filt_fuse_read(struct knote *kn, long hint)
{
struct fuse_d *fd = kn->kn_hook;
int event = 0;
rw_assert_wrlock(&fd->fd_lock);
if (!SIMPLEQ_EMPTY(&fd->fd_fbufs_in))
event = 1;
return (event);
}
int
filt_fuse_modify(struct kevent *kev, struct knote *kn)
{
struct fuse_d *fd = kn->kn_hook;
int active;
rw_enter_write(&fd->fd_lock);
active = knote_modify(kev, kn);
rw_exit_write(&fd->fd_lock);
return (active);
}
int
filt_fuse_process(struct knote *kn, struct kevent *kev)
{
struct fuse_d *fd = kn->kn_hook;
int active;
rw_enter_write(&fd->fd_lock);
active = knote_process(kn, kev);
rw_exit_write(&fd->fd_lock);
return (active);
}