Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

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

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
Loading
Loading
Loading
Loading
+5 −13
Original line number Diff line number Diff line
@@ -372,22 +372,19 @@ const struct inode_operations sysfs_dir_inode_operations = {

static void remove_dir(struct dentry * d)
{
	struct dentry * parent = dget(d->d_parent);
	struct sysfs_dirent * sd;
	struct dentry *parent = d->d_parent;
	struct sysfs_dirent *sd = d->d_fsdata;

	mutex_lock(&parent->d_inode->i_mutex);
	d_delete(d);
	sd = d->d_fsdata;

 	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,
		 atomic_read(&d->d_count));

	mutex_unlock(&parent->d_inode->i_mutex);
	dput(parent);

	sysfs_drop_dentry(sd);
	sysfs_deactivate(sd);
	sysfs_put(sd);
}
@@ -404,7 +401,6 @@ static void __sysfs_remove_dir(struct dentry *dentry)
	struct sysfs_dirent * parent_sd;
	struct sysfs_dirent * sd, * tmp;

	dget(dentry);
	if (!dentry)
		return;

@@ -415,21 +411,17 @@ static void __sysfs_remove_dir(struct dentry *dentry)
		if (!sd->s_type || !(sd->s_type & SYSFS_NOT_PINNED))
			continue;
		list_move(&sd->s_sibling, &removed);
		sysfs_drop_dentry(sd, dentry);
	}
	mutex_unlock(&dentry->d_inode->i_mutex);

	list_for_each_entry_safe(sd, tmp, &removed, s_sibling) {
		list_del_init(&sd->s_sibling);
		sysfs_drop_dentry(sd);
		sysfs_deactivate(sd);
		sysfs_put(sd);
	}

	remove_dir(dentry);
	/**
	 * Drop reference from dget() on entrance.
	 */
	dput(dentry);
}

/**
+61 −21
Original line number Diff line number Diff line
@@ -191,13 +191,25 @@ int sysfs_create(struct sysfs_dirent *sd, struct dentry *dentry, int mode,
	return error;
}

/*
 * Unhashes the dentry corresponding to given sysfs_dirent
 * Called with parent inode's i_mutex held.
/**
 *	sysfs_drop_dentry - drop dentry for the specified sysfs_dirent
 *	@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
	 * field will stay valid as long as sysfs_lock is held.
@@ -205,32 +217,59 @@ void sysfs_drop_dentry(struct sysfs_dirent * sd, struct dentry * parent)
	spin_lock(&sysfs_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);
		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);
	}

	spin_unlock(&dcache_lock);
	spin_unlock(&sysfs_lock);

	/* drop dentry */
	/* drop */
	if (dentry) {
		spin_lock(&dcache_lock);
		spin_lock(&dentry->d_lock);
		if (!d_unhashed(dentry) && dentry->d_inode) {
			dget_locked(dentry);
		__d_drop(dentry);
		spin_unlock(&dentry->d_lock);
			spin_unlock(&dcache_lock);
			simple_unlink(parent->d_inode, dentry);
		} else {
			spin_unlock(&dentry->d_lock);
			spin_unlock(&dcache_lock);
	}

	spin_unlock(&dcache_lock);
	spin_unlock(&sysfs_lock);

	/* 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) {
		dentry->d_inode->i_ctime = curtime;
		drop_nlink(dentry->d_inode);
		if (sd->s_type & SYSFS_DIR) {
			drop_nlink(dentry->d_inode);
			drop_nlink(dir);
			/* XXX: unpin if directory, this will go away soon */
			dput(dentry);
		}
	}

	mutex_unlock(&dir->i_mutex);

	/* bye bye */
	if (dentry)
		dput(dentry);
	else
		dput(parent);
}

int sysfs_hash_and_remove(struct dentry * dir, const char * name)
{
	struct sysfs_dirent * sd;
@@ -251,7 +290,6 @@ int sysfs_hash_and_remove(struct dentry * dir, const char * name)
			continue;
		if (!strcmp(sd->s_name, name)) {
			list_del_init(&sd->s_sibling);
			sysfs_drop_dentry(sd, dir);
			found = 1;
			break;
		}
@@ -261,7 +299,9 @@ int sysfs_hash_and_remove(struct dentry * dir, const char * name)
	if (!found)
		return -ENOENT;

	sysfs_drop_dentry(sd);
	sysfs_deactivate(sd);
	sysfs_put(sd);

	return 0;
}
+1 −1
Original line number Diff line number Diff line
@@ -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 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 spinlock_t sysfs_lock;