summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEric Paris <eparis@redhat.com>2009-12-17 21:24:22 -0500
committerEric Paris <eparis@redhat.com>2010-07-28 09:58:49 -0400
commit1201a5361b9bd6512ae01e6f2b7aa79d458cafb1 (patch)
treebfbb10b38b6b32b773a43cc371b5d31a9ce3fe51
parentb4e4e1407312ae5a267ed7d716e6d4e7120a8430 (diff)
fsnotify: replace an event on a list
fanotify would like to clone events already on its notification list, make changes to the new event, and then replace the old event on the list with the new event. This patch implements the replace functionality of that process. Signed-off-by: Eric Paris <eparis@redhat.com>
-rw-r--r--fs/notify/notification.c56
-rw-r--r--include/linux/fsnotify_backend.h2
2 files changed, 58 insertions, 0 deletions
diff --git a/fs/notify/notification.c b/fs/notify/notification.c
index bc9470c7ece..b493c378445 100644
--- a/fs/notify/notification.c
+++ b/fs/notify/notification.c
@@ -287,6 +287,62 @@ static void initialize_event(struct fsnotify_event *event)
INIT_LIST_HEAD(&event->private_data_list);
}
+/*
+ * Caller damn well better be holding whatever mutex is protecting the
+ * old_holder->event_list.
+ */
+int fsnotify_replace_event(struct fsnotify_event_holder *old_holder,
+ struct fsnotify_event *new_event)
+{
+ struct fsnotify_event *old_event = old_holder->event;
+ struct fsnotify_event_holder *new_holder = NULL;
+
+ /*
+ * There is one fsnotify_event_holder embedded inside each fsnotify_event.
+ * Check if we expect to be able to use that holder. If not alloc a new
+ * holder.
+ * For the overflow event it's possible that something will use the in
+ * event holder before we get the lock so we may need to jump back and
+ * alloc a new holder, this can't happen for most events...
+ */
+ if (!list_empty(&new_event->holder.event_list)) {
+alloc_holder:
+ new_holder = fsnotify_alloc_event_holder();
+ if (!new_holder)
+ return -ENOMEM;
+ }
+
+ spin_lock(&old_event->lock);
+ spin_lock(&new_event->lock);
+
+ if (list_empty(&new_event->holder.event_list)) {
+ if (unlikely(new_holder))
+ fsnotify_destroy_event_holder(new_holder);
+ new_holder = &new_event->holder;
+ } else if (unlikely(!new_holder)) {
+ /* between the time we checked above and got the lock the in
+ * event holder was used, go back and get a new one */
+ spin_unlock(&new_event->lock);
+ spin_unlock(&old_event->lock);
+ goto alloc_holder;
+ }
+
+ new_holder->event = new_event;
+ list_replace_init(&old_holder->event_list, &new_holder->event_list);
+
+ spin_unlock(&new_event->lock);
+ spin_unlock(&old_event->lock);
+
+ /* event == holder means we are referenced through the in event holder */
+ if (old_holder != &old_event->holder)
+ fsnotify_destroy_event_holder(old_holder);
+
+ fsnotify_get_event(new_event); /* on the list take reference */
+ fsnotify_put_event(old_event); /* off the list, drop reference */
+
+ return 0;
+}
+
struct fsnotify_event *fsnotify_clone_event(struct fsnotify_event *old_event)
{
struct fsnotify_event *event;
diff --git a/include/linux/fsnotify_backend.h b/include/linux/fsnotify_backend.h
index 3a7fff23553..427f6ffab12 100644
--- a/include/linux/fsnotify_backend.h
+++ b/include/linux/fsnotify_backend.h
@@ -365,6 +365,8 @@ extern struct fsnotify_event *fsnotify_create_event(struct inode *to_tell, __u32
/* fanotify likes to change events after they are on lists... */
extern struct fsnotify_event *fsnotify_clone_event(struct fsnotify_event *old_event);
+extern int fsnotify_replace_event(struct fsnotify_event_holder *old_holder,
+ struct fsnotify_event *new_event);
#else