diff options
Diffstat (limited to 'fs/notify')
-rw-r--r-- | fs/notify/Makefile | 2 | ||||
-rw-r--r-- | fs/notify/fsnotify.h | 6 | ||||
-rw-r--r-- | fs/notify/mark.c | 20 | ||||
-rw-r--r-- | fs/notify/vfsmount_mark.c | 171 |
4 files changed, 189 insertions, 10 deletions
diff --git a/fs/notify/Makefile b/fs/notify/Makefile index 8f7f3b024a2e..ae5f33a6d868 100644 --- a/fs/notify/Makefile +++ b/fs/notify/Makefile @@ -1,5 +1,5 @@ obj-$(CONFIG_FSNOTIFY) += fsnotify.o notification.o group.o inode_mark.o \ - mark.o + mark.o vfsmount_mark.o obj-y += dnotify/ obj-y += inotify/ diff --git a/fs/notify/fsnotify.h b/fs/notify/fsnotify.h index 7c7a904b802d..38f3fb5cef28 100644 --- a/fs/notify/fsnotify.h +++ b/fs/notify/fsnotify.h @@ -24,6 +24,10 @@ extern void fsnotify_flush_notify(struct fsnotify_group *group); extern int fsnotify_add_inode_mark(struct fsnotify_mark *mark, struct fsnotify_group *group, struct inode *inode, int allow_dups); +/* add a mark to a vfsmount */ +extern int fsnotify_add_vfsmount_mark(struct fsnotify_mark *mark, + struct fsnotify_group *group, struct vfsmount *mnt, + int allow_dups); /* add a group to the inode group list */ extern void fsnotify_add_inode_group(struct fsnotify_group *group); @@ -32,6 +36,8 @@ extern void fsnotify_add_vfsmount_group(struct fsnotify_group *group); /* final kfree of a group */ extern void fsnotify_final_destroy_group(struct fsnotify_group *group); +/* vfsmount specific destruction of a mark */ +extern void fsnotify_destroy_vfsmount_mark(struct fsnotify_mark *mark); /* inode specific destruction of a mark */ extern void fsnotify_destroy_inode_mark(struct fsnotify_mark *mark); /* run the list of all marks associated with inode and flag them to be freed */ diff --git a/fs/notify/mark.c b/fs/notify/mark.c index 57bb1d74a2b6..d296ec9ffb2a 100644 --- a/fs/notify/mark.c +++ b/fs/notify/mark.c @@ -115,15 +115,11 @@ void fsnotify_put_mark(struct fsnotify_mark *mark) void fsnotify_destroy_mark(struct fsnotify_mark *mark) { struct fsnotify_group *group; - struct inode *inode; + struct inode *inode = NULL; spin_lock(&mark->lock); group = mark->group; - inode = mark->i.inode; - - BUG_ON(group && !inode); - BUG_ON(!group && inode); /* if !group something else already marked this to die */ if (!group) { @@ -136,8 +132,11 @@ void fsnotify_destroy_mark(struct fsnotify_mark *mark) spin_lock(&group->mark_lock); - if (mark->flags & FSNOTIFY_MARK_FLAG_INODE) + if (mark->flags & FSNOTIFY_MARK_FLAG_INODE) { fsnotify_destroy_inode_mark(mark); + inode = mark->i.inode; + } else if (mark->flags & FSNOTIFY_MARK_FLAG_VFSMOUNT) + fsnotify_destroy_vfsmount_mark(mark); else BUG(); @@ -169,8 +168,8 @@ void fsnotify_destroy_mark(struct fsnotify_mark *mark) * is just a lazy update (and could be a perf win...) */ - - iput(inode); + if (inode) + iput(inode); /* * it's possible that this group tried to destroy itself, but this @@ -192,7 +191,6 @@ int fsnotify_add_mark(struct fsnotify_mark *mark, { int ret = 0; - BUG_ON(mnt); BUG_ON(inode && mnt); BUG_ON(!inode && !mnt); @@ -223,6 +221,10 @@ int fsnotify_add_mark(struct fsnotify_mark *mark, ret = fsnotify_add_inode_mark(mark, group, inode, allow_dups); if (ret) goto err; + } else if (mnt) { + ret = fsnotify_add_vfsmount_mark(mark, group, mnt, allow_dups); + if (ret) + goto err; } else { BUG(); } diff --git a/fs/notify/vfsmount_mark.c b/fs/notify/vfsmount_mark.c new file mode 100644 index 000000000000..1b61d0a942de --- /dev/null +++ b/fs/notify/vfsmount_mark.c @@ -0,0 +1,171 @@ +/* + * 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/fs.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mount.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/writeback.h> /* for inode_lock */ + +#include <asm/atomic.h> + +#include <linux/fsnotify_backend.h> +#include "fsnotify.h" + +void fsnotify_clear_marks_by_mount(struct vfsmount *mnt) +{ + struct fsnotify_mark *mark, *lmark; + struct hlist_node *pos, *n; + LIST_HEAD(free_list); + + spin_lock(&mnt->mnt_root->d_lock); + hlist_for_each_entry_safe(mark, pos, n, &mnt->mnt_fsnotify_marks, m.m_list) { + list_add(&mark->m.free_m_list, &free_list); + hlist_del_init(&mark->m.m_list); + fsnotify_get_mark(mark); + } + spin_unlock(&mnt->mnt_root->d_lock); + + list_for_each_entry_safe(mark, lmark, &free_list, m.free_m_list) { + fsnotify_destroy_mark(mark); + fsnotify_put_mark(mark); + } +} + +/* + * Recalculate the mask of events relevant to a given vfsmount locked. + */ +static void fsnotify_recalc_vfsmount_mask_locked(struct vfsmount *mnt) +{ + struct fsnotify_mark *mark; + struct hlist_node *pos; + __u32 new_mask = 0; + + assert_spin_locked(&mnt->mnt_root->d_lock); + + hlist_for_each_entry(mark, pos, &mnt->mnt_fsnotify_marks, m.m_list) + new_mask |= mark->mask; + mnt->mnt_fsnotify_mask = new_mask; +} + +/* + * Recalculate the mnt->mnt_fsnotify_mask, or the mask of all FS_* event types + * any notifier is interested in hearing for this mount point + */ +void fsnotify_recalc_vfsmount_mask(struct vfsmount *mnt) +{ + spin_lock(&mnt->mnt_root->d_lock); + fsnotify_recalc_vfsmount_mask_locked(mnt); + spin_unlock(&mnt->mnt_root->d_lock); +} + +void fsnotify_destroy_vfsmount_mark(struct fsnotify_mark *mark) +{ + struct vfsmount *mnt = mark->m.mnt; + + assert_spin_locked(&mark->lock); + assert_spin_locked(&mark->group->mark_lock); + + spin_lock(&mnt->mnt_root->d_lock); + + hlist_del_init(&mark->m.m_list); + mark->m.mnt = NULL; + + fsnotify_recalc_vfsmount_mask_locked(mnt); + + spin_unlock(&mnt->mnt_root->d_lock); +} + +static struct fsnotify_mark *fsnotify_find_vfsmount_mark_locked(struct fsnotify_group *group, + struct vfsmount *mnt) +{ + struct fsnotify_mark *mark; + struct hlist_node *pos; + + assert_spin_locked(&mnt->mnt_root->d_lock); + + hlist_for_each_entry(mark, pos, &mnt->mnt_fsnotify_marks, m.m_list) { + if (mark->group == group) { + fsnotify_get_mark(mark); + return mark; + } + } + return NULL; +} + +/* + * given a group and vfsmount, find the mark associated with that combination. + * if found take a reference to that mark and return it, else return NULL + */ +struct fsnotify_mark *fsnotify_find_vfsmount_mark(struct fsnotify_group *group, + struct vfsmount *mnt) +{ + struct fsnotify_mark *mark; + + spin_lock(&mnt->mnt_root->d_lock); + mark = fsnotify_find_vfsmount_mark_locked(group, mnt); + spin_unlock(&mnt->mnt_root->d_lock); + + return mark; +} + +/* + * Attach an initialized mark to a given group and vfsmount. + * These marks may be used for the fsnotify backend to determine which + * event types should be delivered to which groups. + */ +int fsnotify_add_vfsmount_mark(struct fsnotify_mark *mark, + struct fsnotify_group *group, struct vfsmount *mnt, + int allow_dups) +{ + struct fsnotify_mark *lmark = NULL; + int ret = 0; + + mark->flags = FSNOTIFY_MARK_FLAG_VFSMOUNT; + + /* + * LOCKING ORDER!!!! + * mark->lock + * group->mark_lock + * mnt->mnt_root->d_lock + */ + assert_spin_locked(&mark->lock); + assert_spin_locked(&group->mark_lock); + + spin_lock(&mnt->mnt_root->d_lock); + + if (!allow_dups) + lmark = fsnotify_find_vfsmount_mark_locked(group, mnt); + if (!lmark) { + mark->m.mnt = mnt; + + hlist_add_head(&mark->m.m_list, &mnt->mnt_fsnotify_marks); + + fsnotify_recalc_vfsmount_mask_locked(mnt); + } else { + ret = -EEXIST; + } + + spin_unlock(&mnt->mnt_root->d_lock); + + return ret; +} |