Commit 0ab66088 authored by Tejun Heo's avatar Tejun Heo Committed by Greg Kroah-Hartman

sysfs: implement sysfs_dirent active reference and immediate disconnect

sysfs: implement sysfs_dirent active reference and immediate disconnect

Opening a sysfs node references its associated kobject, so userland
can arbitrarily prolong lifetime of a kobject which complicates
lifetime rules in drivers.  This patch implements active reference and
makes the association between kobject and sysfs immediately breakable.

Now each sysfs_dirent has two reference counts - s_count and s_active.
s_count is a regular reference count which guarantees that the
containing sysfs_dirent is accessible.  As long as s_count reference
is held, all sysfs internal fields in sysfs_dirent are accessible
including s_parent and s_name.

The newly added s_active is active reference count.  This is acquired
by invoking sysfs_get_active() and it's the caller's responsibility to
ensure sysfs_dirent itself is accessible (should be holding s_count
one way or the other).  Dereferencing sysfs_dirent to access objects
out of sysfs proper requires active reference.  This includes access
to the associated kobjects, attributes and ops.

The active references can be drained and denied by calling
sysfs_deactivate().  All active sysfs_dirents must be deactivated
after deletion but before the default reference is dropped.  This
enables immediate disconnect of sysfs nodes.  Once a sysfs_dirent is
deleted, it won't access any entity external to sysfs proper.

Because attr/bin_attr ops access both the node itself and its parent
for kobject, they need to hold active references to both.
sysfs_get/put_active_two() helpers are provided to help grabbing both
references.  Parent's is acquired first and released last.

Unlike other operations, mmapped area lingers on after mmap() is
finished and the module implement implementing it and kobj need to
stay referenced till all the mapped pages are gone.  This is
accomplished by holding one set of active references to the bin_attr
and its parent if there have been any mmap during lifetime of an
openfile.  The references are dropped when the openfile is released.

This change makes sysfs lifetime rules independent from both kobject's
and module's.  It not only fixes several race conditions caused by
sysfs not holding onto the proper module when referencing kobject, but
also helps fixing and simplifying lifetime management in driver model
and drivers by taking sysfs out of the equation.

