[PATCH 4/4] fanotify: Expose the file changes to the user

From: Alexey Zaytsev
Date: Sun Nov 21 2010 - 19:35:57 EST


And revamp the event passing interface in the process.

Signed-off-by: Alexey Zaytsev <alexey.zaytsev@xxxxxxxxx>
---
fs/notify/fanotify/fanotify.c | 19 +++++
fs/notify/fanotify/fanotify_user.c | 132 +++++++++++++++++++++++++++++++-----
include/linux/fanotify.h | 85 ++++++++++++++++++++---
3 files changed, 206 insertions(+), 30 deletions(-)

diff --git a/fs/notify/fanotify/fanotify.c b/fs/notify/fanotify/fanotify.c
index b04f88e..1dced4f 100644
--- a/fs/notify/fanotify/fanotify.c
+++ b/fs/notify/fanotify/fanotify.c
@@ -53,9 +53,15 @@ static struct fsnotify_event *fanotify_merge(struct list_head *list,

fsnotify_get_event(test_event);

- /* if they are exactly the same we are done */
- if (test_event->mask == event->mask)
+
+ /*
+ * Event masks are the same, and the event does not
+ * carry any optional data
+ */
+ if (test_event->mask == event->mask &&
+ !(event->mask & (FS_MODIFY | FS_CLOSE_WRITE))) {
return test_event;
+ }

/*
* if the refcnt == 2 this is the only queue
@@ -64,6 +70,13 @@ static struct fsnotify_event *fanotify_merge(struct list_head *list,
*/
if (atomic_read(&test_event->refcnt) == 2) {
test_event->mask |= event->mask;
+ /*
+ * Update the event's ranges, works even if
+ * the event does not carry any ranges, so don't
+ * bother checking.
+ */
+ fsnotify_update_range(&test_event->mod_range, &event->mod_range);
+ fsnotify_update_range(&test_event->cw_range, &event->cw_range);
return test_event;
}

@@ -78,6 +91,8 @@ static struct fsnotify_event *fanotify_merge(struct list_head *list,

/* build new event and replace it on the list */
new_event->mask = (test_event->mask | event->mask);
+ fsnotify_update_range(&new_event->mod_range, &event->mod_range);
+ fsnotify_update_range(&new_event->cw_range, &event->cw_range);
fsnotify_replace_event(test_holder, new_event);

/* we hold a reference on new_event from clone_event */
diff --git a/fs/notify/fanotify/fanotify_user.c b/fs/notify/fanotify/fanotify_user.c
index 0632248..5774552 100644
--- a/fs/notify/fanotify/fanotify_user.c
+++ b/fs/notify/fanotify/fanotify_user.c
@@ -31,6 +31,57 @@ struct fanotify_response_event {
struct fsnotify_event *event;
};

+#ifdef DEBUG
+static inline void dump_event(const char *str, void *buf)
+{
+ struct fanotify_event_metadata *meta = buf;
+ struct fanotify_opt_hdr *tmp;
+
+ pr_debug("%s: event_len=%d vers=%d mask=0x%lld fd=%d pid=%d\n", str,
+ meta->event_len, meta->vers, meta->mask, meta->fd, meta->pid);
+
+ FAN_FOR_EACH_OPTION(meta, tmp) {
+ /* Hope gcc does a good job here. */
+ struct fan_modify_option *m_opt = FAN_OPT_PAYLOAD(tmp);
+ struct fan_close_write_option *cw_opt = FAN_OPT_PAYLOAD(tmp);
+ switch(tmp->type) {
+
+ case FAN_OPT_MODIFY:
+ pr_debug("\tOption: type = %d, len = %d, range {%lld, %lld}\n",
+ tmp->type, tmp->len, m_opt->start, m_opt->end);
+ break;
+
+ case FAN_OPT_CLOSE_WRITE:
+ pr_debug("\tOption: type = %d, len = %d, range {%lld, %lld}\n",
+ tmp->type, tmp->len, cw_opt->start, cw_opt->end);
+ break;
+
+ default:
+ /* Don't forget to add new options here. */
+ WARN_ON_ONCE(1);
+ }
+ }
+}
+#else
+static inline void dump_event(const char *str, void *buf)
+{
+}
+#endif
+
+static inline int fanotify_event_len(struct fsnotify_event *event)
+{
+ int len = sizeof(struct fanotify_event_metadata);
+ u32 mask = event->mask & FAN_ALL_OUTGOING_EVENTS;
+
+ if (mask & FAN_MODIFY)
+ len += (sizeof(struct fanotify_opt_hdr)
+ + sizeof(struct fan_modify_option));
+ if (mask & FAN_CLOSE_WRITE)
+ len += (sizeof(struct fanotify_opt_hdr)
+ + sizeof(struct fan_close_write_option));
+ return len;
+}
+
/*
* Get an fsnotify notification event if one exists and is small
* enough to fit in "count". Return an error pointer if the count
@@ -41,6 +92,7 @@ struct fanotify_response_event {
static struct fsnotify_event *get_one_event(struct fsnotify_group *group,
size_t count)
{
+ struct fsnotify_event *event;
BUG_ON(!mutex_is_locked(&group->notification_mutex));

pr_debug("%s: group=%p count=%zd\n", __func__, group, count);
@@ -48,7 +100,8 @@ static struct fsnotify_event *get_one_event(struct fsnotify_group *group,
if (fsnotify_notify_queue_is_empty(group))
return NULL;

- if (FAN_EVENT_METADATA_LEN > count)
+ event = fsnotify_peek_notify_event(group);
+ if (fanotify_event_len(event) > count)
return ERR_PTR(-EINVAL);

/* held the notification_mutex the whole time, so this is the
@@ -106,20 +159,58 @@ static int create_fd(struct fsnotify_group *group, struct fsnotify_event *event)
return client_fd;
}

-static ssize_t fill_event_metadata(struct fsnotify_group *group,
- struct fanotify_event_metadata *metadata,
+static int fill_event_metadata(struct fsnotify_group *group,
+ char *event_buffer, int len,
struct fsnotify_event *event)
{
- pr_debug("%s: group=%p metadata=%p event=%p\n", __func__,
- group, metadata, event);
+ struct fanotify_event_metadata *meta =
+ (struct fanotify_event_metadata *) event_buffer;
+
+ struct fanotify_opt_hdr *opt_hdr = (struct fanotify_opt_hdr *)(meta + 1);
+ int meta_len = sizeof(struct fanotify_event_metadata);
+
+ pr_debug("%s: group=%p meta=%p event=%p\n", __func__,
+ group, meta, event);
+
+ if (event->mask & FS_MODIFY) {
+ struct fan_modify_option *opt =
+ (struct fan_modify_option *) (opt_hdr + 1);
+ int opt_len = sizeof(struct fanotify_opt_hdr) +
+ sizeof(struct fan_modify_option);

- metadata->event_len = FAN_EVENT_METADATA_LEN;
- metadata->vers = FANOTIFY_METADATA_VERSION;
- metadata->mask = event->mask & FAN_ALL_OUTGOING_EVENTS;
- metadata->pid = pid_vnr(event->tgid);
- metadata->fd = create_fd(group, event);
+ meta_len += opt_len;
+ opt_hdr->type = FAN_OPT_MODIFY;
+ opt_hdr->len = opt_len;
+ opt->start = event->mod_range.start;
+ opt->end = event->mod_range.end;

- return metadata->fd;
+ opt_hdr = (struct fanotify_opt_hdr *) (((char *) opt_hdr) + opt_len);
+ }
+ if (event->mask & FS_CLOSE_WRITE) {
+ struct fan_close_write_option *opt =
+ (struct fan_close_write_option *) (opt_hdr + 1);
+ int opt_len = sizeof(struct fanotify_opt_hdr) +
+ sizeof(struct fan_close_write_option);
+
+ meta_len += opt_len;
+ opt_hdr->type = FAN_OPT_CLOSE_WRITE;
+ opt_hdr->len = opt_len;
+ opt->start = event->cw_range.start;
+ opt->end = event->cw_range.end;
+
+ opt_hdr = (struct fanotify_opt_hdr *) (((char *) opt_hdr) + opt_len);
+ }
+
+ WARN_ON_ONCE(len != meta_len);
+
+ meta->event_len = meta_len;
+ meta->vers = FANOTIFY_METADATA_VERSION;
+ meta->options_offset = sizeof(struct fanotify_event_metadata);
+ meta->mask = event->mask & FAN_ALL_OUTGOING_EVENTS;
+ meta->pid = pid_vnr(event->tgid);
+ meta->fd = create_fd(group, event);
+
+ return meta->fd;
}

#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
@@ -248,16 +339,17 @@ static void remove_access_response(struct fsnotify_group *group,
}
#endif

-static ssize_t copy_event_to_user(struct fsnotify_group *group,
+static int copy_event_to_user(struct fsnotify_group *group,
struct fsnotify_event *event,
char __user *buf)
{
- struct fanotify_event_metadata fanotify_event_metadata;
+ int len = fanotify_event_len(event);
+ char fanotify_event_buf[len];
int fd, ret;

pr_debug("%s: group=%p event=%p\n", __func__, group, event);

- fd = fill_event_metadata(group, &fanotify_event_metadata, event);
+ fd = fill_event_metadata(group, fanotify_event_buf, len, event);
if (fd < 0)
return fd;

@@ -266,10 +358,12 @@ static ssize_t copy_event_to_user(struct fsnotify_group *group,
goto out_close_fd;

ret = -EFAULT;
- if (copy_to_user(buf, &fanotify_event_metadata, FAN_EVENT_METADATA_LEN))
+ if (copy_to_user(buf, fanotify_event_buf, len))
goto out_kill_access_response;

- return FAN_EVENT_METADATA_LEN;
+ dump_event(__func__, fanotify_event_buf);
+
+ return len;

out_kill_access_response:
remove_access_response(group, event, fd);
@@ -418,8 +512,10 @@ static long fanotify_ioctl(struct file *file, unsigned int cmd, unsigned long ar
switch (cmd) {
case FIONREAD:
mutex_lock(&group->notification_mutex);
- list_for_each_entry(holder, &group->notification_list, event_list)
- send_len += FAN_EVENT_METADATA_LEN;
+ list_for_each_entry(holder, &group->notification_list, event_list) {
+ struct fsnotify_event *event = holder->event;
+ send_len += fanotify_event_len(event);
+ }
mutex_unlock(&group->notification_mutex);
ret = put_user(send_len, (int __user *) p);
break;
diff --git a/include/linux/fanotify.h b/include/linux/fanotify.h
index 9a7986f..87dfe36 100644
--- a/include/linux/fanotify.h
+++ b/include/linux/fanotify.h
@@ -83,17 +83,50 @@
FAN_ALL_PERM_EVENTS |\
FAN_Q_OVERFLOW)

-#define FANOTIFY_METADATA_VERSION 2
+/* Option types, mirror the event mask, but we can pack them into u8. */
+enum fanotify_opt_type {
+ FAN_OPT_NONE = 0,
+ FAN_OPT_ACCESS,
+ FAN_OPT_MODIFY,
+ FAN_OPT_CLOSE_WRITE,
+ FAN_OPT_CLOSE_NOWRITE,
+ FAN_OPT_OPEN,
+ FAN_OPT_Q_OVERFLOW,
+ FAN_OPT_OPEN_PERM,
+ FAN_OPT_ACCESS_PERM,
+};
+
+struct fanotify_opt_hdr {
+ __u8 type;
+ __u8 reserved;
+ __u16 len;
+ /* Payload goes here. */
+};
+
+#define FANOTIFY_METADATA_VERSION 3

struct fanotify_event_metadata {
- __u16 event_len;
+ __u16 event_len; /* Including the options */
__u8 vers;
- __u8 reserved;
+ __u8 options_offset; /* Aka header length */
__s32 fd;
__aligned_u64 mask;
__s32 pid;
+ /* Options go here. */
};

+struct fan_modify_option {
+ __aligned_u64 start; /* Start of the modification. */
+ __aligned_u64 end; /* The position right after the modification. */
+};
+
+struct fan_close_write_option {
+ __aligned_u64 start; /* Start of the modifications. */
+ __aligned_u64 end; /* The position right after the modifications. */
+};
+
+/* Update fanotify_event_len() when you add more options. */
+
struct fanotify_response {
__s32 fd;
__u32 response;
@@ -103,15 +136,47 @@ struct fanotify_response {
#define FAN_ALLOW 0x01
#define FAN_DENY 0x02

-/* Helper functions to deal with fanotify_event_metadata buffers */
-#define FAN_EVENT_METADATA_LEN (sizeof(struct fanotify_event_metadata))
-
#define FAN_EVENT_NEXT(meta, len) ((len) -= (meta)->event_len, \
(struct fanotify_event_metadata*)(((char *)(meta)) + \
(meta)->event_len))

-#define FAN_EVENT_OK(meta, len) ((long)(len) >= (long)FAN_EVENT_METADATA_LEN && \
- (long)(meta)->event_len >= (long)FAN_EVENT_METADATA_LEN && \
- (long)(meta)->event_len <= (long)(len))
-
+static inline int FAN_EVENT_OK(struct fanotify_event_metadata *meta, size_t len)
+{
+ return (len >= sizeof(struct fanotify_event_metadata) &&
+ meta->event_len >= sizeof(struct fanotify_event_metadata) &&
+ meta->event_len <= len);
+}
+
+#define FAN_FOR_EACH_EVENT(meta, buf, len) \
+ for (meta = (struct fanotify_event_metadata *) buf; \
+ FAN_EVENT_OK(meta, len); \
+ meta = FAN_EVENT_NEXT(meta, len))
+
+
+static inline struct fanotify_opt_hdr *FAN_OPTION_FIRST(
+ struct fanotify_event_metadata *meta)
+{
+ return (struct fanotify_opt_hdr *) (((char *) meta) + meta->options_offset);
+}
+static inline struct fanotify_opt_hdr *FAN_OPTION_NEXT(
+ struct fanotify_opt_hdr *opt)
+{
+ return (struct fanotify_opt_hdr *) (((char *) opt) + opt->len);
+}
+
+static inline int FAN_OPTION_OK(struct fanotify_event_metadata *meta,
+ struct fanotify_opt_hdr *opt)
+{
+ return ((char *) opt) < (((char *)meta) + meta->event_len);
+}
+
+#define FAN_FOR_EACH_OPTION(meta, tmp) \
+ for (tmp = FAN_OPTION_FIRST(meta); \
+ FAN_OPTION_OK(meta, tmp); \
+ tmp = FAN_OPTION_NEXT(tmp))
+
+static inline void *FAN_OPT_PAYLOAD(struct fanotify_opt_hdr *opt)
+{
+ return (opt + 1);
+}
#endif /* _LINUX_FANOTIFY_H */

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/