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