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@xxxxxxxxx> --- 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(-) 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) -- 2.7.4 -- To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html