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