mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 02:30:34 +02:00 
			
		
		
		
	configfs: provide exclusion between IO and removals
Make sure that attribute methods are not called after the item has been removed from the tree. To do so, we * at the point of no return in removals, grab ->frag_sem exclusive and mark the fragment dead. * call the methods of attributes with ->frag_sem taken shared and only after having verified that the fragment is still alive. The main benefit is for method instances - they are guaranteed that the objects they are accessing *and* all ancestors are still there. Another win is that we don't need to bother with extra refcount on config_item when opening a file - the item will be alive for as long as it stays in the tree, and we won't touch it/attributes/any associated data after it's been removed from the tree. Signed-off-by: Al Viro <viro@zeniv.linux.org.uk> Signed-off-by: Christoph Hellwig <hch@lst.de>
This commit is contained in:
		
							parent
							
								
									47320fbe11
								
							
						
					
					
						commit
						b0841eefd9
					
				
					 2 changed files with 81 additions and 19 deletions
				
			
		| 
						 | 
					@ -1461,6 +1461,7 @@ static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
 | 
				
			||||||
	struct config_item *item;
 | 
						struct config_item *item;
 | 
				
			||||||
	struct configfs_subsystem *subsys;
 | 
						struct configfs_subsystem *subsys;
 | 
				
			||||||
	struct configfs_dirent *sd;
 | 
						struct configfs_dirent *sd;
 | 
				
			||||||
 | 
						struct configfs_fragment *frag;
 | 
				
			||||||
	struct module *subsys_owner = NULL, *dead_item_owner = NULL;
 | 
						struct module *subsys_owner = NULL, *dead_item_owner = NULL;
 | 
				
			||||||
	int ret;
 | 
						int ret;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1518,6 +1519,16 @@ static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	} while (ret == -EAGAIN);
 | 
						} while (ret == -EAGAIN);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						frag = sd->s_frag;
 | 
				
			||||||
 | 
						if (down_write_killable(&frag->frag_sem)) {
 | 
				
			||||||
 | 
							spin_lock(&configfs_dirent_lock);
 | 
				
			||||||
 | 
							configfs_detach_rollback(dentry);
 | 
				
			||||||
 | 
							spin_unlock(&configfs_dirent_lock);
 | 
				
			||||||
 | 
							return -EINTR;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						frag->frag_dead = true;
 | 
				
			||||||
 | 
						up_write(&frag->frag_sem);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/* Get a working ref for the duration of this function */
 | 
						/* Get a working ref for the duration of this function */
 | 
				
			||||||
	item = configfs_get_config_item(dentry);
 | 
						item = configfs_get_config_item(dentry);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1821,6 +1832,12 @@ void configfs_unregister_group(struct config_group *group)
 | 
				
			||||||
	struct configfs_subsystem *subsys = group->cg_subsys;
 | 
						struct configfs_subsystem *subsys = group->cg_subsys;
 | 
				
			||||||
	struct dentry *dentry = group->cg_item.ci_dentry;
 | 
						struct dentry *dentry = group->cg_item.ci_dentry;
 | 
				
			||||||
	struct dentry *parent = group->cg_item.ci_parent->ci_dentry;
 | 
						struct dentry *parent = group->cg_item.ci_parent->ci_dentry;
 | 
				
			||||||
 | 
						struct configfs_dirent *sd = dentry->d_fsdata;
 | 
				
			||||||
 | 
						struct configfs_fragment *frag = sd->s_frag;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						down_write(&frag->frag_sem);
 | 
				
			||||||
 | 
						frag->frag_dead = true;
 | 
				
			||||||
 | 
						up_write(&frag->frag_sem);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	inode_lock_nested(d_inode(parent), I_MUTEX_PARENT);
 | 
						inode_lock_nested(d_inode(parent), I_MUTEX_PARENT);
 | 
				
			||||||
	spin_lock(&configfs_dirent_lock);
 | 
						spin_lock(&configfs_dirent_lock);
 | 
				
			||||||
| 
						 | 
					@ -1947,12 +1964,18 @@ void configfs_unregister_subsystem(struct configfs_subsystem *subsys)
 | 
				
			||||||
	struct config_group *group = &subsys->su_group;
 | 
						struct config_group *group = &subsys->su_group;
 | 
				
			||||||
	struct dentry *dentry = group->cg_item.ci_dentry;
 | 
						struct dentry *dentry = group->cg_item.ci_dentry;
 | 
				
			||||||
	struct dentry *root = dentry->d_sb->s_root;
 | 
						struct dentry *root = dentry->d_sb->s_root;
 | 
				
			||||||
 | 
						struct configfs_dirent *sd = dentry->d_fsdata;
 | 
				
			||||||
 | 
						struct configfs_fragment *frag = sd->s_frag;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (dentry->d_parent != root) {
 | 
						if (dentry->d_parent != root) {
 | 
				
			||||||
		pr_err("Tried to unregister non-subsystem!\n");
 | 
							pr_err("Tried to unregister non-subsystem!\n");
 | 
				
			||||||
		return;
 | 
							return;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						down_write(&frag->frag_sem);
 | 
				
			||||||
 | 
						frag->frag_dead = true;
 | 
				
			||||||
 | 
						up_write(&frag->frag_sem);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	inode_lock_nested(d_inode(root),
 | 
						inode_lock_nested(d_inode(root),
 | 
				
			||||||
			  I_MUTEX_PARENT);
 | 
								  I_MUTEX_PARENT);
 | 
				
			||||||
	inode_lock_nested(d_inode(dentry), I_MUTEX_CHILD);
 | 
						inode_lock_nested(d_inode(dentry), I_MUTEX_CHILD);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -48,22 +48,32 @@ struct configfs_buffer {
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static inline struct configfs_fragment *to_frag(struct file *file)
 | 
				
			||||||
static int fill_read_buffer(struct configfs_buffer * buffer)
 | 
					 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	ssize_t count;
 | 
						struct configfs_dirent *sd = file->f_path.dentry->d_fsdata;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return sd->s_frag;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int fill_read_buffer(struct file *file, struct configfs_buffer *buffer)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct configfs_fragment *frag = to_frag(file);
 | 
				
			||||||
 | 
						ssize_t count = -ENOENT;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (!buffer->page)
 | 
						if (!buffer->page)
 | 
				
			||||||
		buffer->page = (char *) get_zeroed_page(GFP_KERNEL);
 | 
							buffer->page = (char *) get_zeroed_page(GFP_KERNEL);
 | 
				
			||||||
	if (!buffer->page)
 | 
						if (!buffer->page)
 | 
				
			||||||
		return -ENOMEM;
 | 
							return -ENOMEM;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						down_read(&frag->frag_sem);
 | 
				
			||||||
 | 
						if (!frag->frag_dead)
 | 
				
			||||||
		count = buffer->attr->show(buffer->item, buffer->page);
 | 
							count = buffer->attr->show(buffer->item, buffer->page);
 | 
				
			||||||
 | 
						up_read(&frag->frag_sem);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (count < 0)
 | 
						if (count < 0)
 | 
				
			||||||
		return count;
 | 
							return count;
 | 
				
			||||||
	if (WARN_ON_ONCE(count > (ssize_t)SIMPLE_ATTR_SIZE))
 | 
						if (WARN_ON_ONCE(count > (ssize_t)SIMPLE_ATTR_SIZE))
 | 
				
			||||||
		return -EIO;
 | 
							return -EIO;
 | 
				
			||||||
 | 
					 | 
				
			||||||
	buffer->needs_read_fill = 0;
 | 
						buffer->needs_read_fill = 0;
 | 
				
			||||||
	buffer->count = count;
 | 
						buffer->count = count;
 | 
				
			||||||
	return 0;
 | 
						return 0;
 | 
				
			||||||
| 
						 | 
					@ -96,7 +106,7 @@ configfs_read_file(struct file *file, char __user *buf, size_t count, loff_t *pp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	mutex_lock(&buffer->mutex);
 | 
						mutex_lock(&buffer->mutex);
 | 
				
			||||||
	if (buffer->needs_read_fill) {
 | 
						if (buffer->needs_read_fill) {
 | 
				
			||||||
		retval = fill_read_buffer(buffer);
 | 
							retval = fill_read_buffer(file, buffer);
 | 
				
			||||||
		if (retval)
 | 
							if (retval)
 | 
				
			||||||
			goto out;
 | 
								goto out;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -133,6 +143,7 @@ static ssize_t
 | 
				
			||||||
configfs_read_bin_file(struct file *file, char __user *buf,
 | 
					configfs_read_bin_file(struct file *file, char __user *buf,
 | 
				
			||||||
		       size_t count, loff_t *ppos)
 | 
							       size_t count, loff_t *ppos)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 | 
						struct configfs_fragment *frag = to_frag(file);
 | 
				
			||||||
	struct configfs_buffer *buffer = file->private_data;
 | 
						struct configfs_buffer *buffer = file->private_data;
 | 
				
			||||||
	ssize_t retval = 0;
 | 
						ssize_t retval = 0;
 | 
				
			||||||
	ssize_t len = min_t(size_t, count, PAGE_SIZE);
 | 
						ssize_t len = min_t(size_t, count, PAGE_SIZE);
 | 
				
			||||||
| 
						 | 
					@ -148,7 +159,12 @@ configfs_read_bin_file(struct file *file, char __user *buf,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (buffer->needs_read_fill) {
 | 
						if (buffer->needs_read_fill) {
 | 
				
			||||||
		/* perform first read with buf == NULL to get extent */
 | 
							/* perform first read with buf == NULL to get extent */
 | 
				
			||||||
 | 
							down_read(&frag->frag_sem);
 | 
				
			||||||
 | 
							if (!frag->frag_dead)
 | 
				
			||||||
			len = buffer->bin_attr->read(buffer->item, NULL, 0);
 | 
								len = buffer->bin_attr->read(buffer->item, NULL, 0);
 | 
				
			||||||
 | 
							else
 | 
				
			||||||
 | 
								len = -ENOENT;
 | 
				
			||||||
 | 
							up_read(&frag->frag_sem);
 | 
				
			||||||
		if (len <= 0) {
 | 
							if (len <= 0) {
 | 
				
			||||||
			retval = len;
 | 
								retval = len;
 | 
				
			||||||
			goto out;
 | 
								goto out;
 | 
				
			||||||
| 
						 | 
					@ -168,8 +184,13 @@ configfs_read_bin_file(struct file *file, char __user *buf,
 | 
				
			||||||
		buffer->bin_buffer_size = len;
 | 
							buffer->bin_buffer_size = len;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/* perform second read to fill buffer */
 | 
							/* perform second read to fill buffer */
 | 
				
			||||||
 | 
							down_read(&frag->frag_sem);
 | 
				
			||||||
 | 
							if (!frag->frag_dead)
 | 
				
			||||||
			len = buffer->bin_attr->read(buffer->item,
 | 
								len = buffer->bin_attr->read(buffer->item,
 | 
				
			||||||
						     buffer->bin_buffer, len);
 | 
											     buffer->bin_buffer, len);
 | 
				
			||||||
 | 
							else
 | 
				
			||||||
 | 
								len = -ENOENT;
 | 
				
			||||||
 | 
							up_read(&frag->frag_sem);
 | 
				
			||||||
		if (len < 0) {
 | 
							if (len < 0) {
 | 
				
			||||||
			retval = len;
 | 
								retval = len;
 | 
				
			||||||
			vfree(buffer->bin_buffer);
 | 
								vfree(buffer->bin_buffer);
 | 
				
			||||||
| 
						 | 
					@ -220,9 +241,16 @@ fill_write_buffer(struct configfs_buffer * buffer, const char __user * buf, size
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static int
 | 
					static int
 | 
				
			||||||
flush_write_buffer(struct configfs_buffer *buffer, size_t count)
 | 
					flush_write_buffer(struct file *file, struct configfs_buffer *buffer, size_t count)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	return buffer->attr->store(buffer->item, buffer->page, count);
 | 
						struct configfs_fragment *frag = to_frag(file);
 | 
				
			||||||
 | 
						int res = -ENOENT;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						down_read(&frag->frag_sem);
 | 
				
			||||||
 | 
						if (!frag->frag_dead)
 | 
				
			||||||
 | 
							res = buffer->attr->store(buffer->item, buffer->page, count);
 | 
				
			||||||
 | 
						up_read(&frag->frag_sem);
 | 
				
			||||||
 | 
						return res;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -252,7 +280,7 @@ configfs_write_file(struct file *file, const char __user *buf, size_t count, lof
 | 
				
			||||||
	mutex_lock(&buffer->mutex);
 | 
						mutex_lock(&buffer->mutex);
 | 
				
			||||||
	len = fill_write_buffer(buffer, buf, count);
 | 
						len = fill_write_buffer(buffer, buf, count);
 | 
				
			||||||
	if (len > 0)
 | 
						if (len > 0)
 | 
				
			||||||
		len = flush_write_buffer(buffer, len);
 | 
							len = flush_write_buffer(file, buffer, len);
 | 
				
			||||||
	if (len > 0)
 | 
						if (len > 0)
 | 
				
			||||||
		*ppos += len;
 | 
							*ppos += len;
 | 
				
			||||||
	mutex_unlock(&buffer->mutex);
 | 
						mutex_unlock(&buffer->mutex);
 | 
				
			||||||
| 
						 | 
					@ -328,6 +356,7 @@ configfs_write_bin_file(struct file *file, const char __user *buf,
 | 
				
			||||||
static int __configfs_open_file(struct inode *inode, struct file *file, int type)
 | 
					static int __configfs_open_file(struct inode *inode, struct file *file, int type)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	struct dentry *dentry = file->f_path.dentry;
 | 
						struct dentry *dentry = file->f_path.dentry;
 | 
				
			||||||
 | 
						struct configfs_fragment *frag = to_frag(file);
 | 
				
			||||||
	struct configfs_attribute *attr;
 | 
						struct configfs_attribute *attr;
 | 
				
			||||||
	struct configfs_buffer *buffer;
 | 
						struct configfs_buffer *buffer;
 | 
				
			||||||
	int error;
 | 
						int error;
 | 
				
			||||||
| 
						 | 
					@ -337,8 +366,13 @@ static int __configfs_open_file(struct inode *inode, struct file *file, int type
 | 
				
			||||||
	if (!buffer)
 | 
						if (!buffer)
 | 
				
			||||||
		goto out;
 | 
							goto out;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						error = -ENOENT;
 | 
				
			||||||
 | 
						down_read(&frag->frag_sem);
 | 
				
			||||||
 | 
						if (unlikely(frag->frag_dead))
 | 
				
			||||||
 | 
							goto out_free_buffer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	error = -EINVAL;
 | 
						error = -EINVAL;
 | 
				
			||||||
	buffer->item = configfs_get_config_item(dentry->d_parent);
 | 
						buffer->item = to_item(dentry->d_parent);
 | 
				
			||||||
	if (!buffer->item)
 | 
						if (!buffer->item)
 | 
				
			||||||
		goto out_free_buffer;
 | 
							goto out_free_buffer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -396,6 +430,7 @@ static int __configfs_open_file(struct inode *inode, struct file *file, int type
 | 
				
			||||||
	buffer->read_in_progress = false;
 | 
						buffer->read_in_progress = false;
 | 
				
			||||||
	buffer->write_in_progress = false;
 | 
						buffer->write_in_progress = false;
 | 
				
			||||||
	file->private_data = buffer;
 | 
						file->private_data = buffer;
 | 
				
			||||||
 | 
						up_read(&frag->frag_sem);
 | 
				
			||||||
	return 0;
 | 
						return 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
out_put_module:
 | 
					out_put_module:
 | 
				
			||||||
| 
						 | 
					@ -403,6 +438,7 @@ static int __configfs_open_file(struct inode *inode, struct file *file, int type
 | 
				
			||||||
out_put_item:
 | 
					out_put_item:
 | 
				
			||||||
	config_item_put(buffer->item);
 | 
						config_item_put(buffer->item);
 | 
				
			||||||
out_free_buffer:
 | 
					out_free_buffer:
 | 
				
			||||||
 | 
						up_read(&frag->frag_sem);
 | 
				
			||||||
	kfree(buffer);
 | 
						kfree(buffer);
 | 
				
			||||||
out:
 | 
					out:
 | 
				
			||||||
	return error;
 | 
						return error;
 | 
				
			||||||
| 
						 | 
					@ -412,8 +448,6 @@ static int configfs_release(struct inode *inode, struct file *filp)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	struct configfs_buffer *buffer = filp->private_data;
 | 
						struct configfs_buffer *buffer = filp->private_data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (buffer->item)
 | 
					 | 
				
			||||||
		config_item_put(buffer->item);
 | 
					 | 
				
			||||||
	module_put(buffer->owner);
 | 
						module_put(buffer->owner);
 | 
				
			||||||
	if (buffer->page)
 | 
						if (buffer->page)
 | 
				
			||||||
		free_page((unsigned long)buffer->page);
 | 
							free_page((unsigned long)buffer->page);
 | 
				
			||||||
| 
						 | 
					@ -439,12 +473,17 @@ static int configfs_release_bin_file(struct inode *inode, struct file *file)
 | 
				
			||||||
	buffer->read_in_progress = false;
 | 
						buffer->read_in_progress = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (buffer->write_in_progress) {
 | 
						if (buffer->write_in_progress) {
 | 
				
			||||||
 | 
							struct configfs_fragment *frag = to_frag(file);
 | 
				
			||||||
		buffer->write_in_progress = false;
 | 
							buffer->write_in_progress = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							down_read(&frag->frag_sem);
 | 
				
			||||||
 | 
							if (!frag->frag_dead) {
 | 
				
			||||||
			/* result of ->release() is ignored */
 | 
								/* result of ->release() is ignored */
 | 
				
			||||||
		buffer->bin_attr->write(buffer->item, buffer->bin_buffer,
 | 
								buffer->bin_attr->write(buffer->item,
 | 
				
			||||||
 | 
										buffer->bin_buffer,
 | 
				
			||||||
					buffer->bin_buffer_size);
 | 
										buffer->bin_buffer_size);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							up_read(&frag->frag_sem);
 | 
				
			||||||
		/* vfree on NULL is safe */
 | 
							/* vfree on NULL is safe */
 | 
				
			||||||
		vfree(buffer->bin_buffer);
 | 
							vfree(buffer->bin_buffer);
 | 
				
			||||||
		buffer->bin_buffer = NULL;
 | 
							buffer->bin_buffer = NULL;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue