Add fsnotify_restart_event() which reinserts an event at the head of the notification queue for reprocessing, without merge, insert, or overflow handling. This is used by fanotify to restart permission events when a queue fd is released. Add fanotify_restart_pending_events() helper that walks the access_list and reinserts each pending permission event back into the notification queue using fsnotify_restart_event(). Suggested-by: Jan Kara Link: https://lore.kernel.org/linux-fsdevel/sx5g7pmkchjqucfbzi77xh7wx4wua5nteqi5bsa2hfqgxua2a2@v7x6ja3gsirn/ Signed-off-by: Ibrahim Jirdeh --- fs/notify/fanotify/fanotify_user.c | 27 +++++++++++++++++++++++++++ fs/notify/notification.c | 25 +++++++++++++++++++++++++ include/linux/fsnotify_backend.h | 4 ++++ 3 files changed, 56 insertions(+) diff --git a/fs/notify/fanotify/fanotify_user.c b/fs/notify/fanotify/fanotify_user.c index d0b9b984002f..8203ecf5ea39 100644 --- a/fs/notify/fanotify/fanotify_user.c +++ b/fs/notify/fanotify/fanotify_user.c @@ -1095,6 +1095,33 @@ static ssize_t fanotify_write(struct file *file, const char __user *buf, size_t return count; } +static void fanotify_restart_pending_events(struct fsnotify_group *group) +{ + spin_lock(&group->notification_lock); + while (!list_empty(&group->fanotify_data.access_list)) { + struct fanotify_perm_event *event; + int ret; + + event = list_first_entry(&group->fanotify_data.access_list, + struct fanotify_perm_event, + fae.fse.list); + list_del_init(&event->fae.fse.list); + spin_unlock(&group->notification_lock); + + ret = fsnotify_restart_event(group, &event->fae.fse); + if (ret) { + /* + * Group is being shut down. Reply ALLOW for the event. + */ + spin_lock(&group->notification_lock); + finish_permission_event(group, event, FAN_ALLOW, NULL); + } + + spin_lock(&group->notification_lock); + } + spin_unlock(&group->notification_lock); +} + static int fanotify_release(struct inode *ignored, struct file *file) { struct fsnotify_group *group = file->private_data; diff --git a/fs/notify/notification.c b/fs/notify/notification.c index 9022ae650cf8..2d377122c904 100644 --- a/fs/notify/notification.c +++ b/fs/notify/notification.c @@ -129,6 +129,31 @@ int fsnotify_insert_event(struct fsnotify_group *group, return ret; } +/* + * Reinsert an event to the head of the notification queue for reprocessing. + * No merge or overflow handling. + * The function returns: + * 0 if the event was added to a queue + * 2 if the event was not queued - the group is shutting down. + */ +int fsnotify_restart_event(struct fsnotify_group *group, + struct fsnotify_event *event) +{ + spin_lock(&group->notification_lock); + + if (group->shutdown) { + spin_unlock(&group->notification_lock); + return 2; + } + + group->q_len++; + list_add(&event->list, &group->notification_list); + spin_unlock(&group->notification_lock); + + wake_up(&group->notification_waitq); + return 0; +} + void fsnotify_remove_queued_event(struct fsnotify_group *group, struct fsnotify_event *event) { diff --git a/include/linux/fsnotify_backend.h b/include/linux/fsnotify_backend.h index 0d954ea7b179..573c796513bf 100644 --- a/include/linux/fsnotify_backend.h +++ b/include/linux/fsnotify_backend.h @@ -736,6 +736,10 @@ static inline int fsnotify_add_event(struct fsnotify_group *group, return fsnotify_insert_event(group, event, merge, NULL); } +/* Reinsert event at head of queue for reprocessing */ +extern int fsnotify_restart_event(struct fsnotify_group *group, + struct fsnotify_event *event); + /* Queue overflow event to a notification group */ static inline void fsnotify_queue_overflow(struct fsnotify_group *group) { -- 2.52.0