When user requests the flag FAN_EVENT_INFO_FH in fanotify_init(), sb root watcher events data will start with the reported object's file handle, followed by an optional filename for filename events. The file handle can be used as unique object identifier of the affected inode. It also makes holding the dentry reference and passing file descriptor to user redundant, so they may be made optional later on. Signed-off-by: Amir Goldstein <amir73il@xxxxxxxxx> --- fs/notify/fanotify/fanotify.c | 40 ++++++++++++++++----- fs/notify/fanotify/fanotify.h | 19 ++++++++-- fs/notify/fanotify/fanotify_user.c | 72 +++++++++++++++++++++++++++----------- include/uapi/linux/fanotify.h | 4 ++- 4 files changed, 102 insertions(+), 33 deletions(-) diff --git a/fs/notify/fanotify/fanotify.c b/fs/notify/fanotify/fanotify.c index 8a88d88..ca9894c 100644 --- a/fs/notify/fanotify/fanotify.c +++ b/fs/notify/fanotify/fanotify.c @@ -148,7 +148,8 @@ static bool fanotify_should_send_event(struct fsnotify_mark *inode_mark, return true; } -struct fanotify_event_info *fanotify_alloc_event(struct inode *inode, u32 mask, +struct fanotify_event_info *fanotify_alloc_event(struct fsnotify_group *group, + struct inode *inode, u32 mask, const struct path *path, const char *file_name) { @@ -183,19 +184,42 @@ struct fanotify_event_info *fanotify_alloc_event(struct inode *inode, u32 mask, if (mask & (FAN_FILENAME_EVENTS | FAN_EVENT_ON_SB)) { struct fanotify_file_event_info *ffe; int alloc_len = sizeof(*ffe); - int len = 0; + int name_len = 0; - if ((mask & FAN_FILENAME_EVENTS) && file_name) { - len = strlen(file_name); - alloc_len += len + 1; + if ((mask & FAN_FILENAME_EVENTS) && file_name && + (group->fanotify_data.flags & FAN_EVENT_INFO_NAME)) { + name_len = strlen(file_name); + alloc_len += name_len + 1; } ffe = kmalloc(alloc_len, GFP_KERNEL); if (!ffe) return NULL; event = &ffe->fae; - ffe->name_len = len; - if (len) + ffe->name_len = name_len; + if (name_len) strcpy(ffe->name, file_name); + + ffe->fh.handle_type = FILEID_INVALID; + ffe->fh.handle_bytes = 0; + if ((mask & FAN_EVENT_ON_SB) && + (group->fanotify_data.flags & FAN_EVENT_INFO_FH)) { + /* + * Encode only parent (dentry) for filename events + * and both parent and child for other events. + * ffe->fid is big enough to encode xfs type 0x82: + * 64bit parent+child inodes and 32bit generations + */ + int handle_dwords = sizeof(ffe->fid) >> 2; + int type = exportfs_encode_fh(path->dentry, + (struct fid *)&ffe->fid, + &handle_dwords, + !(mask & FAN_FILENAME_EVENTS)); + + if (type > 0 && type < FILEID_INVALID) { + ffe->fh.handle_type = type; + ffe->fh.handle_bytes = handle_dwords << 2; + } + } goto init; } @@ -267,7 +291,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, file_name); + event = fanotify_alloc_event(group, inode, mask, &path, file_name); if (unlikely(!event)) return -ENOMEM; diff --git a/fs/notify/fanotify/fanotify.h b/fs/notify/fanotify/fanotify.h index d23b35b..cbe8b2a 100644 --- a/fs/notify/fanotify/fanotify.h +++ b/fs/notify/fanotify/fanotify.h @@ -1,6 +1,7 @@ #include <linux/fsnotify_backend.h> #include <linux/path.h> #include <linux/slab.h> +#include <linux/exportfs.h> extern struct kmem_cache *fanotify_event_cachep; extern struct kmem_cache *fanotify_perm_event_cachep; @@ -20,6 +21,13 @@ struct fanotify_event_info { struct pid *tgid; }; +struct fanotify_fid64 { + u64 ino; + u32 gen; + u64 parent_ino; + u32 parent_gen; +} __attribute__((packed)); + /* * Structure for fanotify events with variable length data. * It gets allocated in fanotify_handle_event() and freed @@ -28,11 +36,16 @@ struct fanotify_event_info { struct fanotify_file_event_info { struct fanotify_event_info fae; /* + * For events reported to sb root record the file handle + */ + struct file_handle fh; + struct fanotify_fid64 fid; /* make this allocated? */ + /* * For filename events (create,delete,rename), path points to the * directory and name holds the entry name */ int name_len; - char name[]; + char name[]; /* make this allocated? */ }; #ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS @@ -88,7 +101,7 @@ 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 fanotify_event_info *fanotify_alloc_event(struct fsnotify_group *group, + struct inode *inode, u32 mask, const 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 191bd7d..adef7b0 100644 --- a/fs/notify/fanotify/fanotify_user.c +++ b/fs/notify/fanotify/fanotify_user.c @@ -45,6 +45,20 @@ static struct kmem_cache *fanotify_mark_cache __read_mostly; struct kmem_cache *fanotify_event_cachep __read_mostly; struct kmem_cache *fanotify_perm_event_cachep __read_mostly; +static int round_event_data_len(struct fanotify_file_event_info *event) +{ + int data_len = 0; + + if (!event->name_len && !event->fh.handle_bytes) + return 0; + + if (event->name_len) + data_len += event->name_len + 1; + if (event->fh.handle_bytes) + data_len += sizeof(event->fh) + event->fh.handle_bytes; + return roundup(data_len, FAN_EVENT_METADATA_LEN); +} + /* * Get an fsnotify notification event if one exists and is small * enough to fit in "count". Return an error pointer if the count @@ -55,14 +69,23 @@ struct kmem_cache *fanotify_perm_event_cachep __read_mostly; static struct fsnotify_event *get_one_event(struct fsnotify_group *group, size_t count) { - assert_spin_locked(&group->notification_lock); + size_t event_size = FAN_EVENT_METADATA_LEN; + struct fsnotify_event *event; - pr_debug("%s: group=%p count=%zd\n", __func__, group, count); + assert_spin_locked(&group->notification_lock); if (fsnotify_notify_queue_is_empty(group)) return NULL; - if (FAN_EVENT_METADATA_LEN > count) + event = fsnotify_peek_first_event(group); + + pr_debug("%s: group=%p event=%p count=%zd\n", __func__, + group, event, count); + + if (FANOTIFY_IS_FE(event)) + event_size += round_event_data_len(FANOTIFY_FE(event)); + + if (event_size > count) return ERR_PTR(-EINVAL); /* held the notification_lock the whole time, so this is the @@ -206,13 +229,6 @@ 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) @@ -221,7 +237,7 @@ static ssize_t copy_event_to_user(struct fsnotify_group *group, struct fanotify_file_event_info *ffe = NULL; struct file *f; int fd, ret; - size_t pad_name_len = 0; + size_t pad_data_len = 0; pr_debug("%s: group=%p event=%p\n", __func__, group, event); @@ -230,10 +246,11 @@ static ssize_t copy_event_to_user(struct fsnotify_group *group, return ret; if (FANOTIFY_IS_FE(event) && - (group->fanotify_data.flags & FAN_EVENT_INFO_NAME)) { + (group->fanotify_data.flags & + (FAN_EVENT_INFO_NAME | FAN_EVENT_INFO_FH))) { ffe = FANOTIFY_FE(event); - pad_name_len = round_event_name_len(ffe); - fanotify_event_metadata.event_len += pad_name_len; + pad_data_len = round_event_data_len(ffe); + fanotify_event_metadata.event_len += pad_data_len; } fd = fanotify_event_metadata.fd; @@ -249,14 +266,27 @@ static ssize_t copy_event_to_user(struct fsnotify_group *group, * 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; + if (ffe && pad_data_len) { + if (ffe->fh.handle_bytes) { + int fh_len = sizeof(ffe->fh) + ffe->fh.handle_bytes; + + /* copy the file handle (bytes,type,fid) */ + if (copy_to_user(buf, &ffe->fh, fh_len)) + goto out_close_fd; + buf += fh_len; + pad_data_len -= fh_len; + } + + if (ffe->name_len) { + /* copy the filename */ + if (copy_to_user(buf, ffe->name, ffe->name_len)) + goto out_close_fd; + buf += ffe->name_len; + pad_data_len -= ffe->name_len; + } /* fill userspace with 0's */ - if (clear_user(buf, pad_name_len - ffe->name_len)) + if (clear_user(buf, pad_data_len)) goto out_close_fd; } @@ -803,7 +833,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, NULL); + oevent = fanotify_alloc_event(group, NULL, FS_Q_OVERFLOW, NULL, NULL); if (unlikely(!oevent)) { fd = -ENOMEM; goto out_destroy_group; diff --git a/include/uapi/linux/fanotify.h b/include/uapi/linux/fanotify.h index b202e09..86f0321 100644 --- a/include/uapi/linux/fanotify.h +++ b/include/uapi/linux/fanotify.h @@ -51,8 +51,10 @@ /* These bits determine the format of the reported events */ #define FAN_EVENT_INFO_PARENT 0x00000100 /* Event fd maybe of parent */ #define FAN_EVENT_INFO_NAME 0x00000200 /* Event data has filename */ +#define FAN_EVENT_INFO_FH 0x00000400 /* Event data has filehandle */ #define FAN_ALL_EVENT_INFO_BITS (FAN_EVENT_INFO_PARENT | \ - FAN_EVENT_INFO_NAME) + FAN_EVENT_INFO_NAME | \ + FAN_EVENT_INFO_FH) #define FAN_ALL_INIT_FLAGS (FAN_CLOEXEC | FAN_NONBLOCK | \ FAN_ALL_CLASS_BITS | \ -- 2.7.4