summaryrefslogtreecommitdiff
path: root/fs/eventpoll.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/eventpoll.c')
-rw-r--r--fs/eventpoll.c18
1 files changed, 17 insertions, 1 deletions
diff --git a/fs/eventpoll.c b/fs/eventpoll.c
index 0e65c7431dfc..eed8cecd94e3 100644
--- a/fs/eventpoll.c
+++ b/fs/eventpoll.c
@@ -459,11 +459,14 @@ static struct kmem_cache *pwq_cache __ro_after_init;
* Wrapper anchor for file->f_ep when the watched file is not itself an
* eventpoll; for the epoll-watches-epoll case, file->f_ep points at
* &watched_ep->refs directly. The ->next field threads
- * ctx->tfile_check_list during one EPOLL_CTL_ADD path check.
+ * ctx->tfile_check_list during one EPOLL_CTL_ADD path check. The ->file
+ * field holds a reference to the associated file while the head is on
+ * the list.
*/
struct epitems_head {
struct hlist_head epitems;
struct epitems_head *next;
+ struct file *file;
};
static struct kmem_cache *ephead_cache __ro_after_init;
@@ -480,6 +483,16 @@ static void list_file(struct file *file, struct ep_ctl_ctx *ctx)
head = container_of(file->f_ep, struct epitems_head, epitems);
if (!head->next) {
+ /*
+ * The caller owns a reference to @file or holds the ep->mtx for the
+ * epitem that led here. The latter blocks eventpoll_release_file()
+ * before the file allocation can be freed and reused. A dying leaf
+ * can be skipped since removing links cannot increase the reverse
+ * path count.
+ */
+ if (!file_ref_get(&file->f_ref))
+ return;
+ head->file = file;
head->next = ctx->tfile_check_list;
ctx->tfile_check_list = head;
}
@@ -489,15 +502,18 @@ static void unlist_file(struct epitems_head *head)
{
struct epitems_head *to_free = head;
struct hlist_node *p = rcu_dereference(hlist_first_rcu(&head->epitems));
+ struct file *file = head->file;
if (p) {
struct epitem *epi= container_of(p, struct epitem, fllink);
spin_lock(&epi->ffd.file->f_lock);
if (!hlist_empty(&head->epitems))
to_free = NULL;
head->next = NULL;
+ head->file = NULL;
spin_unlock(&epi->ffd.file->f_lock);
}
free_ephead(to_free);
+ fput(file);
}
#ifdef CONFIG_SYSCTL