Commit b0841eef authored by Al Viro's avatar Al Viro Committed by Christoph Hellwig

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: default avatarAl Viro <viro@zeniv.linux.org.uk>
Signed-off-by: default avatarChristoph Hellwig <hch@lst.de>
parent 47320fbe
...@@ -1461,6 +1461,7 @@ static int configfs_rmdir(struct inode *dir, struct dentry *dentry) ...@@ -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) ...@@ -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) ...@@ -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) ...@@ -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 { ...@@ -48,22 +48,32 @@ struct configfs_buffer {
}; };
}; };
static inline struct configfs_fragment *to_frag(struct file *file)
{
struct configfs_dirent *sd = file->f_path.dentry->d_fsdata;
return sd->s_frag;
}
static int fill_read_buffer(struct configfs_buffer * buffer) static int fill_read_buffer(struct file *file, struct configfs_buffer *buffer)
{ {
ssize_t count; 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 ...@@ -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 ...@@ -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, ...@@ -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, ...@@ -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 ...@@ -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 ...@@ -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, ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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) ...@@ -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) ...@@ -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;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment