diff options
-rw-r--r-- | fs/sysfs/bin.c | 95 | ||||
-rw-r--r-- | fs/sysfs/dir.c | 28 | ||||
-rw-r--r-- | fs/sysfs/file.c | 130 | ||||
-rw-r--r-- | fs/sysfs/inode.c | 8 | ||||
-rw-r--r-- | fs/sysfs/sysfs.h | 123 |
5 files changed, 271 insertions, 113 deletions
diff --git a/fs/sysfs/bin.c b/fs/sysfs/bin.c index 5dc47fe5..618b8ae 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 2a94dc3..e0d377a 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 04f6b0e..310430b 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 617d10c..7b9a8f1 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 6071766..d998e8e 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) |