Merge patch series "io_uring related epoll cleanups"

Jens Axboe <axboe@kernel.dk> says:

One of the nastier things about epoll is how it allows nesting contexts
inside each other, leading to the necessity of loop detection and the
issues that have come with that.

I don't believe there's any reason to support nesting on the io_uring
side, in fact IORING_OP_EPOLL_CTL is a historical mistake, imho. But
let's at least try and contain the damage and disallow nested contexts
from our side.

Christian Brauner <brauner@kernel.org> says:

Bring in the eventpoll specific io_uring changes together with the
eventpoll cleanup I did this cycle. The io_uring changes can go on top
of both through the block tree.

* patches from https://patch.msgid.link/20260514140817.623026-1-axboe@kernel.dk:
  eventpoll: rename struct epoll_filefd to epoll_key
  eventpoll: add file based control interface
  eventpoll: export is_file_epoll()
  eventpoll: pass struct epoll_filefd through ep_find() and ep_insert()

Link: https://patch.msgid.link/20260514140817.623026-1-axboe@kernel.dk
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
This commit is contained in:
Christian Brauner 2026-05-15 17:41:05 +02:00
commit 6ece1a31c5
2 changed files with 51 additions and 51 deletions

View File

@ -224,11 +224,6 @@
#define EP_ITEM_COST (sizeof(struct epitem) + sizeof(struct eppoll_entry))
struct epoll_filefd {
struct file *file;
int fd;
} __packed;
/* Wait structure used by the poll hooks */
struct eppoll_entry {
/* List header used to link this structure to the "struct epitem" */
@ -273,7 +268,7 @@ struct epitem {
struct epitem *ovflist_next;
/* The file descriptor information this item refers to */
struct epoll_filefd ffd;
struct epoll_key ffd;
/* List containing poll wait queues */
struct eppoll_entry *pwqlist;
@ -526,22 +521,13 @@ static void __init epoll_sysctls_init(void)
static const struct file_operations eventpoll_fops;
static inline bool is_file_epoll(struct file *f)
bool is_file_epoll(struct file *f)
{
return f->f_op == &eventpoll_fops;
}
/* Setup the structure that is used as key for the RB tree */
static inline void ep_set_ffd(struct epoll_filefd *ffd,
struct file *file, int fd)
{
ffd->file = file;
ffd->fd = fd;
}
/* Compare RB tree keys */
static inline int ep_cmp_ffd(struct epoll_filefd *p1,
struct epoll_filefd *p2)
static inline int ep_cmp_ffd(struct epoll_key *p1, struct epoll_key *p2)
{
return (p1->file > p2->file ? +1:
(p1->file < p2->file ? -1 : p1->fd - p2->fd));
@ -1437,17 +1423,15 @@ static int ep_alloc(struct eventpoll **pep)
* are protected by the "mtx" mutex, and ep_find() must be called with
* "mtx" held.
*/
static struct epitem *ep_find(struct eventpoll *ep, struct file *file, int fd)
static struct epitem *ep_find(struct eventpoll *ep, struct epoll_key *tf)
{
int kcmp;
struct rb_node *rbp;
struct epitem *epi, *epir = NULL;
struct epoll_filefd ffd;
ep_set_ffd(&ffd, file, fd);
for (rbp = ep->rbr.rb_root.rb_node; rbp; ) {
epi = rb_entry(rbp, struct epitem, rbn);
kcmp = ep_cmp_ffd(&ffd, &epi->ffd);
kcmp = ep_cmp_ffd(tf, &epi->ffd);
if (kcmp > 0)
rbp = rbp->rb_right;
else if (kcmp < 0)
@ -1787,8 +1771,8 @@ static int ep_attach_file(struct file *file, struct epitem *epi)
/*
* Charge the user's epoll_watches quota, allocate a fresh epitem for
* @tfile/@fd, and initialize its fields. The returned item is not yet
* linked into any data structure; the caller must install it via
* @tf, and initialize its fields. The returned item is not yet linked
* into any data structure; the caller must install it via
* ep_register_epitem() (which takes over on success) or kmem_cache_free()
* it and decrement epoll_watches on its own.
*
@ -1797,7 +1781,7 @@ static int ep_attach_file(struct file *file, struct epitem *epi)
*/
static struct epitem *ep_alloc_epitem(struct eventpoll *ep,
const struct epoll_event *event,
struct file *tfile, int fd)
struct epoll_key *tf)
{
struct epitem *epi;
@ -1814,7 +1798,7 @@ static struct epitem *ep_alloc_epitem(struct eventpoll *ep,
INIT_LIST_HEAD(&epi->rdllink);
epi->ep = ep;
ep_set_ffd(&epi->ffd, tfile, fd);
epi->ffd = *tf;
epi->event = *event;
epi_clear_ovflist(epi);
@ -1871,8 +1855,8 @@ static int ep_register_epitem(struct ep_ctl_ctx *ctx, struct eventpoll *ep,
* Must be called with "mtx" held.
*/
static int ep_insert(struct ep_ctl_ctx *ctx, struct eventpoll *ep,
const struct epoll_event *event, struct file *tfile,
int fd, int full_check)
const struct epoll_event *event, struct epoll_key *tf,
int full_check)
{
int error, pwake = 0;
__poll_t revents;
@ -1880,12 +1864,12 @@ static int ep_insert(struct ep_ctl_ctx *ctx, struct eventpoll *ep,
struct ep_pqueue epq;
struct eventpoll *tep = NULL;
if (is_file_epoll(tfile))
tep = tfile->private_data;
if (is_file_epoll(tf->file))
tep = tf->file->private_data;
lockdep_assert_irqs_enabled();
epi = ep_alloc_epitem(ep, event, tfile, fd);
epi = ep_alloc_epitem(ep, event, tf);
if (IS_ERR(epi))
return PTR_ERR(epi);
@ -2610,8 +2594,8 @@ static void ep_ctl_unlock(struct ep_ctl_ctx *ctx, struct eventpoll *ep,
}
}
int do_epoll_ctl(int epfd, int op, int fd, struct epoll_event *epds,
bool nonblock)
int do_epoll_ctl_file(struct file *f, int op, struct epoll_key *tf,
struct epoll_event *epds, bool nonblock)
{
int error;
int full_check;
@ -2619,17 +2603,8 @@ int do_epoll_ctl(int epfd, int op, int fd, struct epoll_event *epds,
struct epitem *epi;
struct ep_ctl_ctx ctx = { };
CLASS(fd, f)(epfd);
if (fd_empty(f))
return -EBADF;
/* Get the "struct file *" for the target file */
CLASS(fd, tf)(fd);
if (fd_empty(tf))
return -EBADF;
/* The target file descriptor must support poll */
if (!file_can_poll(fd_file(tf)))
if (!file_can_poll(tf->file))
return -EPERM;
/* Check if EPOLLWAKEUP is allowed */
@ -2637,10 +2612,10 @@ int do_epoll_ctl(int epfd, int op, int fd, struct epoll_event *epds,
ep_take_care_of_epollwakeup(epds);
/*
* The @epfd file must itself be an eventpoll, and we do not permit
* The @f file must itself be an eventpoll, and we do not permit
* adding an epoll file descriptor inside itself.
*/
if (fd_file(f) == fd_file(tf) || !is_file_epoll(fd_file(f)))
if (f == tf->file || !is_file_epoll(f))
return -EINVAL;
/*
@ -2651,15 +2626,14 @@ int do_epoll_ctl(int epfd, int op, int fd, struct epoll_event *epds,
if (ep_op_has_event(op) && (epds->events & EPOLLEXCLUSIVE)) {
if (op == EPOLL_CTL_MOD)
return -EINVAL;
if (op == EPOLL_CTL_ADD && (is_file_epoll(fd_file(tf)) ||
if (op == EPOLL_CTL_ADD && (is_file_epoll(tf->file) ||
(epds->events & ~EPOLLEXCLUSIVE_OK_BITS)))
return -EINVAL;
}
ep = fd_file(f)->private_data;
ep = f->private_data;
full_check = ep_ctl_lock(&ctx, ep, op, fd_file(f), fd_file(tf),
nonblock);
full_check = ep_ctl_lock(&ctx, ep, op, f, tf->file, nonblock);
if (full_check < 0)
return full_check;
@ -2667,15 +2641,14 @@ int do_epoll_ctl(int epfd, int op, int fd, struct epoll_event *epds,
* Look the target up in ep's RB tree. We hold ep->mtx, so the
* item stays valid until we release.
*/
epi = ep_find(ep, fd_file(tf), fd);
epi = ep_find(ep, tf);
error = -EINVAL;
switch (op) {
case EPOLL_CTL_ADD:
if (!epi) {
epds->events |= EPOLLERR | EPOLLHUP;
error = ep_insert(&ctx, ep, epds, fd_file(tf), fd,
full_check);
error = ep_insert(&ctx, ep, epds, tf, full_check);
} else
error = -EEXIST;
break;
@ -2706,6 +2679,25 @@ int do_epoll_ctl(int epfd, int op, int fd, struct epoll_event *epds,
return error;
}
int do_epoll_ctl(int epfd, int op, int fd, struct epoll_event *epds,
bool nonblock)
{
struct epoll_key efd;
CLASS(fd, f)(epfd);
if (fd_empty(f))
return -EBADF;
/* Get the "struct file *" for the target file */
CLASS(fd, tf)(fd);
if (fd_empty(tf))
return -EBADF;
efd.file = fd_file(tf);
efd.fd = fd;
return do_epoll_ctl_file(fd_file(f), op, &efd, epds, nonblock);
}
/*
* The following function implements the controller interface for
* the eventpoll file that enables the insertion/removal/change of

View File

@ -61,8 +61,16 @@ static inline void eventpoll_release(struct file *file)
eventpoll_release_file(file);
}
struct epoll_key {
struct file *file;
int fd;
} __packed;
int do_epoll_ctl_file(struct file *f, int op, struct epoll_key *tf,
struct epoll_event *epds, bool nonblock);
int do_epoll_ctl(int epfd, int op, int fd, struct epoll_event *epds,
bool nonblock);
bool is_file_epoll(struct file *f);
/* Tells if the epoll_ctl(2) operation needs an event copy from userspace */
static inline int ep_op_has_event(int op)