| // SPDX-License-Identifier: GPL-2.0-only | |
| #include "dev.h" | |
| #include "fuse_i.h" | |
| void fuse_end_polls(struct fuse_conn *fc) | |
| { | |
| struct rb_node *p; | |
| spin_lock(&fc->lock); | |
| p = rb_first(&fc->polled_files); | |
| while (p) { | |
| struct fuse_file *ff; | |
| ff = rb_entry(p, struct fuse_file, polled_node); | |
| wake_up_interruptible_all(&ff->poll_wait); | |
| p = rb_next(p); | |
| } | |
| spin_unlock(&fc->lock); | |
| } | |
| /* | |
| * All files which have been polled are linked to RB tree | |
| * fuse_conn->polled_files which is indexed by kh. Walk the tree and | |
| * find the matching one. | |
| */ | |
| static struct rb_node **fuse_find_polled_node(struct fuse_conn *fc, u64 kh, | |
| struct rb_node **parent_out) | |
| { | |
| struct rb_node **link = &fc->polled_files.rb_node; | |
| struct rb_node *last = NULL; | |
| while (*link) { | |
| struct fuse_file *ff; | |
| last = *link; | |
| ff = rb_entry(last, struct fuse_file, polled_node); | |
| if (kh < ff->kh) | |
| link = &last->rb_left; | |
| else if (kh > ff->kh) | |
| link = &last->rb_right; | |
| else | |
| return link; | |
| } | |
| if (parent_out) | |
| *parent_out = last; | |
| return link; | |
| } | |
| /* | |
| * The file is about to be polled. Make sure it's on the polled_files | |
| * RB tree. Note that files once added to the polled_files tree are | |
| * not removed before the file is released. This is because a file | |
| * polled once is likely to be polled again. | |
| */ | |
| static void fuse_register_polled_file(struct fuse_conn *fc, | |
| struct fuse_file *ff) | |
| { | |
| spin_lock(&fc->lock); | |
| if (RB_EMPTY_NODE(&ff->polled_node)) { | |
| struct rb_node **link, *parent; | |
| link = fuse_find_polled_node(fc, ff->kh, &parent); | |
| BUG_ON(*link); | |
| rb_link_node(&ff->polled_node, parent, link); | |
| rb_insert_color(&ff->polled_node, &fc->polled_files); | |
| } | |
| spin_unlock(&fc->lock); | |
| } | |
| __poll_t fuse_file_poll(struct file *file, poll_table *wait) | |
| { | |
| struct fuse_file *ff = file->private_data; | |
| struct fuse_mount *fm = ff->fm; | |
| struct fuse_poll_in inarg = { .fh = ff->fh, .kh = ff->kh }; | |
| struct fuse_poll_out outarg; | |
| FUSE_ARGS(args); | |
| int err; | |
| if (fm->fc->no_poll) | |
| return DEFAULT_POLLMASK; | |
| poll_wait(file, &ff->poll_wait, wait); | |
| inarg.events = mangle_poll(poll_requested_events(wait)); | |
| /* | |
| * Ask for notification iff there's someone waiting for it. | |
| * The client may ignore the flag and always notify. | |
| */ | |
| if (waitqueue_active(&ff->poll_wait)) { | |
| inarg.flags |= FUSE_POLL_SCHEDULE_NOTIFY; | |
| fuse_register_polled_file(fm->fc, ff); | |
| } | |
| args.opcode = FUSE_POLL; | |
| args.nodeid = ff->nodeid; | |
| args.in_numargs = 1; | |
| args.in_args[0].size = sizeof(inarg); | |
| args.in_args[0].value = &inarg; | |
| args.out_numargs = 1; | |
| args.out_args[0].size = sizeof(outarg); | |
| args.out_args[0].value = &outarg; | |
| err = fuse_simple_request(fm, &args); | |
| if (!err) | |
| return demangle_poll(outarg.revents); | |
| if (err == -ENOSYS) { | |
| fm->fc->no_poll = 1; | |
| return DEFAULT_POLLMASK; | |
| } | |
| return EPOLLERR; | |
| } | |
| EXPORT_SYMBOL_GPL(fuse_file_poll); | |
| /* | |
| * This is called from fuse_handle_notify() on FUSE_NOTIFY_POLL and | |
| * wakes up the poll waiters. | |
| */ | |
| int fuse_notify_poll_wakeup(struct fuse_conn *fc, | |
| struct fuse_notify_poll_wakeup_out *outarg) | |
| { | |
| u64 kh = outarg->kh; | |
| struct rb_node **link; | |
| spin_lock(&fc->lock); | |
| link = fuse_find_polled_node(fc, kh, NULL); | |
| if (*link) { | |
| struct fuse_file *ff; | |
| ff = rb_entry(*link, struct fuse_file, polled_node); | |
| wake_up_interruptible_sync(&ff->poll_wait); | |
| } | |
| spin_unlock(&fc->lock); | |
| return 0; | |
| } | |