configfs: provide exclusion between IO and removals
commit b0841eefd9693827afb9888235e26ddd098f9cef upstream. 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> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
parent
25c118d8d1
commit
0dfc45be87
2 changed files with 81 additions and 19 deletions
|
@ -1474,6 +1474,7 @@ static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
|
|||
struct config_item *item;
|
||||
struct configfs_subsystem *subsys;
|
||||
struct configfs_dirent *sd;
|
||||
struct configfs_fragment *frag;
|
||||
struct module *subsys_owner = NULL, *dead_item_owner = NULL;
|
||||
int ret;
|
||||
|
||||
|
@ -1531,6 +1532,16 @@ static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
|
|||
}
|
||||
} 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 */
|
||||
item = configfs_get_config_item(dentry);
|
||||
|
||||
|
@ -1832,6 +1843,12 @@ void configfs_unregister_group(struct config_group *group)
|
|||
struct configfs_subsystem *subsys = group->cg_subsys;
|
||||
struct dentry *dentry = group->cg_item.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);
|
||||
spin_lock(&configfs_dirent_lock);
|
||||
|
@ -1957,12 +1974,18 @@ void configfs_unregister_subsystem(struct configfs_subsystem *subsys)
|
|||
struct config_group *group = &subsys->su_group;
|
||||
struct dentry *dentry = group->cg_item.ci_dentry;
|
||||
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) {
|
||||
pr_err("Tried to unregister non-subsystem!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
down_write(&frag->frag_sem);
|
||||
frag->frag_dead = true;
|
||||
up_write(&frag->frag_sem);
|
||||
|
||||
inode_lock_nested(d_inode(root),
|
||||
I_MUTEX_PARENT);
|
||||
inode_lock_nested(d_inode(dentry), I_MUTEX_CHILD);
|
||||
|
|
|
@ -62,22 +62,32 @@ struct configfs_buffer {
|
|||
};
|
||||
};
|
||||
|
||||
|
||||
static int fill_read_buffer(struct configfs_buffer * buffer)
|
||||
static inline struct configfs_fragment *to_frag(struct file *file)
|
||||
{
|
||||
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)
|
||||
buffer->page = (char *) get_zeroed_page(GFP_KERNEL);
|
||||
if (!buffer->page)
|
||||
return -ENOMEM;
|
||||
|
||||
count = buffer->attr->show(buffer->item, buffer->page);
|
||||
down_read(&frag->frag_sem);
|
||||
if (!frag->frag_dead)
|
||||
count = buffer->attr->show(buffer->item, buffer->page);
|
||||
up_read(&frag->frag_sem);
|
||||
|
||||
if (count < 0)
|
||||
return count;
|
||||
if (WARN_ON_ONCE(count > (ssize_t)SIMPLE_ATTR_SIZE))
|
||||
return -EIO;
|
||||
|
||||
buffer->needs_read_fill = 0;
|
||||
buffer->count = count;
|
||||
return 0;
|
||||
|
@ -110,7 +120,7 @@ configfs_read_file(struct file *file, char __user *buf, size_t count, loff_t *pp
|
|||
|
||||
mutex_lock(&buffer->mutex);
|
||||
if (buffer->needs_read_fill) {
|
||||
retval = fill_read_buffer(buffer);
|
||||
retval = fill_read_buffer(file, buffer);
|
||||
if (retval)
|
||||
goto out;
|
||||
}
|
||||
|
@ -147,6 +157,7 @@ static ssize_t
|
|||
configfs_read_bin_file(struct file *file, char __user *buf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
struct configfs_fragment *frag = to_frag(file);
|
||||
struct configfs_buffer *buffer = file->private_data;
|
||||
ssize_t retval = 0;
|
||||
ssize_t len = min_t(size_t, count, PAGE_SIZE);
|
||||
|
@ -162,7 +173,12 @@ configfs_read_bin_file(struct file *file, char __user *buf,
|
|||
|
||||
if (buffer->needs_read_fill) {
|
||||
/* perform first read with buf == NULL to get extent */
|
||||
len = buffer->bin_attr->read(buffer->item, NULL, 0);
|
||||
down_read(&frag->frag_sem);
|
||||
if (!frag->frag_dead)
|
||||
len = buffer->bin_attr->read(buffer->item, NULL, 0);
|
||||
else
|
||||
len = -ENOENT;
|
||||
up_read(&frag->frag_sem);
|
||||
if (len <= 0) {
|
||||
retval = len;
|
||||
goto out;
|
||||
|
@ -182,8 +198,13 @@ configfs_read_bin_file(struct file *file, char __user *buf,
|
|||
buffer->bin_buffer_size = len;
|
||||
|
||||
/* perform second read to fill buffer */
|
||||
len = buffer->bin_attr->read(buffer->item,
|
||||
buffer->bin_buffer, len);
|
||||
down_read(&frag->frag_sem);
|
||||
if (!frag->frag_dead)
|
||||
len = buffer->bin_attr->read(buffer->item,
|
||||
buffer->bin_buffer, len);
|
||||
else
|
||||
len = -ENOENT;
|
||||
up_read(&frag->frag_sem);
|
||||
if (len < 0) {
|
||||
retval = len;
|
||||
vfree(buffer->bin_buffer);
|
||||
|
@ -234,9 +255,16 @@ fill_write_buffer(struct configfs_buffer * buffer, const char __user * buf, size
|
|||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
@ -266,7 +294,7 @@ configfs_write_file(struct file *file, const char __user *buf, size_t count, lof
|
|||
mutex_lock(&buffer->mutex);
|
||||
len = fill_write_buffer(buffer, buf, count);
|
||||
if (len > 0)
|
||||
len = flush_write_buffer(buffer, len);
|
||||
len = flush_write_buffer(file, buffer, len);
|
||||
if (len > 0)
|
||||
*ppos += len;
|
||||
mutex_unlock(&buffer->mutex);
|
||||
|
@ -342,6 +370,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)
|
||||
{
|
||||
struct dentry *dentry = file->f_path.dentry;
|
||||
struct configfs_fragment *frag = to_frag(file);
|
||||
struct configfs_attribute *attr;
|
||||
struct configfs_buffer *buffer;
|
||||
int error;
|
||||
|
@ -351,8 +380,13 @@ static int __configfs_open_file(struct inode *inode, struct file *file, int type
|
|||
if (!buffer)
|
||||
goto out;
|
||||
|
||||
error = -ENOENT;
|
||||
down_read(&frag->frag_sem);
|
||||
if (unlikely(frag->frag_dead))
|
||||
goto out_free_buffer;
|
||||
|
||||
error = -EINVAL;
|
||||
buffer->item = configfs_get_config_item(dentry->d_parent);
|
||||
buffer->item = to_item(dentry->d_parent);
|
||||
if (!buffer->item)
|
||||
goto out_free_buffer;
|
||||
|
||||
|
@ -410,6 +444,7 @@ static int __configfs_open_file(struct inode *inode, struct file *file, int type
|
|||
buffer->read_in_progress = false;
|
||||
buffer->write_in_progress = false;
|
||||
file->private_data = buffer;
|
||||
up_read(&frag->frag_sem);
|
||||
return 0;
|
||||
|
||||
out_put_module:
|
||||
|
@ -417,6 +452,7 @@ static int __configfs_open_file(struct inode *inode, struct file *file, int type
|
|||
out_put_item:
|
||||
config_item_put(buffer->item);
|
||||
out_free_buffer:
|
||||
up_read(&frag->frag_sem);
|
||||
kfree(buffer);
|
||||
out:
|
||||
return error;
|
||||
|
@ -426,8 +462,6 @@ static int configfs_release(struct inode *inode, struct file *filp)
|
|||
{
|
||||
struct configfs_buffer *buffer = filp->private_data;
|
||||
|
||||
if (buffer->item)
|
||||
config_item_put(buffer->item);
|
||||
module_put(buffer->owner);
|
||||
if (buffer->page)
|
||||
free_page((unsigned long)buffer->page);
|
||||
|
@ -453,12 +487,17 @@ static int configfs_release_bin_file(struct inode *inode, struct file *file)
|
|||
buffer->read_in_progress = false;
|
||||
|
||||
if (buffer->write_in_progress) {
|
||||
struct configfs_fragment *frag = to_frag(file);
|
||||
buffer->write_in_progress = false;
|
||||
|
||||
/* result of ->release() is ignored */
|
||||
buffer->bin_attr->write(buffer->item, buffer->bin_buffer,
|
||||
buffer->bin_buffer_size);
|
||||
|
||||
down_read(&frag->frag_sem);
|
||||
if (!frag->frag_dead) {
|
||||
/* result of ->release() is ignored */
|
||||
buffer->bin_attr->write(buffer->item,
|
||||
buffer->bin_buffer,
|
||||
buffer->bin_buffer_size);
|
||||
}
|
||||
up_read(&frag->frag_sem);
|
||||
/* vfree on NULL is safe */
|
||||
vfree(buffer->bin_buffer);
|
||||
buffer->bin_buffer = NULL;
|
||||
|
|
Loading…
Reference in a new issue