forked from Minki/linux
cb2d429faf
This introduces an ordering to fsnotify groups. With purely asynchronous notification based "things" implementing fsnotify (inotify, dnotify) ordering isn't particularly important. But if people want to use fsnotify for the basis of sycronous notification or blocking notification ordering becomes important. eg. A Hierarchical Storage Management listener would need to get its event before an AV scanner could get its event (since the HSM would need to bring the data in for the AV scanner to scan.) Typically asynchronous notification would want to run after the AV scanner made any relevant access decisions so as to not send notification about an event that was denied. Signed-off-by: Eric Paris <eparis@redhat.com>
269 lines
7.8 KiB
C
269 lines
7.8 KiB
C
/*
|
|
* Copyright (C) 2008 Red Hat, Inc., Eric Paris <eparis@redhat.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; see the file COPYING. If not, write to
|
|
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*/
|
|
|
|
#include <linux/list.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/srcu.h>
|
|
#include <linux/rculist.h>
|
|
#include <linux/wait.h>
|
|
|
|
#include <linux/fsnotify_backend.h>
|
|
#include "fsnotify.h"
|
|
|
|
#include <asm/atomic.h>
|
|
|
|
/* protects writes to fsnotify_groups and fsnotify_mask */
|
|
static DEFINE_MUTEX(fsnotify_grp_mutex);
|
|
/* protects reads while running the fsnotify_groups list */
|
|
struct srcu_struct fsnotify_grp_srcu;
|
|
/* all groups registered to receive inode filesystem notifications */
|
|
LIST_HEAD(fsnotify_inode_groups);
|
|
/* all groups registered to receive mount point filesystem notifications */
|
|
LIST_HEAD(fsnotify_vfsmount_groups);
|
|
/* bitwise OR of all events (FS_*) interesting to some group on this system */
|
|
__u32 fsnotify_inode_mask;
|
|
/* bitwise OR of all events (FS_*) interesting to some group on this system */
|
|
__u32 fsnotify_vfsmount_mask;
|
|
|
|
/*
|
|
* When a new group registers or changes it's set of interesting events
|
|
* this function updates the fsnotify_mask to contain all interesting events
|
|
*/
|
|
void fsnotify_recalc_global_mask(void)
|
|
{
|
|
struct fsnotify_group *group;
|
|
__u32 inode_mask = 0;
|
|
__u32 vfsmount_mask = 0;
|
|
int idx;
|
|
|
|
idx = srcu_read_lock(&fsnotify_grp_srcu);
|
|
list_for_each_entry_rcu(group, &fsnotify_inode_groups, inode_group_list)
|
|
inode_mask |= group->mask;
|
|
list_for_each_entry_rcu(group, &fsnotify_vfsmount_groups, vfsmount_group_list)
|
|
vfsmount_mask |= group->mask;
|
|
|
|
srcu_read_unlock(&fsnotify_grp_srcu, idx);
|
|
|
|
fsnotify_inode_mask = inode_mask;
|
|
fsnotify_vfsmount_mask = vfsmount_mask;
|
|
}
|
|
|
|
/*
|
|
* Update the group->mask by running all of the marks associated with this
|
|
* group and finding the bitwise | of all of the mark->mask. If we change
|
|
* the group->mask we need to update the global mask of events interesting
|
|
* to the system.
|
|
*/
|
|
void fsnotify_recalc_group_mask(struct fsnotify_group *group)
|
|
{
|
|
__u32 mask = 0;
|
|
__u32 old_mask = group->mask;
|
|
struct fsnotify_mark *mark;
|
|
|
|
spin_lock(&group->mark_lock);
|
|
list_for_each_entry(mark, &group->marks_list, g_list)
|
|
mask |= mark->mask;
|
|
spin_unlock(&group->mark_lock);
|
|
|
|
group->mask = mask;
|
|
|
|
if (old_mask != mask)
|
|
fsnotify_recalc_global_mask();
|
|
}
|
|
|
|
void fsnotify_add_vfsmount_group(struct fsnotify_group *group)
|
|
{
|
|
struct fsnotify_group *group_iter;
|
|
unsigned int priority = group->priority;
|
|
|
|
mutex_lock(&fsnotify_grp_mutex);
|
|
|
|
if (!group->on_vfsmount_group_list) {
|
|
list_for_each_entry(group_iter, &fsnotify_vfsmount_groups,
|
|
vfsmount_group_list) {
|
|
/* insert in front of this one? */
|
|
if (priority < group_iter->priority) {
|
|
/* list_add_tail() insert in front of group_iter */
|
|
list_add_tail_rcu(&group->inode_group_list,
|
|
&group_iter->inode_group_list);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
/* apparently we need to be the last entry */
|
|
list_add_tail_rcu(&group->vfsmount_group_list, &fsnotify_vfsmount_groups);
|
|
}
|
|
out:
|
|
group->on_vfsmount_group_list = 1;
|
|
|
|
mutex_unlock(&fsnotify_grp_mutex);
|
|
}
|
|
|
|
void fsnotify_add_inode_group(struct fsnotify_group *group)
|
|
{
|
|
struct fsnotify_group *group_iter;
|
|
unsigned int priority = group->priority;
|
|
|
|
mutex_lock(&fsnotify_grp_mutex);
|
|
|
|
/* add to global group list, priority 0 first, UINT_MAX last */
|
|
if (!group->on_inode_group_list) {
|
|
list_for_each_entry(group_iter, &fsnotify_inode_groups,
|
|
inode_group_list) {
|
|
if (priority < group_iter->priority) {
|
|
/* list_add_tail() insert in front of group_iter */
|
|
list_add_tail_rcu(&group->inode_group_list,
|
|
&group_iter->inode_group_list);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
/* apparently we need to be the last entry */
|
|
list_add_tail_rcu(&group->inode_group_list, &fsnotify_inode_groups);
|
|
}
|
|
out:
|
|
group->on_inode_group_list = 1;
|
|
|
|
mutex_unlock(&fsnotify_grp_mutex);
|
|
}
|
|
|
|
/*
|
|
* Final freeing of a group
|
|
*/
|
|
void fsnotify_final_destroy_group(struct fsnotify_group *group)
|
|
{
|
|
/* clear the notification queue of all events */
|
|
fsnotify_flush_notify(group);
|
|
|
|
if (group->ops->free_group_priv)
|
|
group->ops->free_group_priv(group);
|
|
|
|
kfree(group);
|
|
}
|
|
|
|
/*
|
|
* Trying to get rid of a group. We need to first get rid of any outstanding
|
|
* allocations and then free the group. Remember that fsnotify_clear_marks_by_group
|
|
* could miss marks that are being freed by inode and those marks could still
|
|
* hold a reference to this group (via group->num_marks) If we get into that
|
|
* situtation, the fsnotify_final_destroy_group will get called when that final
|
|
* mark is freed.
|
|
*/
|
|
static void fsnotify_destroy_group(struct fsnotify_group *group)
|
|
{
|
|
/* clear all inode marks for this group */
|
|
fsnotify_clear_marks_by_group(group);
|
|
|
|
/* past the point of no return, matches the initial value of 1 */
|
|
if (atomic_dec_and_test(&group->num_marks))
|
|
fsnotify_final_destroy_group(group);
|
|
}
|
|
|
|
/*
|
|
* Remove this group from the global list of groups that will get events
|
|
* this can be done even if there are still references and things still using
|
|
* this group. This just stops the group from getting new events.
|
|
*/
|
|
static void __fsnotify_evict_group(struct fsnotify_group *group)
|
|
{
|
|
BUG_ON(!mutex_is_locked(&fsnotify_grp_mutex));
|
|
|
|
if (group->on_inode_group_list)
|
|
list_del_rcu(&group->inode_group_list);
|
|
group->on_inode_group_list = 0;
|
|
if (group->on_vfsmount_group_list)
|
|
list_del_rcu(&group->vfsmount_group_list);
|
|
group->on_vfsmount_group_list = 0;
|
|
}
|
|
|
|
/*
|
|
* Called when a group is no longer interested in getting events. This can be
|
|
* used if a group is misbehaving or if for some reason a group should no longer
|
|
* get any filesystem events.
|
|
*/
|
|
void fsnotify_evict_group(struct fsnotify_group *group)
|
|
{
|
|
mutex_lock(&fsnotify_grp_mutex);
|
|
__fsnotify_evict_group(group);
|
|
mutex_unlock(&fsnotify_grp_mutex);
|
|
}
|
|
|
|
/*
|
|
* Drop a reference to a group. Free it if it's through.
|
|
*/
|
|
void fsnotify_put_group(struct fsnotify_group *group)
|
|
{
|
|
if (!atomic_dec_and_mutex_lock(&group->refcnt, &fsnotify_grp_mutex))
|
|
return;
|
|
|
|
/*
|
|
* OK, now we know that there's no other users *and* we hold mutex,
|
|
* so no new references will appear
|
|
*/
|
|
__fsnotify_evict_group(group);
|
|
|
|
/*
|
|
* now it's off the list, so the only thing we might care about is
|
|
* srcu access....
|
|
*/
|
|
mutex_unlock(&fsnotify_grp_mutex);
|
|
synchronize_srcu(&fsnotify_grp_srcu);
|
|
|
|
/* and now it is really dead. _Nothing_ could be seeing it */
|
|
fsnotify_recalc_global_mask();
|
|
fsnotify_destroy_group(group);
|
|
}
|
|
|
|
/*
|
|
* Create a new fsnotify_group and hold a reference for the group returned.
|
|
*/
|
|
struct fsnotify_group *fsnotify_alloc_group(const struct fsnotify_ops *ops)
|
|
{
|
|
struct fsnotify_group *group;
|
|
|
|
group = kzalloc(sizeof(struct fsnotify_group), GFP_KERNEL);
|
|
if (!group)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
/* set to 0 when there a no external references to this group */
|
|
atomic_set(&group->refcnt, 1);
|
|
/*
|
|
* hits 0 when there are no external references AND no marks for
|
|
* this group
|
|
*/
|
|
atomic_set(&group->num_marks, 1);
|
|
|
|
mutex_init(&group->notification_mutex);
|
|
INIT_LIST_HEAD(&group->notification_list);
|
|
init_waitqueue_head(&group->notification_waitq);
|
|
group->max_events = UINT_MAX;
|
|
|
|
INIT_LIST_HEAD(&group->inode_group_list);
|
|
INIT_LIST_HEAD(&group->vfsmount_group_list);
|
|
|
|
spin_lock_init(&group->mark_lock);
|
|
INIT_LIST_HEAD(&group->marks_list);
|
|
|
|
group->priority = UINT_MAX;
|
|
|
|
group->ops = ops;
|
|
|
|
return group;
|
|
}
|