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

sysfs: reimplement sysfs_drop_dentry()

This patch reimplements sysfs_drop_dentry() such that remove_dir() can
use it to drop dentry instead of using a separate mechanism.  With
this change, making directories reclaimable is much easier.

This patch used to contain fixes for two race conditions around
sd->s_dentry but that part has been separated out and included into
mainline early as commit 6aa054aa and
dd14cbc9.
Signed-off-by: default avatarTejun Heo <htejun@gmail.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent 198a2a84
...@@ -372,22 +372,19 @@ const struct inode_operations sysfs_dir_inode_operations = { ...@@ -372,22 +372,19 @@ const struct inode_operations sysfs_dir_inode_operations = {
static void remove_dir(struct dentry * d) static void remove_dir(struct dentry * d)
{ {
struct dentry * parent = dget(d->d_parent); struct dentry *parent = d->d_parent;
struct sysfs_dirent * sd; struct sysfs_dirent *sd = d->d_fsdata;
mutex_lock(&parent->d_inode->i_mutex); mutex_lock(&parent->d_inode->i_mutex);
d_delete(d);
sd = d->d_fsdata;
list_del_init(&sd->s_sibling); list_del_init(&sd->s_sibling);
if (d->d_inode)
simple_rmdir(parent->d_inode,d);
pr_debug(" o %s removing done (%d)\n",d->d_name.name, pr_debug(" o %s removing done (%d)\n",d->d_name.name,
atomic_read(&d->d_count)); atomic_read(&d->d_count));
mutex_unlock(&parent->d_inode->i_mutex); mutex_unlock(&parent->d_inode->i_mutex);
dput(parent);
sysfs_drop_dentry(sd);
sysfs_deactivate(sd); sysfs_deactivate(sd);
sysfs_put(sd); sysfs_put(sd);
} }
...@@ -404,7 +401,6 @@ static void __sysfs_remove_dir(struct dentry *dentry) ...@@ -404,7 +401,6 @@ static void __sysfs_remove_dir(struct dentry *dentry)
struct sysfs_dirent * parent_sd; struct sysfs_dirent * parent_sd;
struct sysfs_dirent * sd, * tmp; struct sysfs_dirent * sd, * tmp;
dget(dentry);
if (!dentry) if (!dentry)
return; return;
...@@ -415,21 +411,17 @@ static void __sysfs_remove_dir(struct dentry *dentry) ...@@ -415,21 +411,17 @@ static void __sysfs_remove_dir(struct dentry *dentry)
if (!sd->s_type || !(sd->s_type & SYSFS_NOT_PINNED)) if (!sd->s_type || !(sd->s_type & SYSFS_NOT_PINNED))
continue; continue;
list_move(&sd->s_sibling, &removed); list_move(&sd->s_sibling, &removed);
sysfs_drop_dentry(sd, dentry);
} }
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_for_each_entry_safe(sd, tmp, &removed, s_sibling) {
list_del_init(&sd->s_sibling); list_del_init(&sd->s_sibling);
sysfs_drop_dentry(sd);
sysfs_deactivate(sd); sysfs_deactivate(sd);
sysfs_put(sd); sysfs_put(sd);
} }
remove_dir(dentry); remove_dir(dentry);
/**
* Drop reference from dget() on entrance.
*/
dput(dentry);
} }
/** /**
......
...@@ -191,13 +191,25 @@ int sysfs_create(struct sysfs_dirent *sd, struct dentry *dentry, int mode, ...@@ -191,13 +191,25 @@ int sysfs_create(struct sysfs_dirent *sd, struct dentry *dentry, int mode,
return error; return error;
} }
/* /**
* Unhashes the dentry corresponding to given sysfs_dirent * sysfs_drop_dentry - drop dentry for the specified sysfs_dirent
* Called with parent inode's i_mutex held. * @sd: target sysfs_dirent
*
* Drop dentry for @sd. @sd must have been unlinked from its
* parent on entry to this function such that it can't be looked
* up anymore.
*
* @sd->s_dentry which is protected with sysfs_lock points to the
* currently associated dentry but we're not holding a reference
* to it and racing with dput(). Grab dcache_lock and verify
* dentry before dropping it. If @sd->s_dentry is NULL or dput()
* beats us, no need to bother.
*/ */
void sysfs_drop_dentry(struct sysfs_dirent * sd, struct dentry * parent) void sysfs_drop_dentry(struct sysfs_dirent *sd)
{ {
struct dentry *dentry = NULL; struct dentry *dentry = NULL, *parent = NULL;
struct inode *dir;
struct timespec curtime;
/* We're not holding a reference to ->s_dentry dentry but the /* We're not holding a reference to ->s_dentry dentry but the
* field will stay valid as long as sysfs_lock is held. * field will stay valid as long as sysfs_lock is held.
...@@ -205,30 +217,57 @@ void sysfs_drop_dentry(struct sysfs_dirent * sd, struct dentry * parent) ...@@ -205,30 +217,57 @@ void sysfs_drop_dentry(struct sysfs_dirent * sd, struct dentry * parent)
spin_lock(&sysfs_lock); spin_lock(&sysfs_lock);
spin_lock(&dcache_lock); spin_lock(&dcache_lock);
/* dget dentry if it's still alive */ if (sd->s_dentry && sd->s_dentry->d_inode) {
if (sd->s_dentry && sd->s_dentry->d_inode) /* get dentry if it's there and dput() didn't kill it yet */
dentry = dget_locked(sd->s_dentry); dentry = dget_locked(sd->s_dentry);
parent = dentry->d_parent;
} else if (sd->s_parent->s_dentry->d_inode) {
/* We need to update the parent even if dentry for the
* victim itself doesn't exist.
*/
parent = dget_locked(sd->s_parent->s_dentry);
}
/* drop */
if (dentry) {
spin_lock(&dentry->d_lock);
__d_drop(dentry);
spin_unlock(&dentry->d_lock);
}
spin_unlock(&dcache_lock); spin_unlock(&dcache_lock);
spin_unlock(&sysfs_lock); spin_unlock(&sysfs_lock);
/* drop dentry */ /* nothing to do if the parent isn't in dcache */
if (!parent)
return;
/* adjust nlink and update timestamp */
dir = parent->d_inode;
mutex_lock(&dir->i_mutex);
curtime = CURRENT_TIME;
dir->i_ctime = dir->i_mtime = curtime;
if (dentry) { if (dentry) {
spin_lock(&dcache_lock); dentry->d_inode->i_ctime = curtime;
spin_lock(&dentry->d_lock); drop_nlink(dentry->d_inode);
if (!d_unhashed(dentry) && dentry->d_inode) { if (sd->s_type & SYSFS_DIR) {
dget_locked(dentry); drop_nlink(dentry->d_inode);
__d_drop(dentry); drop_nlink(dir);
spin_unlock(&dentry->d_lock); /* XXX: unpin if directory, this will go away soon */
spin_unlock(&dcache_lock); dput(dentry);
simple_unlink(parent->d_inode, dentry);
} else {
spin_unlock(&dentry->d_lock);
spin_unlock(&dcache_lock);
} }
}
mutex_unlock(&dir->i_mutex);
/* bye bye */
if (dentry)
dput(dentry); dput(dentry);
} else
dput(parent);
} }
int sysfs_hash_and_remove(struct dentry * dir, const char * name) int sysfs_hash_and_remove(struct dentry * dir, const char * name)
...@@ -251,7 +290,6 @@ int sysfs_hash_and_remove(struct dentry * dir, const char * name) ...@@ -251,7 +290,6 @@ int sysfs_hash_and_remove(struct dentry * dir, const char * name)
continue; continue;
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);
found = 1; found = 1;
break; break;
} }
...@@ -261,7 +299,9 @@ int sysfs_hash_and_remove(struct dentry * dir, const char * name) ...@@ -261,7 +299,9 @@ int sysfs_hash_and_remove(struct dentry * dir, const char * name)
if (!found) if (!found)
return -ENOENT; return -ENOENT;
sysfs_drop_dentry(sd);
sysfs_deactivate(sd); sysfs_deactivate(sd);
sysfs_put(sd); sysfs_put(sd);
return 0; return 0;
} }
...@@ -76,7 +76,7 @@ extern struct sysfs_dirent *sysfs_find(struct sysfs_dirent *dir, const char * na ...@@ -76,7 +76,7 @@ extern struct sysfs_dirent *sysfs_find(struct sysfs_dirent *dir, const char * na
extern int sysfs_create_subdir(struct kobject *, const char *, struct dentry **); extern int sysfs_create_subdir(struct kobject *, const char *, struct dentry **);
extern void sysfs_remove_subdir(struct dentry *); extern void sysfs_remove_subdir(struct dentry *);
extern void sysfs_drop_dentry(struct sysfs_dirent *sd, struct dentry *parent); extern void sysfs_drop_dentry(struct sysfs_dirent *sd);
extern int sysfs_setattr(struct dentry *dentry, struct iattr *iattr); extern int sysfs_setattr(struct dentry *dentry, struct iattr *iattr);
extern spinlock_t sysfs_lock; extern spinlock_t sysfs_lock;
......
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