diff options
Diffstat (limited to 'fs/notify/inotify/inotify_fsnotify.c')
| -rw-r--r-- | fs/notify/inotify/inotify_fsnotify.c | 170 |
1 files changed, 92 insertions, 78 deletions
diff --git a/fs/notify/inotify/inotify_fsnotify.c b/fs/notify/inotify/inotify_fsnotify.c index 1afb0a10229..43ab1e1a07a 100644 --- a/fs/notify/inotify/inotify_fsnotify.c +++ b/fs/notify/inotify/inotify_fsnotify.c @@ -22,87 +22,107 @@ * General Public License for more details. */ +#include <linux/dcache.h> /* d_unlinked */ #include <linux/fs.h> /* struct inode */ #include <linux/fsnotify_backend.h> #include <linux/inotify.h> #include <linux/path.h> /* struct path */ #include <linux/slab.h> /* kmem_* */ #include <linux/types.h> +#include <linux/sched.h> #include "inotify.h" -static int inotify_handle_event(struct fsnotify_group *group, struct fsnotify_event *event) +/* + * Check if 2 events contain the same information. + */ +static bool event_compare(struct fsnotify_event *old_fsn, + struct fsnotify_event *new_fsn) { - struct fsnotify_mark_entry *entry; - struct inotify_inode_mark_entry *ientry; - struct inode *to_tell; - struct inotify_event_private_data *event_priv; - struct fsnotify_event_private_data *fsn_event_priv; - int wd, ret; - - to_tell = event->to_tell; - - spin_lock(&to_tell->i_lock); - entry = fsnotify_find_mark_entry(group, to_tell); - spin_unlock(&to_tell->i_lock); - /* race with watch removal? We already passes should_send */ - if (unlikely(!entry)) - return 0; - ientry = container_of(entry, struct inotify_inode_mark_entry, - fsn_entry); - wd = ientry->wd; + struct inotify_event_info *old, *new; - event_priv = kmem_cache_alloc(event_priv_cachep, GFP_KERNEL); - if (unlikely(!event_priv)) - return -ENOMEM; + if (old_fsn->mask & FS_IN_IGNORED) + return false; + old = INOTIFY_E(old_fsn); + new = INOTIFY_E(new_fsn); + if ((old_fsn->mask == new_fsn->mask) && + (old_fsn->inode == new_fsn->inode) && + (old->name_len == new->name_len) && + (!old->name_len || !strcmp(old->name, new->name))) + return true; + return false; +} - fsn_event_priv = &event_priv->fsnotify_event_priv_data; +static int inotify_merge(struct list_head *list, + struct fsnotify_event *event) +{ + struct fsnotify_event *last_event; - fsn_event_priv->group = group; - event_priv->wd = wd; + last_event = list_entry(list->prev, struct fsnotify_event, list); + return event_compare(last_event, event); +} - ret = fsnotify_add_notify_event(group, event, fsn_event_priv); - if (ret) { - inotify_free_event_priv(fsn_event_priv); - /* EEXIST says we tail matched, EOVERFLOW isn't something - * to report up the stack. */ - if ((ret == -EEXIST) || - (ret == -EOVERFLOW)) - ret = 0; +int inotify_handle_event(struct fsnotify_group *group, + struct inode *inode, + struct fsnotify_mark *inode_mark, + struct fsnotify_mark *vfsmount_mark, + u32 mask, void *data, int data_type, + const unsigned char *file_name, u32 cookie) +{ + struct inotify_inode_mark *i_mark; + struct inotify_event_info *event; + struct fsnotify_event *fsn_event; + int ret; + int len = 0; + int alloc_len = sizeof(struct inotify_event_info); + + BUG_ON(vfsmount_mark); + + if ((inode_mark->mask & FS_EXCL_UNLINK) && + (data_type == FSNOTIFY_EVENT_PATH)) { + struct path *path = data; + + if (d_unlinked(path->dentry)) + return 0; + } + if (file_name) { + len = strlen(file_name); + alloc_len += len + 1; } - /* - * If we hold the entry until after the event is on the queue - * IN_IGNORED won't be able to pass this event in the queue - */ - fsnotify_put_mark(entry); + pr_debug("%s: group=%p inode=%p mask=%x\n", __func__, group, inode, + mask); - return ret; -} + i_mark = container_of(inode_mark, struct inotify_inode_mark, + fsn_mark); -static void inotify_freeing_mark(struct fsnotify_mark_entry *entry, struct fsnotify_group *group) -{ - inotify_ignored_and_remove_idr(entry, group); -} + event = kmalloc(alloc_len, GFP_KERNEL); + if (unlikely(!event)) + return -ENOMEM; -static bool inotify_should_send_event(struct fsnotify_group *group, struct inode *inode, __u32 mask) -{ - struct fsnotify_mark_entry *entry; - bool send; + fsn_event = &event->fse; + fsnotify_init_event(fsn_event, inode, mask); + event->wd = i_mark->wd; + event->sync_cookie = cookie; + event->name_len = len; + if (len) + strcpy(event->name, file_name); - spin_lock(&inode->i_lock); - entry = fsnotify_find_mark_entry(group, inode); - spin_unlock(&inode->i_lock); - if (!entry) - return false; + ret = fsnotify_add_notify_event(group, fsn_event, inotify_merge); + if (ret) { + /* Our event wasn't used in the end. Free it. */ + fsnotify_destroy_event(group, fsn_event); + } - mask = (mask & ~FS_EVENT_ON_CHILD); - send = (entry->mask & mask); + if (inode_mark->mask & IN_ONESHOT) + fsnotify_destroy_mark(inode_mark, group); - /* find took a reference */ - fsnotify_put_mark(entry); + return 0; +} - return send; +static void inotify_freeing_mark(struct fsnotify_mark *fsn_mark, struct fsnotify_group *group) +{ + inotify_ignored_and_remove_idr(fsn_mark, group); } /* @@ -114,18 +134,18 @@ static bool inotify_should_send_event(struct fsnotify_group *group, struct inode */ static int idr_callback(int id, void *p, void *data) { - struct fsnotify_mark_entry *entry; - struct inotify_inode_mark_entry *ientry; + struct fsnotify_mark *fsn_mark; + struct inotify_inode_mark *i_mark; static bool warned = false; if (warned) return 0; warned = true; - entry = p; - ientry = container_of(entry, struct inotify_inode_mark_entry, fsn_entry); + fsn_mark = p; + i_mark = container_of(fsn_mark, struct inotify_inode_mark, fsn_mark); - WARN(1, "inotify closing but id=%d for entry=%p in group=%p still in " + WARN(1, "inotify closing but id=%d for fsn_mark=%p in group=%p still in " "idr. Probably leaking memory\n", id, p, data); /* @@ -134,35 +154,29 @@ static int idr_callback(int id, void *p, void *data) * out why we got here and the panic is no worse than the original * BUG() that was here. */ - if (entry) - printk(KERN_WARNING "entry->group=%p inode=%p wd=%d\n", - entry->group, entry->inode, ientry->wd); + if (fsn_mark) + printk(KERN_WARNING "fsn_mark->group=%p inode=%p wd=%d\n", + fsn_mark->group, fsn_mark->i.inode, i_mark->wd); return 0; } static void inotify_free_group_priv(struct fsnotify_group *group) { - /* ideally the idr is empty and we won't hit the BUG in teh callback */ + /* ideally the idr is empty and we won't hit the BUG in the callback */ idr_for_each(&group->inotify_data.idr, idr_callback, group); - idr_remove_all(&group->inotify_data.idr); idr_destroy(&group->inotify_data.idr); + atomic_dec(&group->inotify_data.user->inotify_devs); + free_uid(group->inotify_data.user); } -void inotify_free_event_priv(struct fsnotify_event_private_data *fsn_event_priv) +static void inotify_free_event(struct fsnotify_event *fsn_event) { - struct inotify_event_private_data *event_priv; - - - event_priv = container_of(fsn_event_priv, struct inotify_event_private_data, - fsnotify_event_priv_data); - - kmem_cache_free(event_priv_cachep, event_priv); + kfree(INOTIFY_E(fsn_event)); } const struct fsnotify_ops inotify_fsnotify_ops = { .handle_event = inotify_handle_event, - .should_send_event = inotify_should_send_event, .free_group_priv = inotify_free_group_priv, - .free_event_priv = inotify_free_event_priv, + .free_event = inotify_free_event, .freeing_mark = inotify_freeing_mark, }; |
