Changeset View
Changeset View
Standalone View
Standalone View
sys/fs/fuse/fuse_device.c
Show All 27 Lines | |||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
* | * | ||||
* Copyright (C) 2005 Csaba Henk. | * Copyright (C) 2005 Csaba Henk. | ||||
* All rights reserved. | * All rights reserved. | ||||
* | * | ||||
* Copyright (c) 2019 The FreeBSD Foundation | |||||
* | |||||
* Portions of this software were developed by BFF Storage Systems, LLC under | |||||
* sponsorship from the FreeBSD Foundation. | |||||
* | |||||
* Redistribution and use in source and binary forms, with or without | * Redistribution and use in source and binary forms, with or without | ||||
* modification, are permitted provided that the following conditions | * modification, are permitted provided that the following conditions | ||||
* are met: | * are met: | ||||
* 1. Redistributions of source code must retain the above copyright | * 1. Redistributions of source code must retain the above copyright | ||||
* notice, this list of conditions and the following disclaimer. | * notice, this list of conditions and the following disclaimer. | ||||
* 2. Redistributions in binary form must reproduce the above copyright | * 2. Redistributions in binary form must reproduce the above copyright | ||||
* notice, this list of conditions and the following disclaimer in the | * notice, this list of conditions and the following disclaimer in the | ||||
* documentation and/or other materials provided with the distribution. | * documentation and/or other materials provided with the distribution. | ||||
Show All 32 Lines | |||||
#include <sys/sdt.h> | #include <sys/sdt.h> | ||||
#include <sys/stat.h> | #include <sys/stat.h> | ||||
#include <sys/fcntl.h> | #include <sys/fcntl.h> | ||||
#include <sys/sysctl.h> | #include <sys/sysctl.h> | ||||
#include <sys/poll.h> | #include <sys/poll.h> | ||||
#include <sys/selinfo.h> | #include <sys/selinfo.h> | ||||
#include "fuse.h" | #include "fuse.h" | ||||
#include "fuse_internal.h" | |||||
#include "fuse_ipc.h" | #include "fuse_ipc.h" | ||||
SDT_PROVIDER_DECLARE(fuse); | SDT_PROVIDER_DECLARE(fusefs); | ||||
/* | /* | ||||
* Fuse trace probe: | * Fuse trace probe: | ||||
* arg0: verbosity. Higher numbers give more verbose messages | * arg0: verbosity. Higher numbers give more verbose messages | ||||
* arg1: Textual message | * arg1: Textual message | ||||
*/ | */ | ||||
SDT_PROBE_DEFINE2(fuse, , device, trace, "int", "char*"); | SDT_PROBE_DEFINE2(fusefs, , device, trace, "int", "char*"); | ||||
static struct cdev *fuse_dev; | static struct cdev *fuse_dev; | ||||
static d_kqfilter_t fuse_device_filter; | |||||
static d_open_t fuse_device_open; | static d_open_t fuse_device_open; | ||||
static d_close_t fuse_device_close; | |||||
static d_poll_t fuse_device_poll; | static d_poll_t fuse_device_poll; | ||||
static d_read_t fuse_device_read; | static d_read_t fuse_device_read; | ||||
static d_write_t fuse_device_write; | static d_write_t fuse_device_write; | ||||
static struct cdevsw fuse_device_cdevsw = { | static struct cdevsw fuse_device_cdevsw = { | ||||
.d_kqfilter = fuse_device_filter, | |||||
.d_open = fuse_device_open, | .d_open = fuse_device_open, | ||||
.d_close = fuse_device_close, | |||||
.d_name = "fuse", | .d_name = "fuse", | ||||
.d_poll = fuse_device_poll, | .d_poll = fuse_device_poll, | ||||
.d_read = fuse_device_read, | .d_read = fuse_device_read, | ||||
.d_write = fuse_device_write, | .d_write = fuse_device_write, | ||||
.d_version = D_VERSION, | .d_version = D_VERSION, | ||||
}; | }; | ||||
static int fuse_device_filt_read(struct knote *kn, long hint); | |||||
static void fuse_device_filt_detach(struct knote *kn); | |||||
struct filterops fuse_device_rfiltops = { | |||||
.f_isfd = 1, | |||||
.f_detach = fuse_device_filt_detach, | |||||
.f_event = fuse_device_filt_read, | |||||
}; | |||||
/**************************** | /**************************** | ||||
* | * | ||||
* >>> Fuse device op defs | * >>> Fuse device op defs | ||||
* | * | ||||
****************************/ | ****************************/ | ||||
static void | static void | ||||
fdata_dtor(void *arg) | fdata_dtor(void *arg) | ||||
{ | { | ||||
struct fuse_data *fdata; | struct fuse_data *fdata; | ||||
struct fuse_ticket *tick; | |||||
fdata = arg; | fdata = arg; | ||||
if (fdata == NULL) | |||||
return; | |||||
fdata_set_dead(fdata); | |||||
FUSE_LOCK(); | |||||
fuse_lck_mtx_lock(fdata->aw_mtx); | |||||
/* wakup poll()ers */ | |||||
selwakeuppri(&fdata->ks_rsel, PZERO + 1); | |||||
/* Don't let syscall handlers wait in vain */ | |||||
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); | |||||
/* Cleanup unsent operations */ | |||||
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); | 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); | |||||
/* EVFILT_WRITE is not supported; the device is always ready to write */ | |||||
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) { | |||||
error = EINVAL; | |||||
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); | |||||
} | |||||
/* | /* | ||||
* Resources are set up on a per-open basis | * Resources are set up on a per-open basis | ||||
*/ | */ | ||||
static int | static int | ||||
fuse_device_open(struct cdev *dev, int oflags, int devtype, struct thread *td) | fuse_device_open(struct cdev *dev, int oflags, int devtype, struct thread *td) | ||||
{ | { | ||||
struct fuse_data *fdata; | struct fuse_data *fdata; | ||||
int error; | int error; | ||||
SDT_PROBE2(fuse, , device, trace, 1, "device open"); | SDT_PROBE2(fusefs, , device, trace, 1, "device open"); | ||||
fdata = fdata_alloc(dev, td->td_ucred); | fdata = fdata_alloc(dev, td->td_ucred); | ||||
error = devfs_set_cdevpriv(fdata, fdata_dtor); | error = devfs_set_cdevpriv(fdata, fdata_dtor); | ||||
if (error != 0) | if (error != 0) | ||||
fdata_trydestroy(fdata); | fdata_trydestroy(fdata); | ||||
else | else | ||||
SDT_PROBE2(fuse, , device, trace, 1, "device open success"); | SDT_PROBE2(fusefs, , device, trace, 1, "device open success"); | ||||
return (error); | return (error); | ||||
} | } | ||||
static int | |||||
fuse_device_close(struct cdev *dev, int fflag, int devtype, struct thread *td) | |||||
{ | |||||
struct fuse_data *data; | |||||
struct fuse_ticket *tick; | |||||
int error; | |||||
error = devfs_get_cdevpriv((void **)&data); | |||||
if (error != 0) | |||||
return (error); | |||||
if (!data) | |||||
panic("no fuse data upon fuse device close"); | |||||
fdata_set_dead(data); | |||||
FUSE_LOCK(); | |||||
fuse_lck_mtx_lock(data->aw_mtx); | |||||
/* wakup poll()ers */ | |||||
selwakeuppri(&data->ks_rsel, PZERO + 1); | |||||
/* Don't let syscall handlers wait in vain */ | |||||
while ((tick = fuse_aw_pop(data))) { | |||||
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(data->aw_mtx); | |||||
FUSE_UNLOCK(); | |||||
SDT_PROBE2(fuse, , device, trace, 1, "device close"); | |||||
return (0); | |||||
} | |||||
int | int | ||||
fuse_device_poll(struct cdev *dev, int events, struct thread *td) | fuse_device_poll(struct cdev *dev, int events, struct thread *td) | ||||
{ | { | ||||
struct fuse_data *data; | struct fuse_data *data; | ||||
int error, revents = 0; | int error, revents = 0; | ||||
error = devfs_get_cdevpriv((void **)&data); | error = devfs_get_cdevpriv((void **)&data); | ||||
if (error != 0) | if (error != 0) | ||||
Show All 24 Lines | |||||
{ | { | ||||
int err; | int err; | ||||
struct fuse_data *data; | struct fuse_data *data; | ||||
struct fuse_ticket *tick; | struct fuse_ticket *tick; | ||||
void *buf[] = {NULL, NULL, NULL}; | void *buf[] = {NULL, NULL, NULL}; | ||||
int buflen[3]; | int buflen[3]; | ||||
int i; | int i; | ||||
SDT_PROBE2(fuse, , device, trace, 1, "fuse device read"); | SDT_PROBE2(fusefs, , device, trace, 1, "fuse device read"); | ||||
err = devfs_get_cdevpriv((void **)&data); | err = devfs_get_cdevpriv((void **)&data); | ||||
if (err != 0) | if (err != 0) | ||||
return (err); | return (err); | ||||
fuse_lck_mtx_lock(data->ms_mtx); | fuse_lck_mtx_lock(data->ms_mtx); | ||||
again: | again: | ||||
if (fdata_get_dead(data)) { | if (fdata_get_dead(data)) { | ||||
SDT_PROBE2(fuse, , device, trace, 2, | SDT_PROBE2(fusefs, , device, trace, 2, | ||||
"we know early on that reader should be kicked so we " | "we know early on that reader should be kicked so we " | ||||
"don't wait for news"); | "don't wait for news"); | ||||
fuse_lck_mtx_unlock(data->ms_mtx); | fuse_lck_mtx_unlock(data->ms_mtx); | ||||
return (ENODEV); | return (ENODEV); | ||||
} | } | ||||
if (!(tick = fuse_ms_pop(data))) { | if (!(tick = fuse_ms_pop(data))) { | ||||
/* check if we may block */ | /* check if we may block */ | ||||
if (ioflag & O_NONBLOCK) { | if (ioflag & O_NONBLOCK) { | ||||
Show All 11 Lines | again: | ||||
} | } | ||||
if (!tick) { | if (!tick) { | ||||
/* | /* | ||||
* We can get here if fuse daemon suddenly terminates, | * We can get here if fuse daemon suddenly terminates, | ||||
* eg, by being hit by a SIGKILL | * eg, by being hit by a SIGKILL | ||||
* -- and some other cases, too, tho not totally clear, when | * -- and some other cases, too, tho not totally clear, when | ||||
* (cv_signal/wakeup_one signals the whole process ?) | * (cv_signal/wakeup_one signals the whole process ?) | ||||
*/ | */ | ||||
SDT_PROBE2(fuse, , device, trace, 1, "no message on thread"); | SDT_PROBE2(fusefs, , device, trace, 1, "no message on thread"); | ||||
goto again; | goto again; | ||||
} | } | ||||
fuse_lck_mtx_unlock(data->ms_mtx); | fuse_lck_mtx_unlock(data->ms_mtx); | ||||
if (fdata_get_dead(data)) { | if (fdata_get_dead(data)) { | ||||
/* | /* | ||||
* somebody somewhere -- eg., umount routine -- | * somebody somewhere -- eg., umount routine -- | ||||
* wants this liaison finished off | * wants this liaison finished off | ||||
*/ | */ | ||||
SDT_PROBE2(fuse, , device, trace, 2, "reader is to be sacked"); | SDT_PROBE2(fusefs, , device, trace, 2, | ||||
"reader is to be sacked"); | |||||
if (tick) { | if (tick) { | ||||
SDT_PROBE2(fuse, , device, trace, 2, "weird -- " | SDT_PROBE2(fusefs, , device, trace, 2, "weird -- " | ||||
"\"kick\" is set tho there is message"); | "\"kick\" is set tho there is message"); | ||||
FUSE_ASSERT_MS_DONE(tick); | FUSE_ASSERT_MS_DONE(tick); | ||||
fuse_ticket_drop(tick); | fuse_ticket_drop(tick); | ||||
} | } | ||||
return (ENODEV); /* This should make the daemon get off | return (ENODEV); /* This should make the daemon get off | ||||
* of us */ | * of us */ | ||||
} | } | ||||
SDT_PROBE2(fuse, , device, trace, 1, | SDT_PROBE2(fusefs, , device, trace, 1, | ||||
"fuse device read message successfully"); | "fuse device read message successfully"); | ||||
KASSERT(tick->tk_ms_bufdata || tick->tk_ms_bufsize == 0, | KASSERT(tick->tk_ms_bufdata || tick->tk_ms_bufsize == 0, | ||||
("non-null buf pointer with positive size")); | ("non-null buf pointer with positive size")); | ||||
switch (tick->tk_ms_type) { | switch (tick->tk_ms_type) { | ||||
case FT_M_FIOV: | case FT_M_FIOV: | ||||
buf[0] = tick->tk_ms_fiov.base; | buf[0] = tick->tk_ms_fiov.base; | ||||
Show All 18 Lines | for (i = 0; buf[i]; i++) { | ||||
* XXX note that in such cases Linux FUSE throws EIO at the | * XXX note that in such cases Linux FUSE throws EIO at the | ||||
* syscall invoker and stands back to the message queue. The | * syscall invoker and stands back to the message queue. The | ||||
* rationale should be made clear (and possibly adopt that | * rationale should be made clear (and possibly adopt that | ||||
* behaviour). Keeping the current scheme at least makes | * behaviour). Keeping the current scheme at least makes | ||||
* fallacy as loud as possible... | * fallacy as loud as possible... | ||||
*/ | */ | ||||
if (uio->uio_resid < buflen[i]) { | if (uio->uio_resid < buflen[i]) { | ||||
fdata_set_dead(data); | fdata_set_dead(data); | ||||
SDT_PROBE2(fuse, , device, trace, 2, | SDT_PROBE2(fusefs, , device, trace, 2, | ||||
"daemon is stupid, kick it off..."); | "daemon is stupid, kick it off..."); | ||||
err = ENODEV; | err = ENODEV; | ||||
break; | break; | ||||
} | } | ||||
err = uiomove(buf[i], buflen[i], uio); | err = uiomove(buf[i], buflen[i], uio); | ||||
if (err) | if (err) | ||||
break; | break; | ||||
} | } | ||||
FUSE_ASSERT_MS_DONE(tick); | FUSE_ASSERT_MS_DONE(tick); | ||||
fuse_ticket_drop(tick); | fuse_ticket_drop(tick); | ||||
return (err); | return (err); | ||||
} | } | ||||
static inline int | static inline int | ||||
fuse_ohead_audit(struct fuse_out_header *ohead, struct uio *uio) | fuse_ohead_audit(struct fuse_out_header *ohead, struct uio *uio) | ||||
{ | { | ||||
if (uio->uio_resid + sizeof(struct fuse_out_header) != ohead->len) { | if (uio->uio_resid + sizeof(struct fuse_out_header) != ohead->len) { | ||||
SDT_PROBE2(fuse, , device, trace, 1, "Format error: body size " | SDT_PROBE2(fusefs, , device, trace, 1, | ||||
"Format error: body size " | |||||
"differs from size claimed by header"); | "differs from size claimed by header"); | ||||
return (EINVAL); | return (EINVAL); | ||||
} | } | ||||
if (uio->uio_resid && ohead->error) { | if (uio->uio_resid && ohead->unique != 0 && ohead->error) { | ||||
SDT_PROBE2(fuse, , device, trace, 1, | SDT_PROBE2(fusefs, , device, trace, 1, | ||||
"Format error: non zero error but message had a body"); | "Format error: non zero error but message had a body"); | ||||
return (EINVAL); | return (EINVAL); | ||||
} | } | ||||
/* Sanitize the linuxism of negative errnos */ | |||||
ohead->error = -(ohead->error); | |||||
return (0); | return (0); | ||||
} | } | ||||
SDT_PROBE_DEFINE1(fuse, , device, fuse_device_write_bumped_into_callback, | 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"); | "uint64_t"); | ||||
SDT_PROBE_DEFINE1(fusefs, , device, fuse_device_write_found, | |||||
"struct fuse_ticket*"); | |||||
/* | /* | ||||
* fuse_device_write first reads the header sent by the daemon. | * fuse_device_write first reads the header sent by the daemon. | ||||
* If that's OK, looks up ticket/callback node by the unique id seen in header. | * If that's OK, looks up ticket/callback node by the unique id seen in header. | ||||
* If the callback node contains a handler function, the uio is passed over | * If the callback node contains a handler function, the uio is passed over | ||||
* that. | * that. | ||||
*/ | */ | ||||
static int | static int | ||||
fuse_device_write(struct cdev *dev, struct uio *uio, int ioflag) | fuse_device_write(struct cdev *dev, struct uio *uio, int ioflag) | ||||
{ | { | ||||
struct fuse_out_header ohead; | struct fuse_out_header ohead; | ||||
int err = 0; | int err = 0; | ||||
struct fuse_data *data; | struct fuse_data *data; | ||||
struct fuse_ticket *tick, *x_tick; | struct mount *mp; | ||||
struct fuse_ticket *tick, *itick, *x_tick; | |||||
int found = 0; | int found = 0; | ||||
err = devfs_get_cdevpriv((void **)&data); | err = devfs_get_cdevpriv((void **)&data); | ||||
if (err != 0) | if (err != 0) | ||||
return (err); | return (err); | ||||
mp = data->mp; | |||||
if (uio->uio_resid < sizeof(struct fuse_out_header)) { | if (uio->uio_resid < sizeof(struct fuse_out_header)) { | ||||
SDT_PROBE2(fuse, , device, trace, 1, | SDT_PROBE2(fusefs, , device, trace, 1, | ||||
"fuse_device_write got less than a header!"); | "fuse_device_write got less than a header!"); | ||||
fdata_set_dead(data); | fdata_set_dead(data); | ||||
return (EINVAL); | return (EINVAL); | ||||
} | } | ||||
if ((err = uiomove(&ohead, sizeof(struct fuse_out_header), uio)) != 0) | if ((err = uiomove(&ohead, sizeof(struct fuse_out_header), uio)) != 0) | ||||
return (err); | return (err); | ||||
/* | /* | ||||
* We check header information (which is redundant) and compare it | * We check header information (which is redundant) and compare it | ||||
* with what we see. If we see some inconsistency we discard the | * with what we see. If we see some inconsistency we discard the | ||||
* whole answer and proceed on as if it had never existed. In | * whole answer and proceed on as if it had never existed. In | ||||
* particular, no pretender will be woken up, regardless the | * particular, no pretender will be woken up, regardless the | ||||
* "unique" value in the header. | * "unique" value in the header. | ||||
*/ | */ | ||||
if ((err = fuse_ohead_audit(&ohead, uio))) { | if ((err = fuse_ohead_audit(&ohead, uio))) { | ||||
fdata_set_dead(data); | fdata_set_dead(data); | ||||
return (err); | return (err); | ||||
} | } | ||||
/* Pass stuff over to callback if there is one installed */ | /* Pass stuff over to callback if there is one installed */ | ||||
/* Looking for ticket with the unique id of header */ | /* Looking for ticket with the unique id of header */ | ||||
fuse_lck_mtx_lock(data->aw_mtx); | fuse_lck_mtx_lock(data->aw_mtx); | ||||
TAILQ_FOREACH_SAFE(tick, &data->aw_head, tk_aw_link, | TAILQ_FOREACH_SAFE(tick, &data->aw_head, tk_aw_link, | ||||
x_tick) { | x_tick) { | ||||
SDT_PROBE1(fuse, , device, | |||||
fuse_device_write_bumped_into_callback, | |||||
tick->tk_unique); | |||||
if (tick->tk_unique == ohead.unique) { | if (tick->tk_unique == ohead.unique) { | ||||
SDT_PROBE1(fusefs, , device, fuse_device_write_found, | |||||
tick); | |||||
found = 1; | found = 1; | ||||
fuse_aw_remove(tick); | fuse_aw_remove(tick); | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
if (found && tick->irq_unique > 0) { | |||||
/* | |||||
* Discard the FUSE_INTERRUPT ticket that tried to interrupt | |||||
* this operation | |||||
*/ | |||||
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); | fuse_lck_mtx_unlock(data->aw_mtx); | ||||
if (found) { | if (found) { | ||||
if (tick->tk_aw_handler) { | if (tick->tk_aw_handler) { | ||||
/* | /* | ||||
* We found a callback with proper handler. In this | * We found a callback with proper handler. In this | ||||
* case the out header will be 0wnd by the callback, | * case the out header will be 0wnd by the callback, | ||||
* so the fun of freeing that is left for her. | * so the fun of freeing that is left for her. | ||||
* (Then, by all chance, she'll just get that's done | * (Then, by all chance, she'll just get that's done | ||||
* via ticket_drop(), so no manual mucking | * via ticket_drop(), so no manual mucking | ||||
* around...) | * around...) | ||||
*/ | */ | ||||
SDT_PROBE2(fuse, , device, trace, 1, | SDT_PROBE2(fusefs, , device, trace, 1, | ||||
"pass ticket to a callback"); | "pass ticket to a callback"); | ||||
/* Sanitize the linuxism of negative errnos */ | |||||
ohead.error *= -1; | |||||
memcpy(&tick->tk_aw_ohead, &ohead, sizeof(ohead)); | memcpy(&tick->tk_aw_ohead, &ohead, sizeof(ohead)); | ||||
err = tick->tk_aw_handler(tick, uio); | err = tick->tk_aw_handler(tick, uio); | ||||
} else { | } else { | ||||
/* pretender doesn't wanna do anything with answer */ | /* pretender doesn't wanna do anything with answer */ | ||||
SDT_PROBE2(fuse, , device, trace, 1, | SDT_PROBE2(fusefs, , device, trace, 1, | ||||
"stuff devalidated, so we drop it"); | "stuff devalidated, so we drop it"); | ||||
} | } | ||||
/* | /* | ||||
* As aw_mtx was not held during the callback execution the | * As aw_mtx was not held during the callback execution the | ||||
* ticket may have been inserted again. However, this is safe | * ticket may have been inserted again. However, this is safe | ||||
* because fuse_ticket_drop() will deal with refcount anyway. | * because fuse_ticket_drop() will deal with refcount anyway. | ||||
*/ | */ | ||||
fuse_ticket_drop(tick); | fuse_ticket_drop(tick); | ||||
} else if (ohead.unique == 0){ | |||||
/* unique == 0 means asynchronous notification */ | |||||
SDT_PROBE1(fusefs, , device, fuse_device_write_notify, &ohead); | |||||
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: | |||||
/* | |||||
* Unimplemented. I don't know of any file systems | |||||
* that use them, and the protocol isn't sound anyway, | |||||
* since the notification messages don't include the | |||||
* inode's generation number. Without that, it's | |||||
* possible to manipulate the cache of the wrong vnode. | |||||
* Finally, it's not defined what this message should | |||||
* do for a file with dirty cache. | |||||
*/ | |||||
case FUSE_NOTIFY_POLL: | |||||
/* Unimplemented. See comments in fuse_vnops */ | |||||
default: | |||||
/* Not implemented */ | |||||
err = ENOSYS; | |||||
} | |||||
} else { | } else { | ||||
/* no callback at all! */ | /* no callback at all! */ | ||||
SDT_PROBE2(fuse, , device, trace, 1, | SDT_PROBE1(fusefs, , device, fuse_device_write_missing_ticket, | ||||
"erhm, no handler for this response"); | ohead.unique); | ||||
if (ohead.error == -EAGAIN) { | |||||
/* | |||||
* This was probably a response to a FUSE_INTERRUPT | |||||
* operation whose original operation is already | |||||
* complete. We can't store FUSE_INTERRUPT tickets | |||||
* indefinitely because their responses are optional. | |||||
* So we delete them when the original operation | |||||
* completes. And sadly the fuse_header_out doesn't | |||||
* identify the opcode, so we have to guess. | |||||
*/ | |||||
err = 0; | |||||
} else { | |||||
err = EINVAL; | err = EINVAL; | ||||
} | } | ||||
} | |||||
return (err); | return (err); | ||||
} | } | ||||
int | int | ||||
fuse_device_init(void) | fuse_device_init(void) | ||||
{ | { | ||||
fuse_dev = make_dev(&fuse_device_cdevsw, 0, UID_ROOT, GID_OPERATOR, | fuse_dev = make_dev(&fuse_device_cdevsw, 0, UID_ROOT, GID_OPERATOR, | ||||
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP, "fuse"); | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH, "fuse"); | ||||
if (fuse_dev == NULL) | if (fuse_dev == NULL) | ||||
return (ENOMEM); | return (ENOMEM); | ||||
return (0); | return (0); | ||||
} | } | ||||
void | void | ||||
fuse_device_destroy(void) | fuse_device_destroy(void) | ||||
{ | { | ||||
MPASS(fuse_dev != NULL); | MPASS(fuse_dev != NULL); | ||||
destroy_dev(fuse_dev); | destroy_dev(fuse_dev); | ||||
} | } |