#include <sys/types.h>
#include <sys/param.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/errno.h>
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/conf.h>
#include <sys/uio.h>
#include <sys/malloc.h>
#include <sys/queue.h>
#include <sys/lock.h>
#include <sys/sx.h>
#include <sys/mutex.h>
#include <sys/proc.h>
#include <sys/mount.h>
#include <sys/sdt.h>
#include <sys/stat.h>
#include <sys/fcntl.h>
#include <sys/sysctl.h>
#include <sys/poll.h>
#include <sys/selinfo.h>
#define EXTERR_CATEGORY EXTERR_CAT_FUSE
#include <sys/exterrvar.h>
#include "fuse.h"
#include "fuse_internal.h"
#include "fuse_ipc.h"
#include <compat/linux/linux_errno.h>
#include <compat/linux/linux_errno.inc>
SDT_PROVIDER_DECLARE(fusefs);
SDT_PROBE_DEFINE2(fusefs, , device, trace, "int", "char*");
static struct cdev *fuse_dev;
static d_kqfilter_t fuse_device_filter;
static d_open_t fuse_device_open;
static d_poll_t fuse_device_poll;
static d_read_t fuse_device_read;
static d_write_t fuse_device_write;
static struct cdevsw fuse_device_cdevsw = {
.d_kqfilter = fuse_device_filter,
.d_open = fuse_device_open,
.d_name = "fuse",
.d_poll = fuse_device_poll,
.d_read = fuse_device_read,
.d_write = fuse_device_write,
.d_version = D_VERSION,
};
static int fuse_device_filt_read(struct knote *kn, long hint);
static int fuse_device_filt_write(struct knote *kn, long hint);
static void fuse_device_filt_detach(struct knote *kn);
static const struct filterops fuse_device_rfiltops = {
.f_isfd = 1,
.f_detach = fuse_device_filt_detach,
.f_event = fuse_device_filt_read,
};
static const struct filterops fuse_device_wfiltops = {
.f_isfd = 1,
.f_event = fuse_device_filt_write,
};
static void
fdata_dtor(void *arg)
{
struct fuse_data *fdata;
struct fuse_ticket *tick;
fdata = arg;
if (fdata == NULL)
return;
fdata_set_dead(fdata);
FUSE_LOCK();
fuse_lck_mtx_lock(fdata->aw_mtx);
selwakeuppri(&fdata->ks_rsel, PZERO);
while ((tick = fuse_aw_pop(fdata))) {
fuse_lck_mtx_lock(tick->tk_aw_mtx);
fticket_set_answered(tick);
tick->tk_aw_errno = ENOTCONN;
wakeup(tick);
fuse_lck_mtx_unlock(tick->tk_aw_mtx);
FUSE_ASSERT_AW_DONE(tick);
fuse_ticket_drop(tick);
}
fuse_lck_mtx_unlock(fdata->aw_mtx);
fuse_lck_mtx_lock(fdata->ms_mtx);
while ((tick = fuse_ms_pop(fdata))) {
fuse_ticket_drop(tick);
}
fuse_lck_mtx_unlock(fdata->ms_mtx);
FUSE_UNLOCK();
fdata_trydestroy(fdata);
}
static int
fuse_device_filter(struct cdev *dev, struct knote *kn)
{
struct fuse_data *data;
int error;
error = devfs_get_cdevpriv((void **)&data);
if (error == 0 && kn->kn_filter == EVFILT_READ) {
kn->kn_fop = &fuse_device_rfiltops;
kn->kn_hook = data;
knlist_add(&data->ks_rsel.si_note, kn, 0);
error = 0;
} else if (error == 0 && kn->kn_filter == EVFILT_WRITE) {
kn->kn_fop = &fuse_device_wfiltops;
error = 0;
} else if (error == 0) {
error = EXTERROR(EINVAL, "Unsupported kevent filter");
kn->kn_data = error;
}
return (error);
}
static void
fuse_device_filt_detach(struct knote *kn)
{
struct fuse_data *data;
data = (struct fuse_data*)kn->kn_hook;
MPASS(data != NULL);
knlist_remove(&data->ks_rsel.si_note, kn, 0);
kn->kn_hook = NULL;
}
static int
fuse_device_filt_read(struct knote *kn, long hint)
{
struct fuse_data *data;
int ready;
data = (struct fuse_data*)kn->kn_hook;
MPASS(data != NULL);
mtx_assert(&data->ms_mtx, MA_OWNED);
if (fdata_get_dead(data)) {
kn->kn_flags |= EV_EOF;
kn->kn_fflags = ENODEV;
kn->kn_data = 1;
ready = 1;
} else if (STAILQ_FIRST(&data->ms_head)) {
MPASS(data->ms_count >= 1);
kn->kn_data = data->ms_count;
ready = 1;
} else {
ready = 0;
}
return (ready);
}
static int
fuse_device_filt_write(struct knote *kn, long hint)
{
kn->kn_data = 0;
return (1);
}
static int
fuse_device_open(struct cdev *dev, int oflags, int devtype, struct thread *td)
{
struct fuse_data *fdata;
int error;
SDT_PROBE2(fusefs, , device, trace, 1, "device open");
fdata = fdata_alloc(dev, td->td_ucred);
error = devfs_set_cdevpriv(fdata, fdata_dtor);
if (error != 0)
fdata_trydestroy(fdata);
else
SDT_PROBE2(fusefs, , device, trace, 1, "device open success");
return (error);
}
int
fuse_device_poll(struct cdev *dev, int events, struct thread *td)
{
struct fuse_data *data;
int error, revents = 0;
error = devfs_get_cdevpriv((void **)&data);
if (error != 0)
return (events &
(POLLHUP|POLLIN|POLLRDNORM|POLLOUT|POLLWRNORM));
if (events & (POLLIN | POLLRDNORM)) {
fuse_lck_mtx_lock(data->ms_mtx);
if (fdata_get_dead(data) || STAILQ_FIRST(&data->ms_head))
revents |= events & (POLLIN | POLLRDNORM);
else
selrecord(td, &data->ks_rsel);
fuse_lck_mtx_unlock(data->ms_mtx);
}
if (events & (POLLOUT | POLLWRNORM)) {
revents |= events & (POLLOUT | POLLWRNORM);
}
return (revents);
}
int
fuse_device_read(struct cdev *dev, struct uio *uio, int ioflag)
{
int err;
struct fuse_data *data;
struct fuse_ticket *tick;
void *buf;
int buflen;
SDT_PROBE2(fusefs, , device, trace, 1, "fuse device read");
err = devfs_get_cdevpriv((void **)&data);
if (err != 0)
return (err);
fuse_lck_mtx_lock(data->ms_mtx);
again:
if (fdata_get_dead(data)) {
SDT_PROBE2(fusefs, , device, trace, 2,
"we know early on that reader should be kicked so we "
"don't wait for news");
fuse_lck_mtx_unlock(data->ms_mtx);
return (EXTERROR(ENODEV, "This FUSE session is about to be closed"));
}
if (!(tick = fuse_ms_pop(data))) {
if (ioflag & O_NONBLOCK) {
fuse_lck_mtx_unlock(data->ms_mtx);
return (EAGAIN);
} else {
err = msleep(data, &data->ms_mtx, PCATCH, "fu_msg", 0);
if (err != 0) {
fuse_lck_mtx_unlock(data->ms_mtx);
if (fdata_get_dead(data))
err = EXTERROR(ENODEV,
"This FUSE session is about to be closed");
return (err);
}
tick = fuse_ms_pop(data);
}
}
if (!tick) {
SDT_PROBE2(fusefs, , device, trace, 1, "no message on thread");
goto again;
}
fuse_lck_mtx_unlock(data->ms_mtx);
if (fdata_get_dead(data)) {
SDT_PROBE2(fusefs, , device, trace, 2,
"reader is to be sacked");
if (tick) {
SDT_PROBE2(fusefs, , device, trace, 2, "weird -- "
"\"kick\" is set tho there is message");
FUSE_ASSERT_MS_DONE(tick);
fuse_ticket_drop(tick);
}
return (EXTERROR(ENODEV, "This FUSE session is about to be closed"));
}
SDT_PROBE2(fusefs, , device, trace, 1,
"fuse device read message successfully");
buf = tick->tk_ms_fiov.base;
buflen = tick->tk_ms_fiov.len;
if (uio->uio_resid < buflen) {
fdata_set_dead(data);
SDT_PROBE2(fusefs, , device, trace, 2,
"daemon is stupid, kick it off...");
err = EXTERROR(ENODEV, "Partial read attempted");
} else {
err = uiomove(buf, buflen, uio);
}
FUSE_ASSERT_MS_DONE(tick);
fuse_ticket_drop(tick);
return (err);
}
static inline int
fuse_ohead_audit(struct fuse_out_header *ohead, struct uio *uio)
{
if (uio->uio_resid + sizeof(struct fuse_out_header) != ohead->len) {
SDT_PROBE2(fusefs, , device, trace, 1,
"Format error: body size "
"differs from size claimed by header");
return (EXTERROR(EINVAL, "Format error: body size "
"differs from size claimed by header"));
}
if (uio->uio_resid && ohead->unique != 0 && ohead->error) {
SDT_PROBE2(fusefs, , device, trace, 1,
"Format error: non zero error but message had a body");
return (EXTERROR(EINVAL, "Format error: non zero error, "
"but message had a body"));
}
return (0);
}
SDT_PROBE_DEFINE1(fusefs, , device, fuse_device_write_notify,
"struct fuse_out_header*");
SDT_PROBE_DEFINE1(fusefs, , device, fuse_device_write_missing_ticket,
"uint64_t");
SDT_PROBE_DEFINE1(fusefs, , device, fuse_device_write_found,
"struct fuse_ticket*");
static int
fuse_device_write(struct cdev *dev, struct uio *uio, int ioflag)
{
struct fuse_out_header ohead;
int err = 0;
struct fuse_data *data;
struct mount *mp;
struct fuse_ticket *tick, *itick, *x_tick;
int found = 0;
err = devfs_get_cdevpriv((void **)&data);
if (err != 0)
return (err);
if (uio->uio_resid < sizeof(struct fuse_out_header)) {
SDT_PROBE2(fusefs, , device, trace, 1,
"fuse_device_write got less than a header!");
fdata_set_dead(data);
return (EXTERROR(EINVAL, "fuse_device_write got less than a header!"));
}
if ((err = uiomove(&ohead, sizeof(struct fuse_out_header), uio)) != 0)
return (err);
if (data->linux_errnos != 0 && ohead.error != 0) {
err = -ohead.error;
if (err < 0 || err >= nitems(linux_to_bsd_errtbl))
return (EXTERROR(EINVAL, "Unknown Linux errno", err));
ohead.error = -linux_to_bsd_errtbl[err];
}
if ((err = fuse_ohead_audit(&ohead, uio))) {
fdata_set_dead(data);
return (err);
}
fuse_lck_mtx_lock(data->aw_mtx);
TAILQ_FOREACH_SAFE(tick, &data->aw_head, tk_aw_link,
x_tick) {
if (tick->tk_unique == ohead.unique) {
SDT_PROBE1(fusefs, , device, fuse_device_write_found,
tick);
found = 1;
fuse_aw_remove(tick);
break;
}
}
if (found && tick->irq_unique > 0) {
TAILQ_FOREACH_SAFE(itick, &data->aw_head, tk_aw_link,
x_tick) {
if (itick->tk_unique == tick->irq_unique) {
fuse_aw_remove(itick);
fuse_ticket_drop(itick);
break;
}
}
tick->irq_unique = 0;
}
fuse_lck_mtx_unlock(data->aw_mtx);
if (found) {
if (tick->tk_aw_handler) {
SDT_PROBE2(fusefs, , device, trace, 1,
"pass ticket to a callback");
ohead.error *= -1;
if (ohead.error < 0 || ohead.error > ELAST) {
ohead.error = EIO;
memcpy(&tick->tk_aw_ohead, &ohead,
sizeof(ohead));
tick->tk_aw_handler(tick, uio);
err = EXTERROR(EINVAL, "Unknown errno", ohead.error);
} else {
memcpy(&tick->tk_aw_ohead, &ohead,
sizeof(ohead));
err = tick->tk_aw_handler(tick, uio);
}
} else {
SDT_PROBE2(fusefs, , device, trace, 1,
"stuff devalidated, so we drop it");
}
fuse_ticket_drop(tick);
} else if (ohead.unique == 0){
SDT_PROBE1(fusefs, , device, fuse_device_write_notify, &ohead);
mp = data->mp;
vfs_ref(mp);
err = vfs_busy(mp, 0);
vfs_rel(mp);
if (err)
return (err);
switch (ohead.error) {
case FUSE_NOTIFY_INVAL_ENTRY:
err = fuse_internal_invalidate_entry(mp, uio);
break;
case FUSE_NOTIFY_INVAL_INODE:
err = fuse_internal_invalidate_inode(mp, uio);
break;
case FUSE_NOTIFY_RETRIEVE:
case FUSE_NOTIFY_STORE:
case FUSE_NOTIFY_POLL:
default:
err = EXTERROR(ENOSYS, "Unimplemented FUSE notification code",
ohead.error);
}
vfs_unbusy(mp);
} else {
SDT_PROBE1(fusefs, , device, fuse_device_write_missing_ticket,
ohead.unique);
if (ohead.error == -EAGAIN) {
err = 0;
} else {
err = EXTERROR(EINVAL, "FUSE ticket is missing");
}
}
return (err);
}
int
fuse_device_init(void)
{
fuse_dev = make_dev(&fuse_device_cdevsw, 0, UID_ROOT, GID_OPERATOR,
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH, "fuse");
if (fuse_dev == NULL)
return (ENOMEM);
return (0);
}
void
fuse_device_destroy(void)
{
MPASS(fuse_dev != NULL);
destroy_dev(fuse_dev);
}