diff options
Diffstat (limited to 'fs/notify')
| -rw-r--r-- | fs/notify/dnotify/dnotify.c | 34 | ||||
| -rw-r--r-- | fs/notify/fanotify/fanotify.c | 263 | ||||
| -rw-r--r-- | fs/notify/fanotify/fanotify.h | 50 | ||||
| -rw-r--r-- | fs/notify/fanotify/fanotify_user.c | 275 | ||||
| -rw-r--r-- | fs/notify/fsnotify.c | 42 | ||||
| -rw-r--r-- | fs/notify/group.c | 7 | ||||
| -rw-r--r-- | fs/notify/inotify/inotify.h | 21 | ||||
| -rw-r--r-- | fs/notify/inotify/inotify_fsnotify.c | 161 | ||||
| -rw-r--r-- | fs/notify/inotify/inotify_user.c | 133 | ||||
| -rw-r--r-- | fs/notify/mark.c | 2 | ||||
| -rw-r--r-- | fs/notify/notification.c | 358 | 
11 files changed, 536 insertions, 810 deletions
diff --git a/fs/notify/dnotify/dnotify.c b/fs/notify/dnotify/dnotify.c index 1fedd5f7ccc..abc8cbcfe90 100644 --- a/fs/notify/dnotify/dnotify.c +++ b/fs/notify/dnotify/dnotify.c @@ -82,20 +82,23 @@ static void dnotify_recalc_inode_mask(struct fsnotify_mark *fsn_mark)   * events.   */  static int dnotify_handle_event(struct fsnotify_group *group, +				struct inode *inode,  				struct fsnotify_mark *inode_mark,  				struct fsnotify_mark *vfsmount_mark, -				struct fsnotify_event *event) +				u32 mask, void *data, int data_type, +				const unsigned char *file_name, u32 cookie)  {  	struct dnotify_mark *dn_mark; -	struct inode *to_tell;  	struct dnotify_struct *dn;  	struct dnotify_struct **prev;  	struct fown_struct *fown; -	__u32 test_mask = event->mask & ~FS_EVENT_ON_CHILD; +	__u32 test_mask = mask & ~FS_EVENT_ON_CHILD; -	BUG_ON(vfsmount_mark); +	/* not a dir, dnotify doesn't care */ +	if (!S_ISDIR(inode->i_mode)) +		return 0; -	to_tell = event->to_tell; +	BUG_ON(vfsmount_mark);  	dn_mark = container_of(inode_mark, struct dnotify_mark, fsn_mark); @@ -122,23 +125,6 @@ static int dnotify_handle_event(struct fsnotify_group *group,  	return 0;  } -/* - * Given an inode and mask determine if dnotify would be interested in sending - * userspace notification for that pair. - */ -static bool dnotify_should_send_event(struct fsnotify_group *group, -				      struct inode *inode, -				      struct fsnotify_mark *inode_mark, -				      struct fsnotify_mark *vfsmount_mark, -				      __u32 mask, void *data, int data_type) -{ -	/* not a dir, dnotify doesn't care */ -	if (!S_ISDIR(inode->i_mode)) -		return false; - -	return true; -} -  static void dnotify_free_mark(struct fsnotify_mark *fsn_mark)  {  	struct dnotify_mark *dn_mark = container_of(fsn_mark, @@ -152,10 +138,6 @@ static void dnotify_free_mark(struct fsnotify_mark *fsn_mark)  static struct fsnotify_ops dnotify_fsnotify_ops = {  	.handle_event = dnotify_handle_event, -	.should_send_event = dnotify_should_send_event, -	.free_group_priv = NULL, -	.freeing_mark = NULL, -	.free_event_priv = NULL,  };  /* diff --git a/fs/notify/fanotify/fanotify.c b/fs/notify/fanotify/fanotify.c index 0c2f9122b26..ee9cb3795c2 100644 --- a/fs/notify/fanotify/fanotify.c +++ b/fs/notify/fanotify/fanotify.c @@ -9,91 +9,59 @@  #include <linux/types.h>  #include <linux/wait.h> -static bool should_merge(struct fsnotify_event *old, struct fsnotify_event *new) +#include "fanotify.h" + +static bool should_merge(struct fsnotify_event *old_fsn, +			 struct fsnotify_event *new_fsn)  { -	pr_debug("%s: old=%p new=%p\n", __func__, old, new); +	struct fanotify_event_info *old, *new; -	if (old->to_tell == new->to_tell && -	    old->data_type == new->data_type && -	    old->tgid == new->tgid) { -		switch (old->data_type) { -		case (FSNOTIFY_EVENT_PATH): -#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS -			/* dont merge two permission events */ -			if ((old->mask & FAN_ALL_PERM_EVENTS) && -			    (new->mask & FAN_ALL_PERM_EVENTS)) -				return false; -#endif -			if ((old->path.mnt == new->path.mnt) && -			    (old->path.dentry == new->path.dentry)) -				return true; -			break; -		case (FSNOTIFY_EVENT_NONE): -			return true; -		default: -			BUG(); -		}; -	} +	pr_debug("%s: old=%p new=%p\n", __func__, old_fsn, new_fsn); +	old = FANOTIFY_E(old_fsn); +	new = FANOTIFY_E(new_fsn); + +	if (old_fsn->inode == new_fsn->inode && old->tgid == new->tgid && +	    old->path.mnt == new->path.mnt && +	    old->path.dentry == new->path.dentry) +		return true;  	return false;  }  /* and the list better be locked by something too! */ -static struct fsnotify_event *fanotify_merge(struct list_head *list, -					     struct fsnotify_event *event) +static int fanotify_merge(struct list_head *list, struct fsnotify_event *event)  { -	struct fsnotify_event_holder *test_holder; -	struct fsnotify_event *test_event = NULL; -	struct fsnotify_event *new_event; +	struct fsnotify_event *test_event; +	bool do_merge = false;  	pr_debug("%s: list=%p event=%p\n", __func__, list, event); +#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS +	/* +	 * Don't merge a permission event with any other event so that we know +	 * the event structure we have created in fanotify_handle_event() is the +	 * one we should check for permission response. +	 */ +	if (event->mask & FAN_ALL_PERM_EVENTS) +		return 0; +#endif -	list_for_each_entry_reverse(test_holder, list, event_list) { -		if (should_merge(test_holder->event, event)) { -			test_event = test_holder->event; +	list_for_each_entry_reverse(test_event, list, list) { +		if (should_merge(test_event, event)) { +			do_merge = true;  			break;  		}  	} -	if (!test_event) -		return NULL; - -	fsnotify_get_event(test_event); - -	/* if they are exactly the same we are done */ -	if (test_event->mask == event->mask) -		return test_event; - -	/* -	 * if the refcnt == 2 this is the only queue -	 * for this event and so we can update the mask -	 * in place. -	 */ -	if (atomic_read(&test_event->refcnt) == 2) { -		test_event->mask |= event->mask; -		return test_event; -	} - -	new_event = fsnotify_clone_event(test_event); - -	/* done with test_event */ -	fsnotify_put_event(test_event); - -	/* couldn't allocate memory, merge was not possible */ -	if (unlikely(!new_event)) -		return ERR_PTR(-ENOMEM); - -	/* build new event and replace it on the list */ -	new_event->mask = (test_event->mask | event->mask); -	fsnotify_replace_event(test_holder, new_event); +	if (!do_merge) +		return 0; -	/* we hold a reference on new_event from clone_event */ -	return new_event; +	test_event->mask |= event->mask; +	return 1;  }  #ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS -static int fanotify_get_response_from_access(struct fsnotify_group *group, -					     struct fsnotify_event *event) +static int fanotify_get_response(struct fsnotify_group *group, +				 struct fanotify_perm_event_info *event)  {  	int ret; @@ -106,7 +74,6 @@ static int fanotify_get_response_from_access(struct fsnotify_group *group,  		return 0;  	/* userspace responded, convert to something usable */ -	spin_lock(&event->lock);  	switch (event->response) {  	case FAN_ALLOW:  		ret = 0; @@ -116,7 +83,6 @@ static int fanotify_get_response_from_access(struct fsnotify_group *group,  		ret = -EPERM;  	}  	event->response = 0; -	spin_unlock(&event->lock);  	pr_debug("%s: group=%p event=%p about to return ret=%d\n", __func__,  		 group, event, ret); @@ -125,58 +91,17 @@ static int fanotify_get_response_from_access(struct fsnotify_group *group,  }  #endif -static int fanotify_handle_event(struct fsnotify_group *group, -				 struct fsnotify_mark *inode_mark, -				 struct fsnotify_mark *fanotify_mark, -				 struct fsnotify_event *event) -{ -	int ret = 0; -	struct fsnotify_event *notify_event = NULL; - -	BUILD_BUG_ON(FAN_ACCESS != FS_ACCESS); -	BUILD_BUG_ON(FAN_MODIFY != FS_MODIFY); -	BUILD_BUG_ON(FAN_CLOSE_NOWRITE != FS_CLOSE_NOWRITE); -	BUILD_BUG_ON(FAN_CLOSE_WRITE != FS_CLOSE_WRITE); -	BUILD_BUG_ON(FAN_OPEN != FS_OPEN); -	BUILD_BUG_ON(FAN_EVENT_ON_CHILD != FS_EVENT_ON_CHILD); -	BUILD_BUG_ON(FAN_Q_OVERFLOW != FS_Q_OVERFLOW); -	BUILD_BUG_ON(FAN_OPEN_PERM != FS_OPEN_PERM); -	BUILD_BUG_ON(FAN_ACCESS_PERM != FS_ACCESS_PERM); -	BUILD_BUG_ON(FAN_ONDIR != FS_ISDIR); - -	pr_debug("%s: group=%p event=%p\n", __func__, group, event); - -	notify_event = fsnotify_add_notify_event(group, event, NULL, fanotify_merge); -	if (IS_ERR(notify_event)) -		return PTR_ERR(notify_event); - -#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS -	if (event->mask & FAN_ALL_PERM_EVENTS) { -		/* if we merged we need to wait on the new event */ -		if (notify_event) -			event = notify_event; -		ret = fanotify_get_response_from_access(group, event); -	} -#endif - -	if (notify_event) -		fsnotify_put_event(notify_event); - -	return ret; -} - -static bool fanotify_should_send_event(struct fsnotify_group *group, -				       struct inode *to_tell, -				       struct fsnotify_mark *inode_mark, +static bool fanotify_should_send_event(struct fsnotify_mark *inode_mark,  				       struct fsnotify_mark *vfsmnt_mark, -				       __u32 event_mask, void *data, int data_type) +				       u32 event_mask, +				       void *data, int data_type)  {  	__u32 marks_mask, marks_ignored_mask;  	struct path *path = data; -	pr_debug("%s: group=%p to_tell=%p inode_mark=%p vfsmnt_mark=%p " -		 "mask=%x data=%p data_type=%d\n", __func__, group, to_tell, -		 inode_mark, vfsmnt_mark, event_mask, data, data_type); +	pr_debug("%s: inode_mark=%p vfsmnt_mark=%p mask=%x data=%p" +		 " data_type=%d\n", __func__, inode_mark, vfsmnt_mark, +		 event_mask, data, data_type);  	/* if we don't have enough info to send an event to userspace say no */  	if (data_type != FSNOTIFY_EVENT_PATH) @@ -217,6 +142,93 @@ static bool fanotify_should_send_event(struct fsnotify_group *group,  	return false;  } +struct fanotify_event_info *fanotify_alloc_event(struct inode *inode, u32 mask, +						 struct path *path) +{ +	struct fanotify_event_info *event; + +#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS +	if (mask & FAN_ALL_PERM_EVENTS) { +		struct fanotify_perm_event_info *pevent; + +		pevent = kmem_cache_alloc(fanotify_perm_event_cachep, +					  GFP_KERNEL); +		if (!pevent) +			return NULL; +		event = &pevent->fae; +		pevent->response = 0; +		goto init; +	} +#endif +	event = kmem_cache_alloc(fanotify_event_cachep, GFP_KERNEL); +	if (!event) +		return NULL; +init: __maybe_unused +	fsnotify_init_event(&event->fse, inode, mask); +	event->tgid = get_pid(task_tgid(current)); +	if (path) { +		event->path = *path; +		path_get(&event->path); +	} else { +		event->path.mnt = NULL; +		event->path.dentry = NULL; +	} +	return event; +} + +static int fanotify_handle_event(struct fsnotify_group *group, +				 struct inode *inode, +				 struct fsnotify_mark *inode_mark, +				 struct fsnotify_mark *fanotify_mark, +				 u32 mask, void *data, int data_type, +				 const unsigned char *file_name, u32 cookie) +{ +	int ret = 0; +	struct fanotify_event_info *event; +	struct fsnotify_event *fsn_event; + +	BUILD_BUG_ON(FAN_ACCESS != FS_ACCESS); +	BUILD_BUG_ON(FAN_MODIFY != FS_MODIFY); +	BUILD_BUG_ON(FAN_CLOSE_NOWRITE != FS_CLOSE_NOWRITE); +	BUILD_BUG_ON(FAN_CLOSE_WRITE != FS_CLOSE_WRITE); +	BUILD_BUG_ON(FAN_OPEN != FS_OPEN); +	BUILD_BUG_ON(FAN_EVENT_ON_CHILD != FS_EVENT_ON_CHILD); +	BUILD_BUG_ON(FAN_Q_OVERFLOW != FS_Q_OVERFLOW); +	BUILD_BUG_ON(FAN_OPEN_PERM != FS_OPEN_PERM); +	BUILD_BUG_ON(FAN_ACCESS_PERM != FS_ACCESS_PERM); +	BUILD_BUG_ON(FAN_ONDIR != FS_ISDIR); + +	if (!fanotify_should_send_event(inode_mark, fanotify_mark, mask, data, +					data_type)) +		return 0; + +	pr_debug("%s: group=%p inode=%p mask=%x\n", __func__, group, inode, +		 mask); + +	event = fanotify_alloc_event(inode, mask, data); +	if (unlikely(!event)) +		return -ENOMEM; + +	fsn_event = &event->fse; +	ret = fsnotify_add_notify_event(group, fsn_event, fanotify_merge); +	if (ret) { +		/* Permission events shouldn't be merged */ +		BUG_ON(ret == 1 && mask & FAN_ALL_PERM_EVENTS); +		/* Our event wasn't used in the end. Free it. */ +		fsnotify_destroy_event(group, fsn_event); + +		return 0; +	} + +#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS +	if (mask & FAN_ALL_PERM_EVENTS) { +		ret = fanotify_get_response(group, FANOTIFY_PE(fsn_event)); +		fsnotify_destroy_event(group, fsn_event); +	} +#endif +	return ret; +} +  static void fanotify_free_group_priv(struct fsnotify_group *group)  {  	struct user_struct *user; @@ -226,10 +238,25 @@ static void fanotify_free_group_priv(struct fsnotify_group *group)  	free_uid(user);  } +static void fanotify_free_event(struct fsnotify_event *fsn_event) +{ +	struct fanotify_event_info *event; + +	event = FANOTIFY_E(fsn_event); +	path_put(&event->path); +	put_pid(event->tgid); +#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS +	if (fsn_event->mask & FAN_ALL_PERM_EVENTS) { +		kmem_cache_free(fanotify_perm_event_cachep, +				FANOTIFY_PE(fsn_event)); +		return; +	} +#endif +	kmem_cache_free(fanotify_event_cachep, event); +} +  const struct fsnotify_ops fanotify_fsnotify_ops = {  	.handle_event = fanotify_handle_event, -	.should_send_event = fanotify_should_send_event,  	.free_group_priv = fanotify_free_group_priv, -	.free_event_priv = NULL, -	.freeing_mark = NULL, +	.free_event = fanotify_free_event,  }; diff --git a/fs/notify/fanotify/fanotify.h b/fs/notify/fanotify/fanotify.h new file mode 100644 index 00000000000..2a5fb14115d --- /dev/null +++ b/fs/notify/fanotify/fanotify.h @@ -0,0 +1,50 @@ +#include <linux/fsnotify_backend.h> +#include <linux/path.h> +#include <linux/slab.h> + +extern struct kmem_cache *fanotify_event_cachep; +extern struct kmem_cache *fanotify_perm_event_cachep; + +/* + * Structure for normal fanotify events. It gets allocated in + * fanotify_handle_event() and freed when the information is retrieved by + * userspace + */ +struct fanotify_event_info { +	struct fsnotify_event fse; +	/* +	 * We hold ref to this path so it may be dereferenced at any point +	 * during this object's lifetime +	 */ +	struct path path; +	struct pid *tgid; +}; + +#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS +/* + * Structure for permission fanotify events. It gets allocated and freed in + * fanotify_handle_event() since we wait there for user response. When the + * information is retrieved by userspace the structure is moved from + * group->notification_list to group->fanotify_data.access_list to wait for + * user response. + */ +struct fanotify_perm_event_info { +	struct fanotify_event_info fae; +	int response;	/* userspace answer to question */ +	int fd;		/* fd we passed to userspace for this event */ +}; + +static inline struct fanotify_perm_event_info * +FANOTIFY_PE(struct fsnotify_event *fse) +{ +	return container_of(fse, struct fanotify_perm_event_info, fae.fse); +} +#endif + +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); diff --git a/fs/notify/fanotify/fanotify_user.c b/fs/notify/fanotify/fanotify_user.c index e44cb6427df..3fdc8a3e113 100644 --- a/fs/notify/fanotify/fanotify_user.c +++ b/fs/notify/fanotify/fanotify_user.c @@ -19,21 +19,30 @@  #include "../../mount.h"  #include "../fdinfo.h" +#include "fanotify.h"  #define FANOTIFY_DEFAULT_MAX_EVENTS	16384  #define FANOTIFY_DEFAULT_MAX_MARKS	8192  #define FANOTIFY_DEFAULT_MAX_LISTENERS	128 +/* + * All flags that may be specified in parameter event_f_flags of fanotify_init. + * + * Internal and external open flags are stored together in field f_flags of + * struct file. Only external open flags shall be allowed in event_f_flags. + * Internal flags like FMODE_NONOTIFY, FMODE_EXEC, FMODE_NOCMTIME shall be + * excluded. + */ +#define	FANOTIFY_INIT_ALL_EVENT_F_BITS				( \ +		O_ACCMODE	| O_APPEND	| O_NONBLOCK	| \ +		__O_SYNC	| O_DSYNC	| O_CLOEXEC     | \ +		O_LARGEFILE	| O_NOATIME	) +  extern const struct fsnotify_ops fanotify_fsnotify_ops;  static struct kmem_cache *fanotify_mark_cache __read_mostly; -static struct kmem_cache *fanotify_response_event_cache __read_mostly; - -struct fanotify_response_event { -	struct list_head list; -	__s32 fd; -	struct fsnotify_event *event; -}; +struct kmem_cache *fanotify_event_cachep __read_mostly; +struct kmem_cache *fanotify_perm_event_cachep __read_mostly;  /*   * Get an fsnotify notification event if one exists and is small @@ -61,8 +70,8 @@ static struct fsnotify_event *get_one_event(struct fsnotify_group *group,  }  static int create_fd(struct fsnotify_group *group, -			struct fsnotify_event *event, -			struct file **file) +		     struct fanotify_event_info *event, +		     struct file **file)  {  	int client_fd;  	struct file *new_file; @@ -73,12 +82,6 @@ static int create_fd(struct fsnotify_group *group,  	if (client_fd < 0)  		return client_fd; -	if (event->data_type != FSNOTIFY_EVENT_PATH) { -		WARN_ON(1); -		put_unused_fd(client_fd); -		return -EINVAL; -	} -  	/*  	 * we need a new file handle for the userspace program so it can read even if it was  	 * originally opened O_WRONLY. @@ -109,23 +112,25 @@ static int create_fd(struct fsnotify_group *group,  }  static int fill_event_metadata(struct fsnotify_group *group, -				   struct fanotify_event_metadata *metadata, -				   struct fsnotify_event *event, -				   struct file **file) +			       struct fanotify_event_metadata *metadata, +			       struct fsnotify_event *fsn_event, +			       struct file **file)  {  	int ret = 0; +	struct fanotify_event_info *event;  	pr_debug("%s: group=%p metadata=%p event=%p\n", __func__, -		 group, metadata, event); +		 group, metadata, fsn_event);  	*file = NULL; +	event = container_of(fsn_event, struct fanotify_event_info, fse);  	metadata->event_len = FAN_EVENT_METADATA_LEN;  	metadata->metadata_len = FAN_EVENT_METADATA_LEN;  	metadata->vers = FANOTIFY_METADATA_VERSION;  	metadata->reserved = 0; -	metadata->mask = event->mask & FAN_ALL_OUTGOING_EVENTS; +	metadata->mask = fsn_event->mask & FAN_ALL_OUTGOING_EVENTS;  	metadata->pid = pid_vnr(event->tgid); -	if (unlikely(event->mask & FAN_Q_OVERFLOW)) +	if (unlikely(fsn_event->mask & FAN_Q_OVERFLOW))  		metadata->fd = FAN_NOFD;  	else {  		metadata->fd = create_fd(group, event, file); @@ -137,33 +142,34 @@ static int fill_event_metadata(struct fsnotify_group *group,  }  #ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS -static struct fanotify_response_event *dequeue_re(struct fsnotify_group *group, -						  __s32 fd) +static struct fanotify_perm_event_info *dequeue_event( +				struct fsnotify_group *group, int fd)  { -	struct fanotify_response_event *re, *return_re = NULL; +	struct fanotify_perm_event_info *event, *return_e = NULL; -	mutex_lock(&group->fanotify_data.access_mutex); -	list_for_each_entry(re, &group->fanotify_data.access_list, list) { -		if (re->fd != fd) +	spin_lock(&group->fanotify_data.access_lock); +	list_for_each_entry(event, &group->fanotify_data.access_list, +			    fae.fse.list) { +		if (event->fd != fd)  			continue; -		list_del_init(&re->list); -		return_re = re; +		list_del_init(&event->fae.fse.list); +		return_e = event;  		break;  	} -	mutex_unlock(&group->fanotify_data.access_mutex); +	spin_unlock(&group->fanotify_data.access_lock); -	pr_debug("%s: found return_re=%p\n", __func__, return_re); +	pr_debug("%s: found return_re=%p\n", __func__, return_e); -	return return_re; +	return return_e;  }  static int process_access_response(struct fsnotify_group *group,  				   struct fanotify_response *response_struct)  { -	struct fanotify_response_event *re; -	__s32 fd = response_struct->fd; -	__u32 response = response_struct->response; +	struct fanotify_perm_event_info *event; +	int fd = response_struct->fd; +	int response = response_struct->response;  	pr_debug("%s: group=%p fd=%d response=%d\n", __func__, group,  		 fd, response); @@ -183,58 +189,15 @@ static int process_access_response(struct fsnotify_group *group,  	if (fd < 0)  		return -EINVAL; -	re = dequeue_re(group, fd); -	if (!re) +	event = dequeue_event(group, fd); +	if (!event)  		return -ENOENT; -	re->event->response = response; - +	event->response = response;  	wake_up(&group->fanotify_data.access_waitq); -	kmem_cache_free(fanotify_response_event_cache, re); -  	return 0;  } - -static int prepare_for_access_response(struct fsnotify_group *group, -				       struct fsnotify_event *event, -				       __s32 fd) -{ -	struct fanotify_response_event *re; - -	if (!(event->mask & FAN_ALL_PERM_EVENTS)) -		return 0; - -	re = kmem_cache_alloc(fanotify_response_event_cache, GFP_KERNEL); -	if (!re) -		return -ENOMEM; - -	re->event = event; -	re->fd = fd; - -	mutex_lock(&group->fanotify_data.access_mutex); - -	if (atomic_read(&group->fanotify_data.bypass_perm)) { -		mutex_unlock(&group->fanotify_data.access_mutex); -		kmem_cache_free(fanotify_response_event_cache, re); -		event->response = FAN_ALLOW; -		return 0; -	} -		 -	list_add_tail(&re->list, &group->fanotify_data.access_list); -	mutex_unlock(&group->fanotify_data.access_mutex); - -	return 0; -} - -#else -static int prepare_for_access_response(struct fsnotify_group *group, -				       struct fsnotify_event *event, -				       __s32 fd) -{ -	return 0; -} -  #endif  static ssize_t copy_event_to_user(struct fsnotify_group *group, @@ -249,7 +212,7 @@ static ssize_t copy_event_to_user(struct fsnotify_group *group,  	ret = fill_event_metadata(group, &fanotify_event_metadata, event, &f);  	if (ret < 0) -		goto out; +		return ret;  	fd = fanotify_event_metadata.fd;  	ret = -EFAULT; @@ -257,9 +220,10 @@ static ssize_t copy_event_to_user(struct fsnotify_group *group,  			 fanotify_event_metadata.event_len))  		goto out_close_fd; -	ret = prepare_for_access_response(group, event, fd); -	if (ret) -		goto out_close_fd; +#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS +	if (event->mask & FAN_ALL_PERM_EVENTS) +		FANOTIFY_PE(event)->fd = fd; +#endif  	if (fd != FAN_NOFD)  		fd_install(fd, f); @@ -270,13 +234,6 @@ out_close_fd:  		put_unused_fd(fd);  		fput(f);  	} -out: -#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS -	if (event->mask & FAN_ALL_PERM_EVENTS) { -		event->response = FAN_DENY; -		wake_up(&group->fanotify_data.access_waitq); -	} -#endif  	return ret;  } @@ -316,30 +273,50 @@ static ssize_t fanotify_read(struct file *file, char __user *buf,  		kevent = get_one_event(group, count);  		mutex_unlock(&group->notification_mutex); -		if (kevent) { +		if (IS_ERR(kevent)) {  			ret = PTR_ERR(kevent); -			if (IS_ERR(kevent)) +			break; +		} + +		if (!kevent) { +			ret = -EAGAIN; +			if (file->f_flags & O_NONBLOCK)  				break; -			ret = copy_event_to_user(group, kevent, buf); -			fsnotify_put_event(kevent); -			if (ret < 0) + +			ret = -ERESTARTSYS; +			if (signal_pending(current)) +				break; + +			if (start != buf)  				break; -			buf += ret; -			count -= ret; +			schedule();  			continue;  		} -		ret = -EAGAIN; -		if (file->f_flags & O_NONBLOCK) -			break; -		ret = -ERESTARTSYS; -		if (signal_pending(current)) -			break; - -		if (start != buf) -			break; - -		schedule(); +		ret = copy_event_to_user(group, kevent, buf); +		/* +		 * Permission events get queued to wait for response.  Other +		 * events can be destroyed now. +		 */ +		if (!(kevent->mask & FAN_ALL_PERM_EVENTS)) { +			fsnotify_destroy_event(group, kevent); +			if (ret < 0) +				break; +		} else { +#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS +			if (ret < 0) { +				FANOTIFY_PE(kevent)->response = FAN_DENY; +				wake_up(&group->fanotify_data.access_waitq); +				break; +			} +			spin_lock(&group->fanotify_data.access_lock); +			list_add_tail(&kevent->list, +				      &group->fanotify_data.access_list); +			spin_unlock(&group->fanotify_data.access_lock); +#endif +		} +		buf += ret; +		count -= ret;  	}  	finish_wait(&group->notification_waitq, &wait); @@ -380,22 +357,21 @@ static int fanotify_release(struct inode *ignored, struct file *file)  	struct fsnotify_group *group = file->private_data;  #ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS -	struct fanotify_response_event *re, *lre; +	struct fanotify_perm_event_info *event, *next; -	mutex_lock(&group->fanotify_data.access_mutex); +	spin_lock(&group->fanotify_data.access_lock);  	atomic_inc(&group->fanotify_data.bypass_perm); -	list_for_each_entry_safe(re, lre, &group->fanotify_data.access_list, list) { -		pr_debug("%s: found group=%p re=%p event=%p\n", __func__, group, -			 re, re->event); - -		list_del_init(&re->list); -		re->event->response = FAN_ALLOW; +	list_for_each_entry_safe(event, next, &group->fanotify_data.access_list, +				 fae.fse.list) { +		pr_debug("%s: found group=%p event=%p\n", __func__, group, +			 event); -		kmem_cache_free(fanotify_response_event_cache, re); +		list_del_init(&event->fae.fse.list); +		event->response = FAN_ALLOW;  	} -	mutex_unlock(&group->fanotify_data.access_mutex); +	spin_unlock(&group->fanotify_data.access_lock);  	wake_up(&group->fanotify_data.access_waitq);  #endif @@ -409,7 +385,7 @@ static int fanotify_release(struct inode *ignored, struct file *file)  static long fanotify_ioctl(struct file *file, unsigned int cmd, unsigned long arg)  {  	struct fsnotify_group *group; -	struct fsnotify_event_holder *holder; +	struct fsnotify_event *fsn_event;  	void __user *p;  	int ret = -ENOTTY;  	size_t send_len = 0; @@ -421,7 +397,7 @@ 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) +		list_for_each_entry(fsn_event, &group->notification_list, list)  			send_len += FAN_EVENT_METADATA_LEN;  		mutex_unlock(&group->notification_mutex);  		ret = put_user(send_len, (int __user *) p); @@ -695,6 +671,7 @@ SYSCALL_DEFINE2(fanotify_init, unsigned int, flags, unsigned int, event_f_flags)  	struct fsnotify_group *group;  	int f_flags, fd;  	struct user_struct *user; +	struct fanotify_event_info *oevent;  	pr_debug("%s: flags=%d event_f_flags=%d\n",  		__func__, flags, event_f_flags); @@ -705,6 +682,18 @@ SYSCALL_DEFINE2(fanotify_init, unsigned int, flags, unsigned int, event_f_flags)  	if (flags & ~FAN_ALL_INIT_FLAGS)  		return -EINVAL; +	if (event_f_flags & ~FANOTIFY_INIT_ALL_EVENT_F_BITS) +		return -EINVAL; + +	switch (event_f_flags & O_ACCMODE) { +	case O_RDONLY: +	case O_RDWR: +	case O_WRONLY: +		break; +	default: +		return -EINVAL; +	} +  	user = get_current_user();  	if (atomic_read(&user->fanotify_listeners) > FANOTIFY_DEFAULT_MAX_LISTENERS) {  		free_uid(user); @@ -727,9 +716,18 @@ 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); +	if (unlikely(!oevent)) { +		fd = -ENOMEM; +		goto out_destroy_group; +	} +	group->overflow_event = &oevent->fse; + +	if (force_o_largefile()) +		event_f_flags |= O_LARGEFILE;  	group->fanotify_data.f_flags = event_f_flags;  #ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS -	mutex_init(&group->fanotify_data.access_mutex); +	spin_lock_init(&group->fanotify_data.access_lock);  	init_waitqueue_head(&group->fanotify_data.access_waitq);  	INIT_LIST_HEAD(&group->fanotify_data.access_list);  	atomic_set(&group->fanotify_data.bypass_perm, 0); @@ -803,7 +801,10 @@ SYSCALL_DEFINE5(fanotify_mark, int, fanotify_fd, unsigned int, flags,  	case FAN_MARK_REMOVE:  		if (!mask)  			return -EINVAL; +		break;  	case FAN_MARK_FLUSH: +		if (flags & ~(FAN_MARK_MOUNT | FAN_MARK_FLUSH)) +			return -EINVAL;  		break;  	default:  		return -EINVAL; @@ -840,6 +841,15 @@ SYSCALL_DEFINE5(fanotify_mark, int, fanotify_fd, unsigned int, flags,  	    group->priority == FS_PRIO_0)  		goto fput_and_out; +	if (flags & FAN_MARK_FLUSH) { +		ret = 0; +		if (flags & FAN_MARK_MOUNT) +			fsnotify_clear_vfsmount_marks_by_group(group); +		else +			fsnotify_clear_inode_marks_by_group(group); +		goto fput_and_out; +	} +  	ret = fanotify_find_path(dfd, pathname, &path, flags);  	if (ret)  		goto fput_and_out; @@ -851,7 +861,7 @@ SYSCALL_DEFINE5(fanotify_mark, int, fanotify_fd, unsigned int, flags,  		mnt = path.mnt;  	/* create/update an inode mark */ -	switch (flags & (FAN_MARK_ADD | FAN_MARK_REMOVE | FAN_MARK_FLUSH)) { +	switch (flags & (FAN_MARK_ADD | FAN_MARK_REMOVE)) {  	case FAN_MARK_ADD:  		if (flags & FAN_MARK_MOUNT)  			ret = fanotify_add_vfsmount_mark(group, mnt, mask, flags); @@ -864,12 +874,6 @@ SYSCALL_DEFINE5(fanotify_mark, int, fanotify_fd, unsigned int, flags,  		else  			ret = fanotify_remove_inode_mark(group, inode, mask, flags);  		break; -	case FAN_MARK_FLUSH: -		if (flags & FAN_MARK_MOUNT) -			fsnotify_clear_vfsmount_marks_by_group(group); -		else -			fsnotify_clear_inode_marks_by_group(group); -		break;  	default:  		ret = -EINVAL;  	} @@ -888,9 +892,9 @@ COMPAT_SYSCALL_DEFINE6(fanotify_mark,  {  	return sys_fanotify_mark(fanotify_fd, flags,  #ifdef __BIG_ENDIAN -				((__u64)mask1 << 32) | mask0, -#else  				((__u64)mask0 << 32) | mask1, +#else +				((__u64)mask1 << 32) | mask0,  #endif  				 dfd, pathname);  } @@ -904,8 +908,11 @@ COMPAT_SYSCALL_DEFINE6(fanotify_mark,  static int __init fanotify_user_setup(void)  {  	fanotify_mark_cache = KMEM_CACHE(fsnotify_mark, SLAB_PANIC); -	fanotify_response_event_cache = KMEM_CACHE(fanotify_response_event, -						   SLAB_PANIC); +	fanotify_event_cachep = KMEM_CACHE(fanotify_event_info, SLAB_PANIC); +#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS +	fanotify_perm_event_cachep = KMEM_CACHE(fanotify_perm_event_info, +						SLAB_PANIC); +#endif  	return 0;  } diff --git a/fs/notify/fsnotify.c b/fs/notify/fsnotify.c index 4bb21d67d9b..9d3e9c50066 100644 --- a/fs/notify/fsnotify.c +++ b/fs/notify/fsnotify.c @@ -128,8 +128,7 @@ static int send_to_group(struct inode *to_tell,  			 struct fsnotify_mark *vfsmount_mark,  			 __u32 mask, void *data,  			 int data_is, u32 cookie, -			 const unsigned char *file_name, -			 struct fsnotify_event **event) +			 const unsigned char *file_name)  {  	struct fsnotify_group *group = NULL;  	__u32 inode_test_mask = 0; @@ -170,27 +169,17 @@ static int send_to_group(struct inode *to_tell,  	pr_debug("%s: group=%p to_tell=%p mask=%x inode_mark=%p"  		 " inode_test_mask=%x vfsmount_mark=%p vfsmount_test_mask=%x" -		 " data=%p data_is=%d cookie=%d event=%p\n", +		 " data=%p data_is=%d cookie=%d\n",  		 __func__, group, to_tell, mask, inode_mark,  		 inode_test_mask, vfsmount_mark, vfsmount_test_mask, data, -		 data_is, cookie, *event); +		 data_is, cookie);  	if (!inode_test_mask && !vfsmount_test_mask)  		return 0; -	if (group->ops->should_send_event(group, to_tell, inode_mark, -					  vfsmount_mark, mask, data, -					  data_is) == false) -		return 0; - -	if (!*event) { -		*event = fsnotify_create_event(to_tell, mask, data, -						data_is, file_name, -						cookie, GFP_KERNEL); -		if (!*event) -			return -ENOMEM; -	} -	return group->ops->handle_event(group, inode_mark, vfsmount_mark, *event); +	return group->ops->handle_event(group, to_tell, inode_mark, +					vfsmount_mark, mask, data, data_is, +					file_name, cookie);  }  /* @@ -205,7 +194,6 @@ int fsnotify(struct inode *to_tell, __u32 mask, void *data, int data_is,  	struct hlist_node *inode_node = NULL, *vfsmount_node = NULL;  	struct fsnotify_mark *inode_mark = NULL, *vfsmount_mark = NULL;  	struct fsnotify_group *inode_group, *vfsmount_group; -	struct fsnotify_event *event = NULL;  	struct mount *mnt;  	int idx, ret = 0;  	/* global tests shouldn't care about events on child only the specific event */ @@ -258,18 +246,18 @@ int fsnotify(struct inode *to_tell, __u32 mask, void *data, int data_is,  		if (inode_group > vfsmount_group) {  			/* handle inode */ -			ret = send_to_group(to_tell, inode_mark, NULL, mask, data, -					    data_is, cookie, file_name, &event); +			ret = send_to_group(to_tell, inode_mark, NULL, mask, +					    data, data_is, cookie, file_name);  			/* we didn't use the vfsmount_mark */  			vfsmount_group = NULL;  		} else if (vfsmount_group > inode_group) { -			ret = send_to_group(to_tell, NULL, vfsmount_mark, mask, data, -					    data_is, cookie, file_name, &event); +			ret = send_to_group(to_tell, NULL, vfsmount_mark, mask, +					    data, data_is, cookie, file_name);  			inode_group = NULL;  		} else {  			ret = send_to_group(to_tell, inode_mark, vfsmount_mark, -					    mask, data, data_is, cookie, file_name, -					    &event); +					    mask, data, data_is, cookie, +					    file_name);  		}  		if (ret && (mask & ALL_FSNOTIFY_PERM_EVENTS)) @@ -285,12 +273,6 @@ int fsnotify(struct inode *to_tell, __u32 mask, void *data, int data_is,  	ret = 0;  out:  	srcu_read_unlock(&fsnotify_mark_srcu, idx); -	/* -	 * fsnotify_create_event() took a reference so the event can't be cleaned -	 * up while we are still trying to add it to lists, drop that one. -	 */ -	if (event) -		fsnotify_put_event(event);  	return ret;  } diff --git a/fs/notify/group.c b/fs/notify/group.c index bd2625bd88b..ad199598045 100644 --- a/fs/notify/group.c +++ b/fs/notify/group.c @@ -55,6 +55,13 @@ void fsnotify_destroy_group(struct fsnotify_group *group)  	/* clear the notification queue of all events */  	fsnotify_flush_notify(group); +	/* +	 * Destroy overflow event (we cannot use fsnotify_destroy_event() as +	 * that deliberately ignores overflow events. +	 */ +	if (group->overflow_event) +		group->ops->free_event(group->overflow_event); +  	fsnotify_put_group(group);  } diff --git a/fs/notify/inotify/inotify.h b/fs/notify/inotify/inotify.h index b6642e4de4b..ed855ef6f07 100644 --- a/fs/notify/inotify/inotify.h +++ b/fs/notify/inotify/inotify.h @@ -2,11 +2,12 @@  #include <linux/inotify.h>  #include <linux/slab.h> /* struct kmem_cache */ -extern struct kmem_cache *event_priv_cachep; - -struct inotify_event_private_data { -	struct fsnotify_event_private_data fsnotify_event_priv_data; +struct inotify_event_info { +	struct fsnotify_event fse;  	int wd; +	u32 sync_cookie; +	int name_len; +	char name[];  };  struct inotify_inode_mark { @@ -14,8 +15,18 @@ struct inotify_inode_mark {  	int wd;  }; +static inline struct inotify_event_info *INOTIFY_E(struct fsnotify_event *fse) +{ +	return container_of(fse, struct inotify_event_info, fse); +} +  extern void inotify_ignored_and_remove_idr(struct fsnotify_mark *fsn_mark,  					   struct fsnotify_group *group); -extern void inotify_free_event_priv(struct fsnotify_event_private_data *event_priv); +extern int inotify_handle_event(struct fsnotify_group *group, +				struct inode *inode, +				struct fsnotify_mark *inode_mark, +				struct fsnotify_mark *vfsmount_mark, +				u32 mask, void *data, int data_type, +				const unsigned char *file_name, u32 cookie);  extern const struct fsnotify_ops inotify_fsnotify_ops; diff --git a/fs/notify/inotify/inotify_fsnotify.c b/fs/notify/inotify/inotify_fsnotify.c index 4216308b81b..43ab1e1a07a 100644 --- a/fs/notify/inotify/inotify_fsnotify.c +++ b/fs/notify/inotify/inotify_fsnotify.c @@ -34,107 +34,90 @@  #include "inotify.h"  /* - * Check if 2 events contain the same information.  We do not compare private data - * but at this moment that isn't a problem for any know fsnotify listeners. + * Check if 2 events contain the same information.   */ -static bool event_compare(struct fsnotify_event *old, struct fsnotify_event *new) +static bool event_compare(struct fsnotify_event *old_fsn, +			  struct fsnotify_event *new_fsn)  { -	if ((old->mask == new->mask) && -	    (old->to_tell == new->to_tell) && -	    (old->data_type == new->data_type) && -	    (old->name_len == new->name_len)) { -		switch (old->data_type) { -		case (FSNOTIFY_EVENT_INODE): -			/* remember, after old was put on the wait_q we aren't -			 * allowed to look at the inode any more, only thing -			 * left to check was if the file_name is the same */ -			if (!old->name_len || -			    !strcmp(old->file_name, new->file_name)) -				return true; -			break; -		case (FSNOTIFY_EVENT_PATH): -			if ((old->path.mnt == new->path.mnt) && -			    (old->path.dentry == new->path.dentry)) -				return true; -			break; -		case (FSNOTIFY_EVENT_NONE): -			if (old->mask & FS_Q_OVERFLOW) -				return true; -			else if (old->mask & FS_IN_IGNORED) -				return false; -			return true; -		}; -	} +	struct inotify_event_info *old, *new; + +	if (old_fsn->mask & FS_IN_IGNORED) +		return false; +	old = INOTIFY_E(old_fsn); +	new = INOTIFY_E(new_fsn); +	if ((old_fsn->mask == new_fsn->mask) && +	    (old_fsn->inode == new_fsn->inode) && +	    (old->name_len == new->name_len) && +	    (!old->name_len || !strcmp(old->name, new->name))) +		return true;  	return false;  } -static struct fsnotify_event *inotify_merge(struct list_head *list, -					    struct fsnotify_event *event) +static int inotify_merge(struct list_head *list, +			  struct fsnotify_event *event)  { -	struct fsnotify_event_holder *last_holder;  	struct fsnotify_event *last_event; -	/* and the list better be locked by something too */ -	spin_lock(&event->lock); - -	last_holder = list_entry(list->prev, struct fsnotify_event_holder, event_list); -	last_event = last_holder->event; -	if (event_compare(last_event, event)) -		fsnotify_get_event(last_event); -	else -		last_event = NULL; - -	spin_unlock(&event->lock); - -	return last_event; +	last_event = list_entry(list->prev, struct fsnotify_event, list); +	return event_compare(last_event, event);  } -static int inotify_handle_event(struct fsnotify_group *group, -				struct fsnotify_mark *inode_mark, -				struct fsnotify_mark *vfsmount_mark, -				struct fsnotify_event *event) +int inotify_handle_event(struct fsnotify_group *group, +			 struct inode *inode, +			 struct fsnotify_mark *inode_mark, +			 struct fsnotify_mark *vfsmount_mark, +			 u32 mask, void *data, int data_type, +			 const unsigned char *file_name, u32 cookie)  {  	struct inotify_inode_mark *i_mark; -	struct inode *to_tell; -	struct inotify_event_private_data *event_priv; -	struct fsnotify_event_private_data *fsn_event_priv; -	struct fsnotify_event *added_event; -	int wd, ret = 0; +	struct inotify_event_info *event; +	struct fsnotify_event *fsn_event; +	int ret; +	int len = 0; +	int alloc_len = sizeof(struct inotify_event_info);  	BUG_ON(vfsmount_mark); -	pr_debug("%s: group=%p event=%p to_tell=%p mask=%x\n", __func__, group, -		 event, event->to_tell, event->mask); +	if ((inode_mark->mask & FS_EXCL_UNLINK) && +	    (data_type == FSNOTIFY_EVENT_PATH)) { +		struct path *path = data; + +		if (d_unlinked(path->dentry)) +			return 0; +	} +	if (file_name) { +		len = strlen(file_name); +		alloc_len += len + 1; +	} -	to_tell = event->to_tell; +	pr_debug("%s: group=%p inode=%p mask=%x\n", __func__, group, inode, +		 mask);  	i_mark = container_of(inode_mark, struct inotify_inode_mark,  			      fsn_mark); -	wd = i_mark->wd; -	event_priv = kmem_cache_alloc(event_priv_cachep, GFP_KERNEL); -	if (unlikely(!event_priv)) +	event = kmalloc(alloc_len, GFP_KERNEL); +	if (unlikely(!event))  		return -ENOMEM; -	fsn_event_priv = &event_priv->fsnotify_event_priv_data; - -	fsnotify_get_group(group); -	fsn_event_priv->group = group; -	event_priv->wd = wd; - -	added_event = fsnotify_add_notify_event(group, event, fsn_event_priv, inotify_merge); -	if (added_event) { -		inotify_free_event_priv(fsn_event_priv); -		if (!IS_ERR(added_event)) -			fsnotify_put_event(added_event); -		else -			ret = PTR_ERR(added_event); +	fsn_event = &event->fse; +	fsnotify_init_event(fsn_event, inode, mask); +	event->wd = i_mark->wd; +	event->sync_cookie = cookie; +	event->name_len = len; +	if (len) +		strcpy(event->name, file_name); + +	ret = fsnotify_add_notify_event(group, fsn_event, inotify_merge); +	if (ret) { +		/* Our event wasn't used in the end. Free it. */ +		fsnotify_destroy_event(group, fsn_event);  	}  	if (inode_mark->mask & IN_ONESHOT)  		fsnotify_destroy_mark(inode_mark, group); -	return ret; +	return 0;  }  static void inotify_freeing_mark(struct fsnotify_mark *fsn_mark, struct fsnotify_group *group) @@ -142,22 +125,6 @@ static void inotify_freeing_mark(struct fsnotify_mark *fsn_mark, struct fsnotify  	inotify_ignored_and_remove_idr(fsn_mark, group);  } -static bool inotify_should_send_event(struct fsnotify_group *group, struct inode *inode, -				      struct fsnotify_mark *inode_mark, -				      struct fsnotify_mark *vfsmount_mark, -				      __u32 mask, void *data, int data_type) -{ -	if ((inode_mark->mask & FS_EXCL_UNLINK) && -	    (data_type == FSNOTIFY_EVENT_PATH)) { -		struct path *path = data; - -		if (d_unlinked(path->dentry)) -			return false; -	} - -	return true; -} -  /*   * This is NEVER supposed to be called.  Inotify marks should either have been   * removed from the idr when the watch was removed or in the @@ -202,22 +169,14 @@ static void inotify_free_group_priv(struct fsnotify_group *group)  	free_uid(group->inotify_data.user);  } -void inotify_free_event_priv(struct fsnotify_event_private_data *fsn_event_priv) +static void inotify_free_event(struct fsnotify_event *fsn_event)  { -	struct inotify_event_private_data *event_priv; - - -	event_priv = container_of(fsn_event_priv, struct inotify_event_private_data, -				  fsnotify_event_priv_data); - -	fsnotify_put_group(fsn_event_priv->group); -	kmem_cache_free(event_priv_cachep, event_priv); +	kfree(INOTIFY_E(fsn_event));  }  const struct fsnotify_ops inotify_fsnotify_ops = {  	.handle_event = inotify_handle_event, -	.should_send_event = inotify_should_send_event,  	.free_group_priv = inotify_free_group_priv, -	.free_event_priv = inotify_free_event_priv, +	.free_event = inotify_free_event,  	.freeing_mark = inotify_freeing_mark,  }; diff --git a/fs/notify/inotify/inotify_user.c b/fs/notify/inotify/inotify_user.c index 60f954a891a..cc423a30a0c 100644 --- a/fs/notify/inotify/inotify_user.c +++ b/fs/notify/inotify/inotify_user.c @@ -50,7 +50,6 @@ static int inotify_max_queued_events __read_mostly;  static int inotify_max_user_watches __read_mostly;  static struct kmem_cache *inotify_inode_mark_cachep __read_mostly; -struct kmem_cache *event_priv_cachep __read_mostly;  #ifdef CONFIG_SYSCTL @@ -58,7 +57,7 @@ struct kmem_cache *event_priv_cachep __read_mostly;  static int zero; -ctl_table inotify_table[] = { +struct ctl_table inotify_table[] = {  	{  		.procname	= "max_user_instances",  		.data		= &inotify_max_user_instances, @@ -124,6 +123,16 @@ static unsigned int inotify_poll(struct file *file, poll_table *wait)  	return ret;  } +static int round_event_name_len(struct fsnotify_event *fsn_event) +{ +	struct inotify_event_info *event; + +	event = INOTIFY_E(fsn_event); +	if (!event->name_len) +		return 0; +	return roundup(event->name_len + 1, sizeof(struct inotify_event)); +} +  /*   * Get an inotify_kernel_event if one exists and is small   * enough to fit in "count". Return an error pointer if @@ -144,9 +153,7 @@ static struct fsnotify_event *get_one_event(struct fsnotify_group *group,  	pr_debug("%s: group=%p event=%p\n", __func__, group, event); -	if (event->name_len) -		event_size += roundup(event->name_len + 1, event_size); - +	event_size += round_event_name_len(event);  	if (event_size > count)  		return ERR_PTR(-EINVAL); @@ -164,40 +171,27 @@ static struct fsnotify_event *get_one_event(struct fsnotify_group *group,   * buffer we had in "get_one_event()" above.   */  static ssize_t copy_event_to_user(struct fsnotify_group *group, -				  struct fsnotify_event *event, +				  struct fsnotify_event *fsn_event,  				  char __user *buf)  {  	struct inotify_event inotify_event; -	struct fsnotify_event_private_data *fsn_priv; -	struct inotify_event_private_data *priv; +	struct inotify_event_info *event;  	size_t event_size = sizeof(struct inotify_event); -	size_t name_len = 0; +	size_t name_len; +	size_t pad_name_len; -	pr_debug("%s: group=%p event=%p\n", __func__, group, event); - -	/* we get the inotify watch descriptor from the event private data */ -	spin_lock(&event->lock); -	fsn_priv = fsnotify_remove_priv_from_event(group, event); -	spin_unlock(&event->lock); - -	if (!fsn_priv) -		inotify_event.wd = -1; -	else { -		priv = container_of(fsn_priv, struct inotify_event_private_data, -				    fsnotify_event_priv_data); -		inotify_event.wd = priv->wd; -		inotify_free_event_priv(fsn_priv); -	} +	pr_debug("%s: group=%p event=%p\n", __func__, group, fsn_event); +	event = INOTIFY_E(fsn_event); +	name_len = event->name_len;  	/* -	 * round up event->name_len so it is a multiple of event_size +	 * round up name length so it is a multiple of event_size  	 * plus an extra byte for the terminating '\0'.  	 */ -	if (event->name_len) -		name_len = roundup(event->name_len + 1, event_size); -	inotify_event.len = name_len; - -	inotify_event.mask = inotify_mask_to_arg(event->mask); +	pad_name_len = round_event_name_len(fsn_event); +	inotify_event.len = pad_name_len; +	inotify_event.mask = inotify_mask_to_arg(fsn_event->mask); +	inotify_event.wd = event->wd;  	inotify_event.cookie = event->sync_cookie;  	/* send the main event */ @@ -209,20 +203,18 @@ static ssize_t copy_event_to_user(struct fsnotify_group *group,  	/*  	 * fsnotify only stores the pathname, so here we have to send the pathname  	 * and then pad that pathname out to a multiple of sizeof(inotify_event) -	 * with zeros.  I get my zeros from the nul_inotify_event. +	 * with zeros.  	 */ -	if (name_len) { -		unsigned int len_to_zero = name_len - event->name_len; +	if (pad_name_len) {  		/* copy the path name */ -		if (copy_to_user(buf, event->file_name, event->name_len)) +		if (copy_to_user(buf, event->name, name_len))  			return -EFAULT; -		buf += event->name_len; +		buf += name_len;  		/* fill userspace with 0's */ -		if (clear_user(buf, len_to_zero)) +		if (clear_user(buf, pad_name_len - name_len))  			return -EFAULT; -		buf += len_to_zero; -		event_size += name_len; +		event_size += pad_name_len;  	}  	return event_size; @@ -254,7 +246,7 @@ static ssize_t inotify_read(struct file *file, char __user *buf,  			if (IS_ERR(kevent))  				break;  			ret = copy_event_to_user(group, kevent, buf); -			fsnotify_put_event(kevent); +			fsnotify_destroy_event(group, kevent);  			if (ret < 0)  				break;  			buf += ret; @@ -297,8 +289,7 @@ static long inotify_ioctl(struct file *file, unsigned int cmd,  			  unsigned long arg)  {  	struct fsnotify_group *group; -	struct fsnotify_event_holder *holder; -	struct fsnotify_event *event; +	struct fsnotify_event *fsn_event;  	void __user *p;  	int ret = -ENOTTY;  	size_t send_len = 0; @@ -311,12 +302,10 @@ static long inotify_ioctl(struct file *file, unsigned int cmd,  	switch (cmd) {  	case FIONREAD:  		mutex_lock(&group->notification_mutex); -		list_for_each_entry(holder, &group->notification_list, event_list) { -			event = holder->event; +		list_for_each_entry(fsn_event, &group->notification_list, +				    list) {  			send_len += sizeof(struct inotify_event); -			if (event->name_len) -				send_len += roundup(event->name_len + 1, -						sizeof(struct inotify_event)); +			send_len += round_event_name_len(fsn_event);  		}  		mutex_unlock(&group->notification_mutex);  		ret = put_user(send_len, (int __user *) p); @@ -503,43 +492,12 @@ void inotify_ignored_and_remove_idr(struct fsnotify_mark *fsn_mark,  				    struct fsnotify_group *group)  {  	struct inotify_inode_mark *i_mark; -	struct fsnotify_event *ignored_event, *notify_event; -	struct inotify_event_private_data *event_priv; -	struct fsnotify_event_private_data *fsn_event_priv; -	int ret; - -	i_mark = container_of(fsn_mark, struct inotify_inode_mark, fsn_mark); - -	ignored_event = fsnotify_create_event(NULL, FS_IN_IGNORED, NULL, -					      FSNOTIFY_EVENT_NONE, NULL, 0, -					      GFP_NOFS); -	if (!ignored_event) -		goto skip_send_ignore; - -	event_priv = kmem_cache_alloc(event_priv_cachep, GFP_NOFS); -	if (unlikely(!event_priv)) -		goto skip_send_ignore; - -	fsn_event_priv = &event_priv->fsnotify_event_priv_data; - -	fsnotify_get_group(group); -	fsn_event_priv->group = group; -	event_priv->wd = i_mark->wd; - -	notify_event = fsnotify_add_notify_event(group, ignored_event, fsn_event_priv, NULL); -	if (notify_event) { -		if (IS_ERR(notify_event)) -			ret = PTR_ERR(notify_event); -		else -			fsnotify_put_event(notify_event); -		inotify_free_event_priv(fsn_event_priv); -	} -skip_send_ignore: -	/* matches the reference taken when the event was created */ -	if (ignored_event) -		fsnotify_put_event(ignored_event); +	/* Queue ignore event for the watch */ +	inotify_handle_event(group, NULL, fsn_mark, NULL, FS_IN_IGNORED, +			     NULL, FSNOTIFY_EVENT_NONE, NULL, 0); +	i_mark = container_of(fsn_mark, struct inotify_inode_mark, fsn_mark);  	/* remove this mark from the idr */  	inotify_remove_from_idr(group, i_mark); @@ -675,11 +633,23 @@ static int inotify_update_watch(struct fsnotify_group *group, struct inode *inod  static struct fsnotify_group *inotify_new_group(unsigned int max_events)  {  	struct fsnotify_group *group; +	struct inotify_event_info *oevent;  	group = fsnotify_alloc_group(&inotify_fsnotify_ops);  	if (IS_ERR(group))  		return group; +	oevent = kmalloc(sizeof(struct inotify_event_info), GFP_KERNEL); +	if (unlikely(!oevent)) { +		fsnotify_destroy_group(group); +		return ERR_PTR(-ENOMEM); +	} +	group->overflow_event = &oevent->fse; +	fsnotify_init_event(group->overflow_event, NULL, FS_Q_OVERFLOW); +	oevent->wd = -1; +	oevent->sync_cookie = 0; +	oevent->name_len = 0; +  	group->max_events = max_events;  	spin_lock_init(&group->inotify_data.idr_lock); @@ -836,7 +806,6 @@ static int __init inotify_user_setup(void)  	BUG_ON(hweight32(ALL_INOTIFY_BITS) != 21);  	inotify_inode_mark_cachep = KMEM_CACHE(inotify_inode_mark, SLAB_PANIC); -	event_priv_cachep = KMEM_CACHE(inotify_event_private_data, SLAB_PANIC);  	inotify_max_queued_events = 16384;  	inotify_max_user_instances = 128; diff --git a/fs/notify/mark.c b/fs/notify/mark.c index 923fe4a5f50..d90deaa08e7 100644 --- a/fs/notify/mark.c +++ b/fs/notify/mark.c @@ -340,7 +340,7 @@ void fsnotify_init_mark(struct fsnotify_mark *mark,  static int fsnotify_mark_destroy(void *ignored)  {  	struct fsnotify_mark *mark, *next; -	LIST_HEAD(private_destroy_list); +	struct list_head private_destroy_list;  	for (;;) {  		spin_lock(&destroy_lock); diff --git a/fs/notify/notification.c b/fs/notify/notification.c index 7b51b05f160..1e58402171a 100644 --- a/fs/notify/notification.c +++ b/fs/notify/notification.c @@ -48,15 +48,6 @@  #include <linux/fsnotify_backend.h>  #include "fsnotify.h" -static struct kmem_cache *fsnotify_event_cachep; -static struct kmem_cache *fsnotify_event_holder_cachep; -/* - * This is a magic event we send when the q is too full.  Since it doesn't - * hold real event information we just keep one system wide and use it any time - * it is needed.  It's refcnt is set 1 at kernel init time and will never - * get set to 0 so it will never get 'freed' - */ -static struct fsnotify_event *q_overflow_event;  static atomic_t fsnotify_sync_cookie = ATOMIC_INIT(0);  /** @@ -76,186 +67,82 @@ bool fsnotify_notify_queue_is_empty(struct fsnotify_group *group)  	return list_empty(&group->notification_list) ? true : false;  } -void fsnotify_get_event(struct fsnotify_event *event) +void fsnotify_destroy_event(struct fsnotify_group *group, +			    struct fsnotify_event *event)  { -	atomic_inc(&event->refcnt); -} - -void fsnotify_put_event(struct fsnotify_event *event) -{ -	if (!event) +	/* Overflow events are per-group and we don't want to free them */ +	if (!event || event->mask == FS_Q_OVERFLOW)  		return; -	if (atomic_dec_and_test(&event->refcnt)) { -		pr_debug("%s: event=%p\n", __func__, event); - -		if (event->data_type == FSNOTIFY_EVENT_PATH) -			path_put(&event->path); - -		BUG_ON(!list_empty(&event->private_data_list)); - -		kfree(event->file_name); -		put_pid(event->tgid); -		kmem_cache_free(fsnotify_event_cachep, event); -	} -} - -struct fsnotify_event_holder *fsnotify_alloc_event_holder(void) -{ -	return kmem_cache_alloc(fsnotify_event_holder_cachep, GFP_KERNEL); -} - -void fsnotify_destroy_event_holder(struct fsnotify_event_holder *holder) -{ -	if (holder) -		kmem_cache_free(fsnotify_event_holder_cachep, holder); -} - -/* - * Find the private data that the group previously attached to this event when - * the group added the event to the notification queue (fsnotify_add_notify_event) - */ -struct fsnotify_event_private_data *fsnotify_remove_priv_from_event(struct fsnotify_group *group, struct fsnotify_event *event) -{ -	struct fsnotify_event_private_data *lpriv; -	struct fsnotify_event_private_data *priv = NULL; - -	assert_spin_locked(&event->lock); - -	list_for_each_entry(lpriv, &event->private_data_list, event_list) { -		if (lpriv->group == group) { -			priv = lpriv; -			list_del(&priv->event_list); -			break; -		} -	} -	return priv; +	group->ops->free_event(event);  }  /*   * Add an event to the group notification queue.  The group can later pull this - * event off the queue to deal with.  If the event is successfully added to the - * group's notification queue, a reference is taken on event. + * event off the queue to deal with.  The function returns 0 if the event was + * added to the queue, 1 if the event was merged with some other queued event, + * 2 if the queue of events has overflown.   */ -struct fsnotify_event *fsnotify_add_notify_event(struct fsnotify_group *group, struct fsnotify_event *event, -						 struct fsnotify_event_private_data *priv, -						 struct fsnotify_event *(*merge)(struct list_head *, -										 struct fsnotify_event *)) +int fsnotify_add_notify_event(struct fsnotify_group *group, +			      struct fsnotify_event *event, +			      int (*merge)(struct list_head *, +					   struct fsnotify_event *))  { -	struct fsnotify_event *return_event = NULL; -	struct fsnotify_event_holder *holder = NULL; +	int ret = 0;  	struct list_head *list = &group->notification_list; -	pr_debug("%s: group=%p event=%p priv=%p\n", __func__, group, event, priv); - -	/* -	 * 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(&event->holder.event_list)) { -alloc_holder: -		holder = fsnotify_alloc_event_holder(); -		if (!holder) -			return ERR_PTR(-ENOMEM); -	} +	pr_debug("%s: group=%p event=%p\n", __func__, group, event);  	mutex_lock(&group->notification_mutex);  	if (group->q_len >= group->max_events) { -		event = q_overflow_event; - -		/* -		 * we need to return the overflow event -		 * which means we need a ref -		 */ -		fsnotify_get_event(event); -		return_event = event; - -		/* sorry, no private data on the overflow event */ -		priv = NULL; -	} - -	if (!list_empty(list) && merge) { -		struct fsnotify_event *tmp; - -		tmp = merge(list, event); -		if (tmp) { +		ret = 2; +		/* Queue overflow event only if it isn't already queued */ +		if (!list_empty(&group->overflow_event->list)) {  			mutex_unlock(&group->notification_mutex); - -			if (return_event) -				fsnotify_put_event(return_event); -			if (holder != &event->holder) -				fsnotify_destroy_event_holder(holder); -			return tmp; +			return ret;  		} +		event = group->overflow_event; +		goto queue;  	} -	spin_lock(&event->lock); - -	if (list_empty(&event->holder.event_list)) { -		if (unlikely(holder)) -			fsnotify_destroy_event_holder(holder); -		holder = &event->holder; -	} else if (unlikely(!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(&event->lock); -		mutex_unlock(&group->notification_mutex); - -		if (return_event) { -			fsnotify_put_event(return_event); -			return_event = NULL; +	if (!list_empty(list) && merge) { +		ret = merge(list, event); +		if (ret) { +			mutex_unlock(&group->notification_mutex); +			return ret;  		} - -		goto alloc_holder;  	} +queue:  	group->q_len++; -	holder->event = event; - -	fsnotify_get_event(event); -	list_add_tail(&holder->event_list, list); -	if (priv) -		list_add_tail(&priv->event_list, &event->private_data_list); -	spin_unlock(&event->lock); +	list_add_tail(&event->list, list);  	mutex_unlock(&group->notification_mutex);  	wake_up(&group->notification_waitq);  	kill_fasync(&group->fsn_fa, SIGIO, POLL_IN); -	return return_event; +	return ret;  }  /* - * Remove and return the first event from the notification list.  There is a - * reference held on this event since it was on the list.  It is the responsibility - * of the caller to drop this reference. + * Remove and return the first event from the notification list.  It is the + * responsibility of the caller to destroy the obtained event   */  struct fsnotify_event *fsnotify_remove_notify_event(struct fsnotify_group *group)  {  	struct fsnotify_event *event; -	struct fsnotify_event_holder *holder;  	BUG_ON(!mutex_is_locked(&group->notification_mutex));  	pr_debug("%s: group=%p\n", __func__, group); -	holder = list_first_entry(&group->notification_list, struct fsnotify_event_holder, event_list); - -	event = holder->event; - -	spin_lock(&event->lock); -	holder->event = NULL; -	list_del_init(&holder->event_list); -	spin_unlock(&event->lock); - -	/* event == holder means we are referenced through the in event holder */ -	if (holder != &event->holder) -		fsnotify_destroy_event_holder(holder); - +	event = list_first_entry(&group->notification_list, +				 struct fsnotify_event, list); +	/* +	 * We need to init list head for the case of overflow event so that +	 * check in fsnotify_add_notify_events() works +	 */ +	list_del_init(&event->list);  	group->q_len--;  	return event; @@ -266,15 +153,10 @@ struct fsnotify_event *fsnotify_remove_notify_event(struct fsnotify_group *group   */  struct fsnotify_event *fsnotify_peek_notify_event(struct fsnotify_group *group)  { -	struct fsnotify_event *event; -	struct fsnotify_event_holder *holder; -  	BUG_ON(!mutex_is_locked(&group->notification_mutex)); -	holder = list_first_entry(&group->notification_list, struct fsnotify_event_holder, event_list); -	event = holder->event; - -	return event; +	return list_first_entry(&group->notification_list, +				struct fsnotify_event, list);  }  /* @@ -284,181 +166,31 @@ struct fsnotify_event *fsnotify_peek_notify_event(struct fsnotify_group *group)  void fsnotify_flush_notify(struct fsnotify_group *group)  {  	struct fsnotify_event *event; -	struct fsnotify_event_private_data *priv;  	mutex_lock(&group->notification_mutex);  	while (!fsnotify_notify_queue_is_empty(group)) {  		event = fsnotify_remove_notify_event(group); -		/* if they don't implement free_event_priv they better not have attached any */ -		if (group->ops->free_event_priv) { -			spin_lock(&event->lock); -			priv = fsnotify_remove_priv_from_event(group, event); -			spin_unlock(&event->lock); -			if (priv) -				group->ops->free_event_priv(priv); -		} -		fsnotify_put_event(event); /* matches fsnotify_add_notify_event */ +		fsnotify_destroy_event(group, event);  	}  	mutex_unlock(&group->notification_mutex);  } -static void initialize_event(struct fsnotify_event *event) -{ -	INIT_LIST_HEAD(&event->holder.event_list); -	atomic_set(&event->refcnt, 1); - -	spin_lock_init(&event->lock); - -	INIT_LIST_HEAD(&event->private_data_list); -} - -/* - * Caller damn well better be holding whatever mutex is protecting the - * old_holder->event_list and the new_event must be a clean event which - * cannot be found anywhere else in the kernel. - */ -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 = &new_event->holder; - -	enum event_spinlock_class { -		SPINLOCK_OLD, -		SPINLOCK_NEW, -	}; - -	pr_debug("%s: old_event=%p new_event=%p\n", __func__, old_event, new_event); - -	/* -	 * if the new_event's embedded holder is in use someone -	 * screwed up and didn't give us a clean new event. -	 */ -	BUG_ON(!list_empty(&new_holder->event_list)); - -	spin_lock_nested(&old_event->lock, SPINLOCK_OLD); -	spin_lock_nested(&new_event->lock, SPINLOCK_NEW); - -	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; - -	event = kmem_cache_alloc(fsnotify_event_cachep, GFP_KERNEL); -	if (!event) -		return NULL; - -	pr_debug("%s: old_event=%p new_event=%p\n", __func__, old_event, event); - -	memcpy(event, old_event, sizeof(*event)); -	initialize_event(event); - -	if (event->name_len) { -		event->file_name = kstrdup(old_event->file_name, GFP_KERNEL); -		if (!event->file_name) { -			kmem_cache_free(fsnotify_event_cachep, event); -			return NULL; -		} -	} -	event->tgid = get_pid(old_event->tgid); -	if (event->data_type == FSNOTIFY_EVENT_PATH) -		path_get(&event->path); - -	return event; -} -  /*   * fsnotify_create_event - Allocate a new event which will be sent to each   * group's handle_event function if the group was interested in this   * particular event.   * - * @to_tell the inode which is supposed to receive the event (sometimes a + * @inode the inode which is supposed to receive the event (sometimes a   *	parent of the inode to which the event happened.   * @mask what actually happened.   * @data pointer to the object which was actually affected   * @data_type flag indication if the data is a file, path, inode, nothing...   * @name the filename, if available   */ -struct fsnotify_event *fsnotify_create_event(struct inode *to_tell, __u32 mask, void *data, -					     int data_type, const unsigned char *name, -					     u32 cookie, gfp_t gfp) +void fsnotify_init_event(struct fsnotify_event *event, struct inode *inode, +			 u32 mask)  { -	struct fsnotify_event *event; - -	event = kmem_cache_zalloc(fsnotify_event_cachep, gfp); -	if (!event) -		return NULL; - -	pr_debug("%s: event=%p to_tell=%p mask=%x data=%p data_type=%d\n", -		 __func__, event, to_tell, mask, data, data_type); - -	initialize_event(event); - -	if (name) { -		event->file_name = kstrdup(name, gfp); -		if (!event->file_name) { -			kmem_cache_free(fsnotify_event_cachep, event); -			return NULL; -		} -		event->name_len = strlen(event->file_name); -	} - -	event->tgid = get_pid(task_tgid(current)); -	event->sync_cookie = cookie; -	event->to_tell = to_tell; -	event->data_type = data_type; - -	switch (data_type) { -	case FSNOTIFY_EVENT_PATH: { -		struct path *path = data; -		event->path.dentry = path->dentry; -		event->path.mnt = path->mnt; -		path_get(&event->path); -		break; -	} -	case FSNOTIFY_EVENT_INODE: -		event->inode = data; -		break; -	case FSNOTIFY_EVENT_NONE: -		event->inode = NULL; -		event->path.dentry = NULL; -		event->path.mnt = NULL; -		break; -	default: -		BUG(); -	} - +	INIT_LIST_HEAD(&event->list); +	event->inode = inode;  	event->mask = mask; - -	return event; -} - -static __init int fsnotify_notification_init(void) -{ -	fsnotify_event_cachep = KMEM_CACHE(fsnotify_event, SLAB_PANIC); -	fsnotify_event_holder_cachep = KMEM_CACHE(fsnotify_event_holder, SLAB_PANIC); - -	q_overflow_event = fsnotify_create_event(NULL, FS_Q_OVERFLOW, NULL, -						 FSNOTIFY_EVENT_NONE, NULL, 0, -						 GFP_KERNEL); -	if (!q_overflow_event) -		panic("unable to allocate fsnotify q_overflow_event\n"); - -	return 0;  } -subsys_initcall(fsnotify_notification_init);  | 
