[RFC,7/7] fanotify: pass filename info for filename events
diff mbox

Message ID 1476126784-12520-8-git-send-email-amir73il@gmail.com
State New
Headers show

Commit Message

Amir Goldstein Oct. 10, 2016, 7:13 p.m. UTC
On filename events, allocate a variable length
fanotify_file_event_info struct and store the filename,
along with the parent directory path struct.

When filename exists, The fanotify_event_metadata.event_len
field includes the size of the metadata header and the size
of the padded filename.

User programs that use the fanotify macros FAN_EVENT_NEXT()
and FAN_EVENT_OK() will not experience any breakage and new
user programs can read the filename as a null terminated string
from the event buffer at offset FAN_EVENT_METADATA_LEN.

Nevertheless, programs that want to get the filename info
are required to explicitly request it via the FAN_EVENT_INFO_NAME
flag to fanotify_init(). This flag is only allowed to be set
along with the default FAN_CLASS_NOTIF notification class.

Signed-off-by: Amir Goldstein <amir73il@gmail.com>
---
 fs/notify/fanotify/fanotify.c      | 41 +++++++++++++++++++++++++++++++--
 fs/notify/fanotify/fanotify.h      | 24 +++++++++++++++++++-
 fs/notify/fanotify/fanotify_user.c | 46 ++++++++++++++++++++++++++++++++++++--
 include/uapi/linux/fanotify.h      |  8 ++++++-
 4 files changed, 113 insertions(+), 6 deletions(-)

Patch
diff mbox

diff --git a/fs/notify/fanotify/fanotify.c b/fs/notify/fanotify/fanotify.c
index 378101c..469c0d4 100644
--- a/fs/notify/fanotify/fanotify.c
+++ b/fs/notify/fanotify/fanotify.c
@@ -45,6 +45,12 @@  static int fanotify_merge(struct list_head *list, struct fsnotify_event *event)
 		return 0;
 #endif
 
