summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--fs/sysfs/bin.c95
-rw-r--r--fs/sysfs/dir.c28
-rw-r--r--fs/sysfs/file.c130
-rw-r--r--fs/sysfs/inode.c8
-rw-r--r--fs/sysfs/sysfs.h123
5 files changed, 271 insertions, 113 deletions
diff --git a/fs/sysfs/bin.c b/fs/sysfs/bin.c
index 5dc47fe5de5e..618b8aea6a7b 100644
--- a/fs/sysfs/bin.c
+++ b/fs/sysfs/bin.c
@@ -23,6 +23,7 @@
struct bin_buffer {
struct mutex mutex;
void *buffer;
+ int mmapped;
};
static int
@@ -30,12 +31,20 @@ fill_read(struct dentry *dentry, char *buffer, loff_t off, size_t count)
{
struct sysfs_dirent *attr_sd = dentry->d_fsdata;
struct bin_attribute *attr = attr_sd->s_elem.bin_attr.bin_attr;
- struct kobject * kobj = to_kobj(dentry->d_parent);
+ struct kobject *kobj = attr_sd->s_parent->s_elem.dir.kobj;
+ int rc;
+
+ /* need attr_sd for attr, its parent for kobj */
+ if (!sysfs_get_active_two(attr_sd))
+ return -ENODEV;
- if (!attr->read)
- return -EIO;
+ rc = -EIO;
+ if (attr->read)
+ rc = attr->read(kobj, buffer, off, count);
- return attr->read(kobj, buffer, off, count);
+ sysfs_put_active_two(attr_sd);
+
+ return rc;
}
static ssize_t
@@ -79,12 +88,20 @@ flush_write(struct dentry *dentry, char *buffer, loff_t offset, size_t count)
{
struct sysfs_dirent *attr_sd = dentry->d_fsdata;
struct bin_attribute *attr = attr_sd->s_elem.bin_attr.bin_attr;
- struct kobject *kobj = to_kobj(dentry->d_parent);
+ struct kobject *kobj = attr_sd->s_parent->s_elem.dir.kobj;
+ int rc;
+
+ /* need attr_sd for attr, its parent for kobj */
+ if (!sysfs_get_active_two(attr_sd))
+ return -ENODEV;
- if (!attr->write)
- return -EIO;
+ rc = -EIO;
+ if (attr->write)
+ rc = attr->write(kobj, buffer, offset, count);
- return attr->write(kobj, buffer, offset, count);
+ sysfs_put_active_two(attr_sd);
+
+ return rc;
}
static ssize_t write(struct file *file, const char __user *userbuf,
@@ -124,14 +141,24 @@ static int mmap(struct file *file, struct vm_area_struct *vma)
struct bin_buffer *bb = file->private_data;
struct sysfs_dirent *attr_sd = file->f_path.dentry->d_fsdata;
struct bin_attribute *attr = attr_sd->s_elem.bin_attr.bin_attr;
- struct kobject *kobj = to_kobj(file->f_path.dentry->d_parent);
+ struct kobject *kobj = attr_sd->s_parent->s_elem.dir.kobj;
int rc;
- if (!attr->mmap)
- return -EINVAL;
-
mutex_lock(&bb->mutex);
- rc = attr->mmap(kobj, attr, vma);
+
+ /* need attr_sd for attr, its parent for kobj */
+ if (!sysfs_get_active_two(attr_sd))
+ return -ENODEV;
+
+ rc = -EINVAL;
+ if (attr->mmap)
+ rc = attr->mmap(kobj, attr, vma);
+
+ if (rc == 0 && !bb->mmapped)
+ bb->mmapped = 1;
+ else
+ sysfs_put_active_two(attr_sd);
+
mutex_unlock(&bb->mutex);
return rc;
@@ -139,58 +166,60 @@ static int mmap(struct file *file, struct vm_area_struct *vma)
static int open(struct inode * inode, struct file * file)
{
- struct kobject *kobj = sysfs_get_kobject(file->f_path.dentry->d_parent);
struct sysfs_dirent *attr_sd = file->f_path.dentry->d_fsdata;
struct bin_attribute *attr = attr_sd->s_elem.bin_attr.bin_attr;
struct bin_buffer *bb = NULL;
- int error = -EINVAL;
+ int error;
- if (!kobj || !attr)
- goto Done;
+ /* need attr_sd for attr */
+ if (!sysfs_get_active(attr_sd))
+ return -ENODEV;
- /* Grab the module reference for this attribute if we have one */
+ /* Grab the module reference for this attribute */
error = -ENODEV;
- if (!try_module_get(attr->attr.owner))
- goto Done;
+ if (!try_module_get(attr->attr.owner))
+ goto err_sput;
error = -EACCES;
if ((file->f_mode & FMODE_WRITE) && !(attr->write || attr->mmap))
- goto Error;
+ goto err_mput;
if ((file->f_mode & FMODE_READ) && !(attr->read || attr->mmap))
- goto Error;
+ goto err_mput;
error = -ENOMEM;
bb = kzalloc(sizeof(*bb), GFP_KERNEL);
if (!bb)
- goto Error;
+ goto err_mput;
bb->buffer = kmalloc(PAGE_SIZE, GFP_KERNEL);
if (!bb->buffer)
- goto Error;
+ goto err_mput;
mutex_init(&bb->mutex);
file->private_data = bb;
- error = 0;
- goto Done;
+ /* open succeeded, put active reference and pin attr_sd */
+ sysfs_put_active(attr_sd);
+ sysfs_get(attr_sd);
+ return 0;
- Error:
- kfree(bb);
+ err_mput:
module_put(attr->attr.owner);
- Done:
- if (error)
- kobject_put(kobj);
+ err_sput:
+ sysfs_put_active(attr_sd);
+ kfree(bb);
return error;
}
static int release(struct inode * inode, struct file * file)
{
- struct kobject * kobj = to_kobj(file->f_path.dentry->d_parent);
struct sysfs_dirent *attr_sd = file->f_path.dentry->d_fsdata;
struct bin_attribute *attr = attr_sd->s_elem.bin_attr.bin_attr;
struct bin_buffer *bb = file->private_data;
- kobject_put(kobj);
+ if (bb->mmapped)
+ sysfs_put_active_two(attr_sd);
+ sysfs_put(attr_sd);
module_put(attr->attr.owner);
kfree(bb->buffer);
kfree(bb);
diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c
index 2a94dc36d166..e0d377aaf2cc 100644
--- a/fs/sysfs/dir.c
+++ b/fs/sysfs/dir.c
@@ -53,6 +53,19 @@ void release_sysfs_dirent(struct sysfs_dirent * sd)
repeat:
parent_sd = sd->s_parent;
+ /* If @sd is being released after deletion, s_active is write
+ * locked. If @sd is cursor for directory walk or being
+ * released prematurely, s_active has no reader or writer.
+ *
+ * sysfs_deactivate() lies to lockdep that s_active is
+ * unlocked immediately. Lie one more time to cover the
+ * previous lie.
+ */
+ if (!down_write_trylock(&sd->s_active))
+ rwsem_acquire(&sd->s_active.dep_map,
+ SYSFS_S_ACTIVE_DEACTIVATE, 0, _RET_IP_);
+ up_write(&sd->s_active);
+
if (sd->s_type & SYSFS_KOBJ_LINK)
sysfs_put(sd->s_elem.symlink.target_sd);
if (sd->s_type & SYSFS_COPY_NAME)
@@ -113,6 +126,7 @@ struct sysfs_dirent *sysfs_new_dirent(const char *name, umode_t mode, int type)
atomic_set(&sd->s_count, 1);
atomic_set(&sd->s_event, 1);
+ init_rwsem(&sd->s_active);
INIT_LIST_HEAD(&sd->s_children);
INIT_LIST_HEAD(&sd->s_sibling);
@@ -371,7 +385,6 @@ static void remove_dir(struct dentry * d)
d_delete(d);
sd = d->d_fsdata;
list_del_init(&sd->s_sibling);
- sysfs_put(sd);
if (d->d_inode)
simple_rmdir(parent->d_inode,d);
@@ -380,6 +393,9 @@ static void remove_dir(struct dentry * d)
mutex_unlock(&parent->d_inode->i_mutex);
dput(parent);
+
+ sysfs_deactivate(sd);
+ sysfs_put(sd);
}
void sysfs_remove_subdir(struct dentry * d)
@@ -390,6 +406,7 @@ void sysfs_remove_subdir(struct dentry * d)
static void __sysfs_remove_dir(struct dentry *dentry)
{
+ LIST_HEAD(removed);
struct sysfs_dirent * parent_sd;
struct sysfs_dirent * sd, * tmp;
@@ -403,12 +420,17 @@ static void __sysfs_remove_dir(struct dentry *dentry)
list_for_each_entry_safe(sd, tmp, &parent_sd->s_children, s_sibling) {
if (!sd->s_type || !(sd->s_type & SYSFS_NOT_PINNED))
continue;
- list_del_init(&sd->s_sibling);
+ list_move(&sd->s_sibling, &removed);
sysfs_drop_dentry(sd, dentry);
- sysfs_put(sd);
}
mutex_unlock(&dentry->d_inode->i_mutex);
+ list_for_each_entry_safe(sd, tmp, &removed, s_sibling) {
+ list_del_init(&sd->s_sibling);
+ sysfs_deactivate(sd);
+ sysfs_put(sd);
+ }
+
remove_dir(dentry);
/**
* Drop reference from dget() on entrance.
diff --git a/fs/sysfs/file.c b/fs/sysfs/file.c
index 04f6b0ebc889..310430baf572 100644
--- a/fs/sysfs/file.c
+++ b/fs/sysfs/file.c
@@ -87,8 +87,8 @@ remove_from_collection(struct sysfs_buffer *buffer, struct inode *node)
*/
static int fill_read_buffer(struct dentry * dentry, struct sysfs_buffer * buffer)
{
- struct sysfs_dirent * sd = dentry->d_fsdata;
- struct kobject * kobj = to_kobj(dentry->d_parent);
+ struct sysfs_dirent *attr_sd = dentry->d_fsdata;
+ struct kobject *kobj = attr_sd->s_parent->s_elem.dir.kobj;
struct sysfs_ops * ops = buffer->ops;
int ret = 0;
ssize_t count;
@@ -98,8 +98,15 @@ static int fill_read_buffer(struct dentry * dentry, struct sysfs_buffer * buffer
if (!buffer->page)
return -ENOMEM;
- buffer->event = atomic_read(&sd->s_event);
- count = ops->show(kobj, sd->s_elem.attr.attr, buffer->page);
+ /* need attr_sd for attr and ops, its parent for kobj */
+ if (!sysfs_get_active_two(attr_sd))
+ return -ENODEV;
+
+ buffer->event = atomic_read(&attr_sd->s_event);
+ count = ops->show(kobj, attr_sd->s_elem.attr.attr, buffer->page);
+
+ sysfs_put_active_two(attr_sd);
+
BUG_ON(count > (ssize_t)PAGE_SIZE);
if (count >= 0) {
buffer->needs_read_fill = 0;
@@ -195,14 +202,23 @@ fill_write_buffer(struct sysfs_buffer * buffer, const char __user * buf, size_t
* passing the buffer that we acquired in fill_write_buffer().
*/
-static int
+static int
flush_write_buffer(struct dentry * dentry, struct sysfs_buffer * buffer, size_t count)
{
struct sysfs_dirent *attr_sd = dentry->d_fsdata;
- struct kobject * kobj = to_kobj(dentry->d_parent);
+ struct kobject *kobj = attr_sd->s_parent->s_elem.dir.kobj;
struct sysfs_ops * ops = buffer->ops;
+ int rc;
+
+ /* need attr_sd for attr and ops, its parent for kobj */
+ if (!sysfs_get_active_two(attr_sd))
+ return -ENODEV;
+
+ rc = ops->store(kobj, attr_sd->s_elem.attr.attr, buffer->page, count);
+
+ sysfs_put_active_two(attr_sd);
- return ops->store(kobj, attr_sd->s_elem.attr.attr, buffer->page, count);
+ return rc;
}
@@ -246,22 +262,22 @@ out:
static int sysfs_open_file(struct inode *inode, struct file *file)
{
- struct kobject *kobj = sysfs_get_kobject(file->f_path.dentry->d_parent);
struct sysfs_dirent *attr_sd = file->f_path.dentry->d_fsdata;
struct attribute *attr = attr_sd->s_elem.attr.attr;
+ struct kobject *kobj = attr_sd->s_parent->s_elem.dir.kobj;
struct sysfs_buffer_collection *set;
struct sysfs_buffer * buffer;
struct sysfs_ops * ops = NULL;
- int error = 0;
+ int error;
- if (!kobj || !attr)
- goto Einval;
+ /* need attr_sd for attr and ops, its parent for kobj */
+ if (!sysfs_get_active_two(attr_sd))
+ return -ENODEV;
- /* Grab the module reference for this attribute if we have one */
- if (!try_module_get(attr->owner)) {
- error = -ENODEV;
- goto Done;
- }
+ /* Grab the module reference for this attribute */
+ error = -ENODEV;
+ if (!try_module_get(attr->owner))
+ goto err_sput;
/* if the kobject has no ktype, then we assume that it is a subsystem
* itself, and use ops for it.
@@ -276,30 +292,30 @@ static int sysfs_open_file(struct inode *inode, struct file *file)
/* No sysfs operations, either from having no subsystem,
* or the subsystem have no operations.
*/
+ error = -EACCES;
if (!ops)
- goto Eaccess;
+ goto err_mput;
/* make sure we have a collection to add our buffers to */
mutex_lock(&inode->i_mutex);
if (!(set = inode->i_private)) {
- if (!(set = inode->i_private = kmalloc(sizeof(struct sysfs_buffer_collection), GFP_KERNEL))) {
- error = -ENOMEM;
- goto Done;
- } else {
+ error = -ENOMEM;
+ if (!(set = inode->i_private = kmalloc(sizeof(struct sysfs_buffer_collection), GFP_KERNEL)))
+ goto err_mput;
+ else
INIT_LIST_HEAD(&set->associates);
- }
}
mutex_unlock(&inode->i_mutex);
+ error = -EACCES;
+
/* File needs write support.
* The inode's perms must say it's ok,
* and we must have a store method.
*/
if (file->f_mode & FMODE_WRITE) {
-
if (!(inode->i_mode & S_IWUGO) || !ops->store)
- goto Eaccess;
-
+ goto err_mput;
}
/* File needs read support.
@@ -308,46 +324,45 @@ static int sysfs_open_file(struct inode *inode, struct file *file)
*/
if (file->f_mode & FMODE_READ) {
if (!(inode->i_mode & S_IRUGO) || !ops->show)
- goto Eaccess;
+ goto err_mput;
}
/* No error? Great, allocate a buffer for the file, and store it
* it in file->private_data for easy access.
*/
+ error = -ENOMEM;
buffer = kzalloc(sizeof(struct sysfs_buffer), GFP_KERNEL);
- if (buffer) {
- INIT_LIST_HEAD(&buffer->associates);
- init_MUTEX(&buffer->sem);
- buffer->needs_read_fill = 1;
- buffer->ops = ops;
- add_to_collection(buffer, inode);
- file->private_data = buffer;
- } else
- error = -ENOMEM;
- goto Done;
+ if (!buffer)
+ goto err_mput;
- Einval:
- error = -EINVAL;
- goto Done;
- Eaccess:
- error = -EACCES;
+ INIT_LIST_HEAD(&buffer->associates);
+ init_MUTEX(&buffer->sem);
+ buffer->needs_read_fill = 1;
+ buffer->ops = ops;
+ add_to_collection(buffer, inode);
+ file->private_data = buffer;
+
+ /* open succeeded, put active references and pin attr_sd */
+ sysfs_put_active_two(attr_sd);
+ sysfs_get(attr_sd);
+ return 0;
+
+ err_mput:
module_put(attr->owner);
- Done:
- if (error)
- kobject_put(kobj);
+ err_sput:
+ sysfs_put_active_two(attr_sd);
return error;
}
static int sysfs_release(struct inode * inode, struct file * filp)
{
- struct kobject * kobj = to_kobj(filp->f_path.dentry->d_parent);
struct sysfs_dirent *attr_sd = filp->f_path.dentry->d_fsdata;
struct attribute *attr = attr_sd->s_elem.attr.attr;
struct sysfs_buffer * buffer = filp->private_data;
if (buffer)
remove_from_collection(buffer, inode);
- kobject_put(kobj);
+ sysfs_put(attr_sd);
/* After this point, attr should not be accessed. */
module_put(attr->owner);
@@ -376,18 +391,25 @@ static int sysfs_release(struct inode * inode, struct file * filp)
static unsigned int sysfs_poll(struct file *filp, poll_table *wait)
{
struct sysfs_buffer * buffer = filp->private_data;
- struct kobject * kobj = to_kobj(filp->f_path.dentry->d_parent);
- struct sysfs_dirent * sd = filp->f_path.dentry->d_fsdata;
- int res = 0;
+ struct sysfs_dirent *attr_sd = filp->f_path.dentry->d_fsdata;
+ struct kobject *kobj = attr_sd->s_parent->s_elem.dir.kobj;
+
+ /* need parent for the kobj, grab both */
+ if (!sysfs_get_active_two(attr_sd))
+ goto trigger;
poll_wait(filp, &kobj->poll, wait);
- if (buffer->event != atomic_read(&sd->s_event)) {
- res = POLLERR|POLLPRI;
- buffer->needs_read_fill = 1;
- }
+ sysfs_put_active_two(attr_sd);
- return res;
+ if (buffer->event != atomic_read(&attr_sd->s_event))
+ goto trigger;
+
+ return 0;
+
+ trigger:
+ buffer->needs_read_fill = 1;
+ return POLLERR|POLLPRI;
}
diff --git a/fs/sysfs/inode.c b/fs/sysfs/inode.c
index 617d10cea07d..7b9a8f132d5a 100644
--- a/fs/sysfs/inode.c
+++ b/fs/sysfs/inode.c
@@ -277,12 +277,16 @@ int sysfs_hash_and_remove(struct dentry * dir, const char * name)
if (!strcmp(sd->s_name, name)) {
list_del_init(&sd->s_sibling);
sysfs_drop_dentry(sd, dir);
- sysfs_put(sd);
found = 1;
break;
}
}
mutex_unlock(&dir->d_inode->i_mutex);
- return found ? 0 : -ENOENT;
+ if (!found)
+ return -ENOENT;
+
+ sysfs_deactivate(sd);
+ sysfs_put(sd);
+ return 0;
}
diff --git a/fs/sysfs/sysfs.h b/fs/sysfs/sysfs.h
index 60717660ac55..d998e8e27841 100644
--- a/fs/sysfs/sysfs.h
+++ b/fs/sysfs/sysfs.h
@@ -14,8 +14,14 @@ struct sysfs_elem_bin_attr {
struct bin_attribute * bin_attr;
};
+/*
+ * As long as s_count reference is held, the sysfs_dirent itself is
+ * accessible. Dereferencing s_elem or any other outer entity
+ * requires s_active reference.
+ */
struct sysfs_dirent {
atomic_t s_count;
+ struct rw_semaphore s_active;
struct sysfs_dirent * s_parent;
struct list_head s_sibling;
struct list_head s_children;
@@ -36,6 +42,17 @@ struct sysfs_dirent {
atomic_t s_event;
};
+/*
+ * A sysfs file which deletes another file when written to need to
+ * write lock the s_active of the victim while its s_active is read
+ * locked for the write operation. Tell lockdep that this is okay.
+ */
+enum sysfs_s_active_class
+{
+ SYSFS_S_ACTIVE_NORMAL, /* file r/w access, etc - default */
+ SYSFS_S_ACTIVE_DEACTIVATE, /* file deactivation */
+};
+
extern struct vfsmount * sysfs_mount;
extern struct kmem_cache *sysfs_dir_cachep;
@@ -87,43 +104,107 @@ struct sysfs_buffer_collection {
struct list_head associates;
};
-static inline struct kobject * to_kobj(struct dentry * dentry)
+static inline struct sysfs_dirent * sysfs_get(struct sysfs_dirent * sd)
{
- struct sysfs_dirent * sd = dentry->d_fsdata;
- return sd->s_elem.dir.kobj;
+ if (sd) {
+ WARN_ON(!atomic_read(&sd->s_count));
+ atomic_inc(&sd->s_count);
+ }
+ return sd;
}
-static inline struct kobject *sysfs_get_kobject(struct dentry *dentry)
+static inline void sysfs_put(struct sysfs_dirent * sd)
{
- struct kobject * kobj = NULL;
-
- spin_lock(&dcache_lock);
- if (!d_unhashed(dentry)) {
- struct sysfs_dirent * sd = dentry->d_fsdata;
-
- if (sd->s_type & SYSFS_KOBJ_LINK)
- sd = sd->s_elem.symlink.target_sd;
+ if (sd && atomic_dec_and_test(&sd->s_count))
+ release_sysfs_dirent(sd);
+}
- kobj = kobject_get(sd->s_elem.dir.kobj);
+/**
+ * sysfs_get_active - get an active reference to sysfs_dirent
+ * @sd: sysfs_dirent to get an active reference to
+ *
+ * Get an active reference of @sd. This function is noop if @sd
+ * is NULL.
+ *
+ * RETURNS:
+ * Pointer to @sd on success, NULL on failure.
+ */
+static inline struct sysfs_dirent *sysfs_get_active(struct sysfs_dirent *sd)
+{
+ if (sd) {
+ if (unlikely(!down_read_trylock(&sd->s_active)))
+ sd = NULL;
}
- spin_unlock(&dcache_lock);
+ return sd;
+}
- return kobj;
+/**
+ * sysfs_put_active - put an active reference to sysfs_dirent
+ * @sd: sysfs_dirent to put an active reference to
+ *
+ * Put an active reference to @sd. This function is noop if @sd
+ * is NULL.
+ */
+static inline void sysfs_put_active(struct sysfs_dirent *sd)
+{
+ if (sd)
+ up_read(&sd->s_active);
}
-static inline struct sysfs_dirent * sysfs_get(struct sysfs_dirent * sd)
+/**
+ * sysfs_get_active_two - get active references to sysfs_dirent and parent
+ * @sd: sysfs_dirent of interest
+ *
+ * Get active reference to @sd and its parent. Parent's active
+ * reference is grabbed first. This function is noop if @sd is
+ * NULL.
+ *
+ * RETURNS:
+ * Pointer to @sd on success, NULL on failure.
+ */
+static inline struct sysfs_dirent *sysfs_get_active_two(struct sysfs_dirent *sd)
{
if (sd) {
- WARN_ON(!atomic_read(&sd->s_count));
- atomic_inc(&sd->s_count);
+ if (sd->s_parent && unlikely(!sysfs_get_active(sd->s_parent)))
+ return NULL;
+ if (unlikely(!sysfs_get_active(sd))) {
+ sysfs_put_active(sd->s_parent);
+ return NULL;
+ }
}
return sd;
}
-static inline void sysfs_put(struct sysfs_dirent * sd)
+/**
+ * sysfs_put_active_two - put active references to sysfs_dirent and parent
+ * @sd: sysfs_dirent of interest
+ *
+ * Put active references to @sd and its parent. This function is
+ * noop if @sd is NULL.
+ */
+static inline void sysfs_put_active_two(struct sysfs_dirent *sd)
{
- if (sd && atomic_dec_and_test(&sd->s_count))
- release_sysfs_dirent(sd);
+ if (sd) {
+ sysfs_put_active(sd);
+ sysfs_put_active(sd->s_parent);
+ }
+}
+
+/**
+ * sysfs_deactivate - deactivate sysfs_dirent
+ * @sd: sysfs_dirent to deactivate
+ *
+ * Deny new active references and drain existing ones. s_active
+ * will be unlocked when the sysfs_dirent is released.
+ */
+static inline void sysfs_deactivate(struct sysfs_dirent *sd)
+{
+ down_write_nested(&sd->s_active, SYSFS_S_ACTIVE_DEACTIVATE);
+
+ /* s_active will be unlocked by the thread doing the final put
+ * on @sd. Lie to lockdep.
+ */
+ rwsem_release(&sd->s_active.dep_map, 1, _RET_IP_);
}
static inline int sysfs_is_shadowed_inode(struct inode *inode)