Commit 832b6af1 authored by Eric W. Biederman's avatar Eric W. Biederman Committed by Greg Kroah-Hartman

sysfs: Propagate renames to the vfs on demand

By teaching sysfs_revalidate to hide a dentry for
a sysfs_dirent if the sysfs_dirent has been renamed,
and by teaching sysfs_lookup to return the original
dentry if the sysfs dirent has been renamed.  I can
show the results of renames correctly without having to
update the dcache during the directory rename.

This massively simplifies the rename logic allowing a lot
of weird sysfs special cases to be removed along with
a lot of now unnecesary helper code.
Acked-by: default avatarTejun Heo <tj@kernel.org>
Signed-off-by: default avatarEric W. Biederman <ebiederm@aristanetworks.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent a16bbc34
...@@ -1279,28 +1279,6 @@ struct dentry *lookup_one_len(const char *name, struct dentry *base, int len) ...@@ -1279,28 +1279,6 @@ struct dentry *lookup_one_len(const char *name, struct dentry *base, int len)
return __lookup_hash(&this, base, NULL); return __lookup_hash(&this, base, NULL);
} }
/**
* lookup_one_noperm - bad hack for sysfs
* @name: pathname component to lookup
* @base: base directory to lookup from
*
* This is a variant of lookup_one_len that doesn't perform any permission
* checks. It's a horrible hack to work around the braindead sysfs
* architecture and should not be used anywhere else.
*
* DON'T USE THIS FUNCTION EVER, thanks.
*/
struct dentry *lookup_one_noperm(const char *name, struct dentry *base)
{
int err;
struct qstr this;
err = __lookup_one_len(name, &this, base, strlen(name));
if (err)
return ERR_PTR(err);
return __lookup_hash(&this, base, NULL);
}
int user_path_at(int dfd, const char __user *name, unsigned flags, int user_path_at(int dfd, const char __user *name, unsigned flags,
struct path *path) struct path *path)
{ {
......
...@@ -25,7 +25,6 @@ ...@@ -25,7 +25,6 @@
#include "sysfs.h" #include "sysfs.h"
DEFINE_MUTEX(sysfs_mutex); DEFINE_MUTEX(sysfs_mutex);
DEFINE_MUTEX(sysfs_rename_mutex);
DEFINE_SPINLOCK(sysfs_assoc_lock); DEFINE_SPINLOCK(sysfs_assoc_lock);
static DEFINE_SPINLOCK(sysfs_ino_lock); static DEFINE_SPINLOCK(sysfs_ino_lock);
...@@ -84,46 +83,6 @@ static void sysfs_unlink_sibling(struct sysfs_dirent *sd) ...@@ -84,46 +83,6 @@ static void sysfs_unlink_sibling(struct sysfs_dirent *sd)
} }
} }
/**
* sysfs_get_dentry - get dentry for the given sysfs_dirent
* @sd: sysfs_dirent of interest
*
* Get dentry for @sd. Dentry is looked up if currently not
* present. This function descends from the root looking up
* dentry for each step.
*
* LOCKING:
* mutex_lock(sysfs_rename_mutex)
*
* RETURNS:
* Pointer to found dentry on success, ERR_PTR() value on error.
*/
struct dentry *sysfs_get_dentry(struct sysfs_dirent *sd)
{
struct dentry *dentry = dget(sysfs_sb->s_root);
while (dentry->d_fsdata != sd) {
struct sysfs_dirent *cur;
struct dentry *parent;
/* find the first ancestor which hasn't been looked up */
cur = sd;
while (cur->s_parent != dentry->d_fsdata)
cur = cur->s_parent;
/* look it up */
parent = dentry;
mutex_lock(&parent->d_inode->i_mutex);
dentry = lookup_one_noperm(cur->s_name, parent);
mutex_unlock(&parent->d_inode->i_mutex);
dput(parent);
if (IS_ERR(dentry))
break;
}
return dentry;
}
/** /**
* sysfs_get_active - get an active reference to sysfs_dirent * sysfs_get_active - get an active reference to sysfs_dirent
* @sd: sysfs_dirent to get an active reference to * @sd: sysfs_dirent to get an active reference to
...@@ -315,6 +274,14 @@ static int sysfs_dentry_revalidate(struct dentry *dentry, struct nameidata *nd) ...@@ -315,6 +274,14 @@ static int sysfs_dentry_revalidate(struct dentry *dentry, struct nameidata *nd)
if (sd->s_flags & SYSFS_FLAG_REMOVED) if (sd->s_flags & SYSFS_FLAG_REMOVED)
goto out_bad; goto out_bad;
/* The sysfs dirent has been moved? */
if (dentry->d_parent->d_fsdata != sd->s_parent)
goto out_bad;
/* The sysfs dirent has been renamed */
if (strcmp(dentry->d_name.name, sd->s_name) != 0)
goto out_bad;
mutex_unlock(&sysfs_mutex); mutex_unlock(&sysfs_mutex);
out_valid: out_valid:
return 1; return 1;
...@@ -322,6 +289,12 @@ static int sysfs_dentry_revalidate(struct dentry *dentry, struct nameidata *nd) ...@@ -322,6 +289,12 @@ static int sysfs_dentry_revalidate(struct dentry *dentry, struct nameidata *nd)
/* Remove the dentry from the dcache hashes. /* Remove the dentry from the dcache hashes.
* If this is a deleted dentry we use d_drop instead of d_delete * If this is a deleted dentry we use d_drop instead of d_delete
* so sysfs doesn't need to cope with negative dentries. * so sysfs doesn't need to cope with negative dentries.
*
* If this is a dentry that has simply been renamed we
* use d_drop to remove it from the dcache lookup on its
* old parent. If this dentry persists later when a lookup
* is performed at its new name the dentry will be readded
* to the dcache hashes.
*/ */
is_dir = (sysfs_type(sd) == SYSFS_DIR); is_dir = (sysfs_type(sd) == SYSFS_DIR);
mutex_unlock(&sysfs_mutex); mutex_unlock(&sysfs_mutex);
...@@ -705,10 +678,15 @@ static struct dentry * sysfs_lookup(struct inode *dir, struct dentry *dentry, ...@@ -705,10 +678,15 @@ static struct dentry * sysfs_lookup(struct inode *dir, struct dentry *dentry,
} }
/* instantiate and hash dentry */ /* instantiate and hash dentry */
dentry->d_op = &sysfs_dentry_ops; ret = d_find_alias(inode);
dentry->d_fsdata = sysfs_get(sd); if (!ret) {
d_instantiate(dentry, inode); dentry->d_op = &sysfs_dentry_ops;
d_rehash(dentry); dentry->d_fsdata = sysfs_get(sd);
d_add(dentry, inode);
} else {
d_move(ret, dentry);
iput(inode);
}
out_unlock: out_unlock:
mutex_unlock(&sysfs_mutex); mutex_unlock(&sysfs_mutex);
...@@ -785,62 +763,32 @@ void sysfs_remove_dir(struct kobject * kobj) ...@@ -785,62 +763,32 @@ void sysfs_remove_dir(struct kobject * kobj)
int sysfs_rename_dir(struct kobject * kobj, const char *new_name) int sysfs_rename_dir(struct kobject * kobj, const char *new_name)
{ {
struct sysfs_dirent *sd = kobj->sd; struct sysfs_dirent *sd = kobj->sd;
struct dentry *parent = NULL;
struct dentry *old_dentry = NULL, *new_dentry = NULL;
const char *dup_name = NULL; const char *dup_name = NULL;
int error; int error;
mutex_lock(&sysfs_rename_mutex); mutex_lock(&sysfs_mutex);
error = 0; error = 0;
if (strcmp(sd->s_name, new_name) == 0) if (strcmp(sd->s_name, new_name) == 0)
goto out; /* nothing to rename */ goto out; /* nothing to rename */
/* get the original dentry */
old_dentry = sysfs_get_dentry(sd);
if (IS_ERR(old_dentry)) {
error = PTR_ERR(old_dentry);
old_dentry = NULL;
goto out;
}
parent = old_dentry->d_parent;
/* lock parent and get dentry for new name */
mutex_lock(&parent->d_inode->i_mutex);
mutex_lock(&sysfs_mutex);
error = -EEXIST; error = -EEXIST;
if (sysfs_find_dirent(sd->s_parent, new_name)) if (sysfs_find_dirent(sd->s_parent, new_name))
goto out_unlock; goto out;
error = -ENOMEM;
new_dentry = d_alloc_name(parent, new_name);
if (!new_dentry)
goto out_unlock;
/* rename sysfs_dirent */ /* rename sysfs_dirent */
error = -ENOMEM; error = -ENOMEM;
new_name = dup_name = kstrdup(new_name, GFP_KERNEL); new_name = dup_name = kstrdup(new_name, GFP_KERNEL);
if (!new_name) if (!new_name)
goto out_unlock; goto out;
dup_name = sd->s_name; dup_name = sd->s_name;
sd->s_name = new_name; sd->s_name = new_name;
/* rename */
d_add(new_dentry, NULL);
d_move(old_dentry, new_dentry);
error = 0; error = 0;
out_unlock: out:
mutex_unlock(&sysfs_mutex); mutex_unlock(&sysfs_mutex);
mutex_unlock(&parent->d_inode->i_mutex);
kfree(dup_name); kfree(dup_name);
dput(old_dentry);
dput(new_dentry);
out:
mutex_unlock(&sysfs_rename_mutex);
return error; return error;
} }
...@@ -848,12 +796,11 @@ int sysfs_move_dir(struct kobject *kobj, struct kobject *new_parent_kobj) ...@@ -848,12 +796,11 @@ int sysfs_move_dir(struct kobject *kobj, struct kobject *new_parent_kobj)
{ {
struct sysfs_dirent *sd = kobj->sd; struct sysfs_dirent *sd = kobj->sd;
struct sysfs_dirent *new_parent_sd; struct sysfs_dirent *new_parent_sd;
struct dentry *old_parent, *new_parent = NULL;
struct dentry *old_dentry = NULL, *new_dentry = NULL;
int error; int error;
mutex_lock(&sysfs_rename_mutex);
BUG_ON(!sd->s_parent); BUG_ON(!sd->s_parent);
mutex_lock(&sysfs_mutex);
new_parent_sd = (new_parent_kobj && new_parent_kobj->sd) ? new_parent_sd = (new_parent_kobj && new_parent_kobj->sd) ?
new_parent_kobj->sd : &sysfs_root; new_parent_kobj->sd : &sysfs_root;
...@@ -861,61 +808,20 @@ int sysfs_move_dir(struct kobject *kobj, struct kobject *new_parent_kobj) ...@@ -861,61 +808,20 @@ int sysfs_move_dir(struct kobject *kobj, struct kobject *new_parent_kobj)
if (sd->s_parent == new_parent_sd) if (sd->s_parent == new_parent_sd)
goto out; /* nothing to move */ goto out; /* nothing to move */
/* get dentries */
old_dentry = sysfs_get_dentry(sd);
if (IS_ERR(old_dentry)) {
error = PTR_ERR(old_dentry);
old_dentry = NULL;
goto out;
}
old_parent = old_dentry->d_parent;
new_parent = sysfs_get_dentry(new_parent_sd);
if (IS_ERR(new_parent)) {
error = PTR_ERR(new_parent);
new_parent = NULL;
goto out;
}
again:
mutex_lock(&old_parent->d_inode->i_mutex);
if (!mutex_trylock(&new_parent->d_inode->i_mutex)) {
mutex_unlock(&old_parent->d_inode->i_mutex);
goto again;
}
mutex_lock(&sysfs_mutex);
error = -EEXIST; error = -EEXIST;
if (sysfs_find_dirent(new_parent_sd, sd->s_name)) if (sysfs_find_dirent(new_parent_sd, sd->s_name))
goto out_unlock; goto out;
error = -ENOMEM;
new_dentry = d_alloc_name(new_parent, sd->s_name);
if (!new_dentry)
goto out_unlock;
error = 0;
d_add(new_dentry, NULL);
d_move(old_dentry, new_dentry);
/* Remove from old parent's list and insert into new parent's list. */ /* Remove from old parent's list and insert into new parent's list. */
sysfs_unlink_sibling(sd); sysfs_unlink_sibling(sd);
sysfs_get(new_parent_sd); sysfs_get(new_parent_sd);
drop_nlink(old_parent->d_inode);
sysfs_put(sd->s_parent); sysfs_put(sd->s_parent);
sd->s_parent = new_parent_sd; sd->s_parent = new_parent_sd;
inc_nlink(new_parent->d_inode);
sysfs_link_sibling(sd); sysfs_link_sibling(sd);
out_unlock: error = 0;
out:
mutex_unlock(&sysfs_mutex); mutex_unlock(&sysfs_mutex);
mutex_unlock(&new_parent->d_inode->i_mutex);
mutex_unlock(&old_parent->d_inode->i_mutex);
out:
dput(new_parent);
dput(old_dentry);
dput(new_dentry);
mutex_unlock(&sysfs_rename_mutex);
return error; return error;
} }
......
...@@ -205,17 +205,6 @@ static inline void set_inode_attr(struct inode * inode, struct iattr * iattr) ...@@ -205,17 +205,6 @@ static inline void set_inode_attr(struct inode * inode, struct iattr * iattr)
inode->i_ctime = iattr->ia_ctime; inode->i_ctime = iattr->ia_ctime;
} }
/*
* sysfs has a different i_mutex lock order behavior for i_mutex than other
* filesystems; sysfs i_mutex is called in many places with subsystem locks
* held. At the same time, many of the VFS locking rules do not apply to
* sysfs at all (cross directory rename for example). To untangle this mess
* (which gives false positives in lockdep), we're giving sysfs inodes their
* own class for i_mutex.
*/
static struct lock_class_key sysfs_inode_imutex_key;
static int sysfs_count_nlink(struct sysfs_dirent *sd) static int sysfs_count_nlink(struct sysfs_dirent *sd)
{ {
struct sysfs_dirent *child; struct sysfs_dirent *child;
...@@ -268,7 +257,6 @@ static void sysfs_init_inode(struct sysfs_dirent *sd, struct inode *inode) ...@@ -268,7 +257,6 @@ static void sysfs_init_inode(struct sysfs_dirent *sd, struct inode *inode)
inode->i_mapping->a_ops = &sysfs_aops; inode->i_mapping->a_ops = &sysfs_aops;
inode->i_mapping->backing_dev_info = &sysfs_backing_dev_info; inode->i_mapping->backing_dev_info = &sysfs_backing_dev_info;
inode->i_op = &sysfs_inode_operations; inode->i_op = &sysfs_inode_operations;
lockdep_set_class(&inode->i_mutex, &sysfs_inode_imutex_key);
set_default_inode_attr(inode, sd->s_mode); set_default_inode_attr(inode, sd->s_mode);
sysfs_refresh_inode(sd, inode); sysfs_refresh_inode(sd, inode);
......
...@@ -103,7 +103,6 @@ extern struct kmem_cache *sysfs_dir_cachep; ...@@ -103,7 +103,6 @@ extern struct kmem_cache *sysfs_dir_cachep;
* dir.c * dir.c
*/ */
extern struct mutex sysfs_mutex; extern struct mutex sysfs_mutex;
extern struct mutex sysfs_rename_mutex;
extern spinlock_t sysfs_assoc_lock; extern spinlock_t sysfs_assoc_lock;
extern const struct file_operations sysfs_dir_operations; extern const struct file_operations sysfs_dir_operations;
......
...@@ -76,7 +76,6 @@ extern struct file *nameidata_to_filp(struct nameidata *nd, int flags); ...@@ -76,7 +76,6 @@ extern struct file *nameidata_to_filp(struct nameidata *nd, int flags);
extern void release_open_intent(struct nameidata *); extern void release_open_intent(struct nameidata *);
extern struct dentry *lookup_one_len(const char *, struct dentry *, int); extern struct dentry *lookup_one_len(const char *, struct dentry *, int);
extern struct dentry *lookup_one_noperm(const char *, struct dentry *);
extern int follow_down(struct path *); extern int follow_down(struct path *);
extern int follow_up(struct path *); extern int follow_up(struct path *);
......
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