+	/*
+	 * Don't merge a filename event with any other event
+	 */
+	if (event->mask & FAN_FILENAME_EVENTS)
+		return 0;
+
 	list_for_each_entry_reverse(test_event, list, list) {
 		if (should_merge(test_event, event)) {
 			do_merge = true;
@@ -161,7 +167,8 @@  static bool fanotify_should_send_event(struct fsnotify_mark *inode_mark,
 }
 
 struct fanotify_event_info *fanotify_alloc_event(struct inode *inode, u32 mask,
-						 struct path *path)
+						 struct path *path,
+						 const char *file_name)
 {
 	struct fanotify_event_info *event;
 
@@ -178,6 +185,31 @@  struct fanotify_event_info *fanotify_alloc_event(struct inode *inode, u32 mask,
 		goto init;
 	}
 #endif
+
+	/*
+	 * For filename events (create,delete,rename), path points to the
+	 * directory and name holds the entry name, so allocate a variable
+	 * length fanotify_file_event_info struct.
+	 */
+	if (mask & FAN_FILENAME_EVENTS) {
+		struct fanotify_file_event_info *ffe;
+		int alloc_len = sizeof(*ffe);
+		int len = 0;
+
+		if ((mask & FAN_FILENAME_EVENTS) && file_name) {
+			len = strlen(file_name);
+			alloc_len += len + 1;
+		}
+		ffe = kmalloc(alloc_len, GFP_KERNEL);
+		if (!ffe)
+			return NULL;
+		event = &ffe->fae;
+		ffe->name_len = len;
+		if (len)
+			strcpy(ffe->name, file_name);
+		goto init;
+	}
+
 	event = kmem_cache_alloc(fanotify_event_cachep, GFP_KERNEL);
 	if (!event)
 		return NULL;
@@ -245,7 +277,7 @@  static int fanotify_handle_event(struct fsnotify_group *group,
 	pr_debug("%s: group=%p inode=%p mask=%x\n", __func__, group, inode,
 		 mask);
 
-	event = fanotify_alloc_event(inode, mask, &path);
+	event = fanotify_alloc_event(inode, mask, &path, file_name);
 	if (unlikely(!event))
 		return -ENOMEM;
 
@@ -292,6 +324,11 @@  static void fanotify_free_event(struct fsnotify_event *fsn_event)
 		return;
 	}
 #endif
+	if (fsn_event->mask & FAN_FILENAME_EVENTS) {
+		kfree(FANOTIFY_FE(fsn_event));
+		return;
+	}
+
 	kmem_cache_free(fanotify_event_cachep, event);
 }
 
diff --git a/fs/notify/fanotify/fanotify.h b/fs/notify/fanotify/fanotify.h
index 2a5fb14..1cbf409 100644
--- a/fs/notify/fanotify/fanotify.h
+++ b/fs/notify/fanotify/fanotify.h
@@ -20,6 +20,21 @@  struct fanotify_event_info {
 	struct pid *tgid;
 };
 
+/*
+ * Structure for fanotify events with variable length data.
+ * It gets allocated in fanotify_handle_event() and freed
+ * when the information is retrieved by userspace
+ */
+struct fanotify_file_event_info {
+	struct fanotify_event_info fae;
+	/*
+	 * For filename events (create,delete,rename), path points to the
+	 * directory and name holds the entry name
+	 */
+	int name_len;
+	char name[];
+};
+
 #ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
 /*
  * Structure for permission fanotify events. It gets allocated and freed in
@@ -41,10 +56,17 @@  FANOTIFY_PE(struct fsnotify_event *fse)
 }
 #endif
 
+static inline struct fanotify_file_event_info *
+FANOTIFY_FE(struct fsnotify_event *fse)
+{
+	return container_of(fse, struct fanotify_file_event_info, fae.fse);
+}
+
 static inline struct fanotify_event_info *FANOTIFY_E(struct fsnotify_event *fse)
 {
 	return container_of(fse, struct fanotify_event_info, fse);
 }
 
 struct fanotify_event_info *fanotify_alloc_event(struct inode *inode, u32 mask,
-						 struct path *path);
+						 struct path *path,
+						 const char *file_name);
diff --git a/fs/notify/fanotify/fanotify_user.c b/fs/notify/fanotify/fanotify_user.c
index 616769a..03fcf38 100644
--- a/fs/notify/fanotify/fanotify_user.c
+++ b/fs/notify/fanotify/fanotify_user.c
@@ -205,13 +205,22 @@  static int process_access_response(struct fsnotify_group *group,
 }
 #endif
 
+static int round_event_name_len(struct fanotify_file_event_info *event)
+{
+	if (!event->name_len)
+		return 0;
+	return roundup(event->name_len + 1, FAN_EVENT_METADATA_LEN);
+}
+
 static ssize_t copy_event_to_user(struct fsnotify_group *group,
 				  struct fsnotify_event *event,
 				  char __user *buf)
 {
 	struct fanotify_event_metadata fanotify_event_metadata;
+	struct fanotify_file_event_info *ffe = NULL;
 	struct file *f;
 	int fd, ret;
+	size_t pad_name_len = 0;
 
 	pr_debug("%s: group=%p event=%p\n", __func__, group, event);
 
@@ -219,12 +228,37 @@  static ssize_t copy_event_to_user(struct fsnotify_group *group,
 	if (ret < 0)
 		return ret;
 
+	if ((event->mask & FAN_FILENAME_EVENTS) &&
+	    (group->fanotify_data.flags & FAN_EVENT_INFO_NAME)) {
+		ffe = FANOTIFY_FE(event);
+		pad_name_len = round_event_name_len(ffe);
+		fanotify_event_metadata.event_len += pad_name_len;
+	}
+
 	fd = fanotify_event_metadata.fd;
 	ret = -EFAULT;
 	if (copy_to_user(buf, &fanotify_event_metadata,
-			 fanotify_event_metadata.event_len))
+			 FAN_EVENT_METADATA_LEN))
 		goto out_close_fd;
 
+	buf += FAN_EVENT_METADATA_LEN;
+
+	/*
+	 * send the filename and pad to a multiple of FAN_EVENT_METADATA_LEN
+	 * with zeros.
+	 */
+	ret = -EFAULT;
+	if (ffe && pad_name_len) {
+		/* copy the filename */
+		if (copy_to_user(buf, ffe->name, ffe->name_len))
+			goto out_close_fd;
+		buf += ffe->name_len;
+
+		/* fill userspace with 0's */
+		if (clear_user(buf, pad_name_len - ffe->name_len))
+			goto out_close_fd;
+	}
+
 #ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
 	if (event->mask & FAN_ALL_PERM_EVENTS)
 		FANOTIFY_PE(event)->fd = fd;
@@ -755,7 +789,7 @@  SYSCALL_DEFINE2(fanotify_init, unsigned int, flags, unsigned int, event_f_flags)
 	group->fanotify_data.user = user;
 	atomic_inc(&user->fanotify_listeners);
 
-	oevent = fanotify_alloc_event(NULL, FS_Q_OVERFLOW, NULL);
+	oevent = fanotify_alloc_event(NULL, FS_Q_OVERFLOW, NULL, NULL);
 	if (unlikely(!oevent)) {
 		fd = -ENOMEM;
 		goto out_destroy_group;
@@ -919,6 +953,14 @@  SYSCALL_DEFINE5(fanotify_mark, int, fanotify_fd, unsigned int, flags,
 	    !(group->fanotify_data.flags & FAN_EVENT_INFO_PARENT))
 		mask &= ~FAN_DENTRY_EVENTS;
 
+	/*
+	 * Filename events are not interesting without the name inforamtion.
+	 * Ignore filename events unless user explicitly set the new
+	 * FAN_EVENT_INFO_NAME flag to fanotify_init().
+	 */
+	if (!(group->fanotify_data.flags & FAN_EVENT_INFO_NAME))
+		mask &= ~FAN_FILENAME_EVENTS;
+
 	/* create/update an inode mark */
 	switch (flags & (FAN_MARK_ADD | FAN_MARK_REMOVE)) {
 	case FAN_MARK_ADD:
diff --git a/include/uapi/linux/fanotify.h b/include/uapi/linux/fanotify.h
index 3389da0..83f26d9 100644
--- a/include/uapi/linux/fanotify.h
+++ b/include/uapi/linux/fanotify.h
@@ -47,7 +47,9 @@ 
 
 /* These bits determine the format of the reported events */
 #define FAN_EVENT_INFO_PARENT	0x00000100	/* Event fd maybe of parent */
-#define FAN_ALL_EVENT_INFO_BITS (FAN_EVENT_INFO_PARENT)
+#define FAN_EVENT_INFO_NAME	0x00000200	/* Event data has filename */
+#define FAN_ALL_EVENT_INFO_BITS (FAN_EVENT_INFO_PARENT | \
+				 FAN_EVENT_INFO_NAME)
 
 #define FAN_ALL_INIT_FLAGS	(FAN_CLOEXEC | FAN_NONBLOCK | \
 				 FAN_ALL_CLASS_BITS | \
@@ -98,6 +100,10 @@ 
 #define FAN_ALL_PERM_EVENTS (FAN_OPEN_PERM |\
 			     FAN_ACCESS_PERM)
 
+/* Events on directory requiring to pass filename */
+#define FAN_FILENAME_EVENTS (FAN_MOVED_FROM | FAN_MOVED_TO |\
+			     FAN_CREATE | FAN_DELETE)
+
 #define FAN_ALL_OUTGOING_EVENTS	(FAN_ALL_EVENTS |\
 				 FAN_ALL_PERM_EVENTS |\
 				 FAN_Q_OVERFLOW)