Please read the following message for more info.

  http://article.gmane.org/gmane.linux.kernel/510293Signed-off-by: default avatarTejun Heo <htejun@gmail.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent eb361653
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
struct bin_buffer { struct bin_buffer {
struct mutex mutex; struct mutex mutex;
void *buffer; void *buffer;
int mmapped;
}; };
static int static int
...@@ -30,12 +31,20 @@ fill_read(struct dentry *dentry, char *buffer, loff_t off, size_t count) ...@@ -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 sysfs_dirent *attr_sd = dentry->d_fsdata;
struct bin_attribute *attr = attr_sd->s_elem.bin_attr.bin_attr; 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) rc = -EIO;
return -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 static ssize_t
...@@ -79,12 +88,20 @@ flush_write(struct dentry *dentry, char *buffer, loff_t offset, size_t count) ...@@ -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 sysfs_dirent *attr_sd = dentry->d_fsdata;
struct bin_attribute *attr = attr_sd->s_elem.bin_attr.bin_attr; 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) rc = -EIO;
return -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, 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) ...@@ -124,14 +141,24 @@ static int mmap(struct file *file, struct vm_area_struct *vma)
struct bin_buffer *bb = file->private_data; struct bin_buffer *bb = file->private_data;
struct sysfs_dirent *attr_sd = file->f_path.dentry->d_fsdata; 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_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; int rc;
if (!attr->mmap)
return -EINVAL;
mutex_lock(&bb->mutex); 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); mutex_unlock(&bb->mutex);
return rc; return rc;
...@@ -139,58 +166,60 @@ static int mmap(struct file *file, struct vm_area_struct *vma) ...@@ -139,58 +166,60 @@ static int mmap(struct file *file, struct vm_area_struct *vma)
static int open(struct inode * inode, struct file * file) 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 sysfs_dirent *attr_sd = file->f_path.dentry->d_fsdata;
struct bin_attribute *attr = attr_sd->s_elem.bin_attr.bin_attr; struct bin_attribute *attr = attr_sd->s_elem.bin_attr.bin_attr;
struct bin_buffer *bb = NULL; struct bin_buffer *bb = NULL;
int error = -EINVAL; int error;
if (!kobj || !attr) /* need attr_sd for attr */
goto Done; 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; error = -ENODEV;
if (!try_module_get(attr->attr.owner)) if (!try_module_get(attr->attr.owner))
goto Done; goto err_sput;
error = -EACCES; error = -EACCES;
if ((file->f_mode & FMODE_WRITE) && !(attr->write || attr->mmap)) 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)) if ((file->f_mode & FMODE_READ) && !(attr->read || attr->mmap))
goto Error; goto err_mput;
error = -ENOMEM; error = -ENOMEM;
bb = kzalloc(sizeof(*bb), GFP_KERNEL); bb = kzalloc(sizeof(*bb), GFP_KERNEL);
if (!bb) if (!bb)
goto Error; goto err_mput;
bb->buffer = kmalloc(PAGE_SIZE, GFP_KERNEL); bb->buffer = kmalloc(PAGE_SIZE, GFP_KERNEL);
if (!bb->buffer) if (!bb->buffer)
goto Error; goto err_mput;
mutex_init(&bb->mutex); mutex_init(&bb->mutex);
file->private_data = bb; file->private_data = bb;
error = 0; /* open succeeded, put active reference and pin attr_sd */
goto Done; sysfs_put_active(attr_sd);
sysfs_get(attr_sd);
return 0;
Error: err_mput:
kfree(bb);
module_put(attr->attr.owner); module_put(attr->attr.owner);
Done: err_sput:
if (error) sysfs_put_active(attr_sd);
kobject_put(kobj); kfree(bb);
return error; return error;
} }
static int release(struct inode * inode, struct file * file) 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 sysfs_dirent *attr_sd = file->f_path.dentry->d_fsdata;
struct bin_attribute *attr = attr_sd->s_elem.bin_attr.bin_attr; struct bin_attribute *attr = attr_sd->s_elem.bin_attr.bin_attr;
struct bin_buffer *bb = file->private_data; 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); module_put(attr->attr.owner);
kfree(bb->buffer); kfree(bb->buffer);
kfree(bb); kfree(bb);
......
...@@ -53,6 +53,19 @@ void release_sysfs_dirent(struct sysfs_dirent * sd) ...@@ -53,6 +53,19 @@ void release_sysfs_dirent(struct sysfs_dirent * sd)
repeat: repeat:
parent_sd = sd->s_parent; 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) if (sd->s_type & SYSFS_KOBJ_LINK)
sysfs_put(sd->s_elem.symlink.target_sd); sysfs_put(sd->s_elem.symlink.target_sd);
if (sd->s_type & SYSFS_COPY_NAME) 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) ...@@ -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_count, 1);
atomic_set(&sd->s_event, 1); atomic_set(&sd->s_event, 1);
init_rwsem(&sd->s_active);
INIT_LIST_HEAD(&sd->s_children); INIT_LIST_HEAD(&sd->s_children);
INIT_LIST_HEAD(&sd->s_sibling); INIT_LIST_HEAD(&sd->s_sibling);
...@@ -371,7 +385,6 @@ static void remove_dir(struct dentry * d) ...@@ -371,7 +385,6 @@ static void remove_dir(struct dentry * d)
d_delete(d); d_delete(d);
sd = d->d_fsdata; sd = d->d_fsdata;
list_del_init(&sd->s_sibling); list_del_init(&sd->s_sibling);
sysfs_put(sd);
if (d->d_inode) if (d->d_inode)
simple_rmdir(parent->d_inode,d); simple_rmdir(parent->d_inode,d);
...@@ -380,6 +393,9 @@ static void remove_dir(struct dentry * d) ...@@ -380,6 +393,9 @@ static void remove_dir(struct dentry * d)
mutex_unlock(&parent->d_inode->i_mutex); mutex_unlock(&parent->d_inode->i_mutex);
dput(parent); dput(parent);
sysfs_deactivate(sd);
sysfs_put(sd);
} }
void sysfs_remove_subdir(struct dentry * d) void sysfs_remove_subdir(struct dentry * d)
...@@ -390,6 +406,7 @@ 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) static void __sysfs_remove_dir(struct dentry *dentry)
{ {
LIST_HEAD(removed);
struct sysfs_dirent * parent_sd; struct sysfs_dirent * parent_sd;
struct sysfs_dirent * sd, * tmp; struct sysfs_dirent * sd, * tmp;
...@@ -403,12 +420,17 @@ static void __sysfs_remove_dir(struct dentry *dentry) ...@@ -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) { list_for_each_entry_safe(sd, tmp, &parent_sd->s_children, s_sibling) {
if (!sd->s_type || !(sd->s_type & SYSFS_NOT_PINNED)) if (!sd->s_type || !(sd->s_type & SYSFS_NOT_PINNED))
continue; continue;
list_del_init(&sd->s_sibling); list_move(&sd->s_sibling, &removed);
sysfs_drop_dentry(sd, dentry); sysfs_drop_dentry(sd, dentry);
sysfs_put(sd);
} }
mutex_unlock(&dentry->d_inode->i_mutex); 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); remove_dir(dentry);
/** /**
* Drop reference from dget() on entrance. * Drop reference from dget() on entrance.
......
...@@ -87,8 +87,8 @@ remove_from_collection(struct sysfs_buffer *buffer, struct inode *node) ...@@ -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) static int fill_read_buffer(struct dentry * dentry, struct sysfs_buffer * buffer)
{ {
struct sysfs_dirent * sd = dentry->d_fsdata; 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; struct sysfs_ops * ops = buffer->ops;
int ret = 0; int ret = 0;
ssize_t count; ssize_t count;
...@@ -98,8 +98,15 @@ static int fill_read_buffer(struct dentry * dentry, struct sysfs_buffer * buffer ...@@ -98,8 +98,15 @@ static int fill_read_buffer(struct dentry * dentry, struct sysfs_buffer * buffer
if (!buffer->page) if (!buffer->page)
return -ENOMEM; return -ENOMEM;
buffer->event = atomic_read(&sd->s_event); /* need attr_sd for attr and ops, its parent for kobj */
count = ops->show(kobj, sd->s_elem.attr.attr, buffer->page); 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); BUG_ON(count > (ssize_t)PAGE_SIZE);
if (count >= 0) { if (count >= 0) {
buffer->needs_read_fill = 0; buffer->needs_read_fill = 0;
...@@ -195,14 +202,23 @@ fill_write_buffer(struct sysfs_buffer * buffer, const char __user * buf, size_t ...@@ -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(). * 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) flush_write_buffer(struct dentry * dentry, struct sysfs_buffer * buffer, size_t count)
{ {
struct sysfs_dirent *attr_sd = dentry->d_fsdata; 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; 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 @@ sysfs_write_file(struct file *file, const char __user *buf, size_t count, loff_t ...@@ -246,22 +262,22 @@ sysfs_write_file(struct file *file, const char __user *buf, size_t count, loff_t
static int sysfs_open_file(struct inode *inode, struct file *file) 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 sysfs_dirent *attr_sd = file->f_path.dentry->d_fsdata;
struct attribute *attr = attr_sd->s_elem.attr.attr; 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_collection *set;
struct sysfs_buffer * buffer; struct sysfs_buffer * buffer;
struct sysfs_ops * ops = NULL; struct sysfs_ops * ops = NULL;
int error = 0; int error;
if (!kobj || !attr) /* need attr_sd for attr and ops, its parent for kobj */
goto Einval; if (!sysfs_get_active_two(attr_sd))
return -ENODEV;
/* Grab the module reference for this attribute if we have one */ /* Grab the module reference for this attribute */
if (!try_module_get(attr->owner)) { error = -ENODEV;
error = -ENODEV; if (!try_module_get(attr->owner))
goto Done; goto err_sput;
}
/* if the kobject has no ktype, then we assume that it is a subsystem /* if the kobject has no ktype, then we assume that it is a subsystem
* itself, and use ops for it. * itself, and use ops for it.
...@@ -276,30 +292,30 @@ static int sysfs_open_file(struct inode *inode, struct file *file) ...@@ -276,30 +292,30 @@ static int sysfs_open_file(struct inode *inode, struct file *file)
/* No sysfs operations, either from having no subsystem, /* No sysfs operations, either from having no subsystem,
* or the subsystem have no operations. * or the subsystem have no operations.
*/ */
error = -EACCES;
if (!ops) if (!ops)
goto Eaccess; goto err_mput;
/* make sure we have a collection to add our buffers to */ /* make sure we have a collection to add our buffers to */
mutex_lock(&inode->i_mutex); mutex_lock(&inode->i_mutex);
if (!(set = inode->i_private)) { if (!(set = inode->i_private)) {
if (!(set = inode->i_private = kmalloc(sizeof(struct sysfs_buffer_collection), GFP_KERNEL))) { error = -ENOMEM;
error = -ENOMEM; if (!(set = inode->i_private = kmalloc(sizeof(struct sysfs_buffer_collection), GFP_KERNEL)))
goto Done; goto err_mput;
} else { else
INIT_LIST_HEAD(&set->associates); INIT_LIST_HEAD(&set->associates);
}
} }
mutex_unlock(&inode->i_mutex); mutex_unlock(&inode->i_mutex);
error = -EACCES;
/* File needs write support. /* File needs write support.
* The inode's perms must say it's ok, * The inode's perms must say it's ok,
* and we must have a store method. * and we must have a store method.
*/ */
if (file->f_mode & FMODE_WRITE) { if (file->f_mode & FMODE_WRITE) {
if (!(inode->i_mode & S_IWUGO) || !ops->store) if (!(inode->i_mode & S_IWUGO) || !ops->store)
goto Eaccess; goto err_mput;
} }
/* File needs read support. /* File needs read support.
...@@ -308,46 +324,45 @@ static int sysfs_open_file(struct inode *inode, struct file *file) ...@@ -308,46 +324,45 @@ static int sysfs_open_file(struct inode *inode, struct file *file)
*/ */
if (file->f_mode & FMODE_READ) { if (file->f_mode & FMODE_READ) {
if (!(inode->i_mode & S_IRUGO) || !ops->show) 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 /* No error? Great, allocate a buffer for the file, and store it
* it in file->private_data for easy access. * it in file->private_data for easy access.
*/ */
error = -ENOMEM;
buffer = kzalloc(sizeof(struct sysfs_buffer), GFP_KERNEL); buffer = kzalloc(sizeof(struct sysfs_buffer), GFP_KERNEL);
if (buffer) { if (!buffer)
INIT_LIST_HEAD(&buffer->associates); goto err_mput;
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;
Einval: INIT_LIST_HEAD(&buffer->associates);
error = -EINVAL; init_MUTEX(&buffer->sem);
goto Done; buffer->needs_read_fill = 1;
Eaccess: buffer->ops = ops;
error = -EACCES; 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); module_put(attr->owner);
Done: err_sput:
if (error) sysfs_put_active_two(attr_sd);
kobject_put(kobj);
return error; return error;
} }
static int sysfs_release(struct inode * inode, struct file * filp) 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 sysfs_dirent *attr_sd = filp->f_path.dentry->d_fsdata;
struct attribute *attr = attr_sd->s_elem.attr.attr; struct attribute *attr = attr_sd->s_elem.attr.attr;
struct sysfs_buffer * buffer = filp->private_data; struct sysfs_buffer * buffer = filp->private_data;
if (buffer) if (buffer)
remove_from_collection(buffer, inode); remove_from_collection(buffer, inode);
kobject_put(kobj); sysfs_put(attr_sd);
/* After this point, attr should not be accessed. */ /* After this point, attr should not be accessed. */
module_put(attr->owner); module_put(attr->owner);
...@@ -376,18 +391,25 @@ static int sysfs_release(struct inode * inode, struct file * filp) ...@@ -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) static unsigned int sysfs_poll(struct file *filp, poll_table *wait)
{ {
struct sysfs_buffer * buffer = filp->private_data; struct sysfs_buffer * buffer = filp->private_data;
struct kobject * kobj = to_kobj(filp->f_path.dentry->d_parent); struct sysfs_dirent *attr_sd = filp->f_path.dentry->d_fsdata;
struct sysfs_dirent * sd = filp->f_path.dentry->d_fsdata; struct kobject *kobj = attr_sd->s_parent->s_elem.dir.kobj;
int res = 0;
/* need parent for the kobj, grab both */
if (!sysfs_get_active_two(attr_sd))
goto trigger;
poll_wait(filp, &kobj->poll, wait); poll_wait(filp, &kobj->poll, wait);
if (buffer->event != atomic_read(&sd->s_event)) { sysfs_put_active_two(attr_sd);
res = POLLERR|POLLPRI;
buffer->needs_read_fill = 1;
}
return res; if (buffer->event != atomic_read(&attr_sd->s_event))
goto trigger;
return 0;
trigger:
buffer->needs_read_fill = 1;
return POLLERR|POLLPRI;
} }
......
...@@ -277,12 +277,16 @@ int sysfs_hash_and_remove(struct dentry * dir, const char * name) ...@@ -277,12 +277,16 @@ int sysfs_hash_and_remove(struct dentry * dir, const char * name)
if (!strcmp(sd->s_name, name)) { if (!strcmp(sd->s_name, name)) {
list_del_init(&sd->s_sibling); list_del_init(&sd->s_sibling);
sysfs_drop_dentry(sd, dir); sysfs_drop_dentry(sd, dir);
sysfs_put(sd);
found = 1; found = 1;
break; break;
} }
} }
mutex_unlock(&dir->d_inode->i_mutex); mutex_unlock(&dir->d_inode->i_mutex);
return found ? 0 : -ENOENT; if (!found)
return -ENOENT;
sysfs_deactivate(sd);
sysfs_put(sd);
return 0;
} }
...@@ -14,8 +14,14 @@ struct sysfs_elem_bin_attr { ...@@ -14,8 +14,14 @@ struct sysfs_elem_bin_attr {
struct bin_attribute * 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 { struct sysfs_dirent {
atomic_t s_count; atomic_t s_count;
struct rw_semaphore s_active;
struct sysfs_dirent * s_parent; struct sysfs_dirent * s_parent;
struct list_head s_sibling; struct list_head s_sibling;
struct list_head s_children; struct list_head s_children;
...@@ -36,6 +42,17 @@ struct sysfs_dirent { ...@@ -36,6 +42,17 @@ struct sysfs_dirent {
atomic_t s_event; 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 vfsmount * sysfs_mount;
extern struct kmem_cache *sysfs_dir_cachep; extern struct kmem_cache *sysfs_dir_cachep;
...@@ -87,43 +104,107 @@ struct sysfs_buffer_collection { ...@@ -87,43 +104,107 @@ struct sysfs_buffer_collection {
struct list_head associates; 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; if (sd) {
return sd->s_elem.dir.kobj; 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; if (sd && atomic_dec_and_test(&sd->s_count))
release_sysfs_dirent(sd);
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;
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) { if (sd) {
WARN_ON(!atomic_read(&sd->s_count)); if (sd->s_parent && unlikely(!sysfs_get_active(sd->s_parent)))
atomic_inc(&sd->s_count); return NULL;
if (unlikely(!sysfs_get_active(sd))) {
sysfs_put_active(sd->s_parent);
return NULL;
}
} }
return sd; 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)) if (sd) {
release_sysfs_dirent(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) static inline int sysfs_is_shadowed_inode(struct inode *inode)
......
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