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

Commit 5a18fff2 authored by Al Viro's avatar Al Viro
Browse files

untangle do_lookup()



That thing has devolved into rats nest of gotos; sane use of unlikely()
gets rid of that horror and gives much more readable structure:
	* make a fast attempt to find a dentry; false negatives are OK.
In RCU mode if everything went fine, we are done, otherwise just drop
out of RCU.  If we'd done (RCU) ->d_revalidate() and it had not refused
outright (i.e. didn't give us -ECHILD), remember its result.
	* now we are not in RCU mode and hopefully have a dentry.  If we
do not, lock parent, do full d_lookup() and if that has not found anything,
allocate and call ->lookup().  If we'd done that ->lookup(), remember that
dentry is good and we don't need to revalidate it.
	* now we have a dentry.  If it has ->d_revalidate() and we can't
skip it, call it.
	* hopefully dentry is good; if not, either fail (in case of error)
or try to invalidate it.  If d_invalidate() has succeeded, drop it and
retry everything as if original attempt had not found a dentry.
	* now we can finish it up - deal with mountpoint crossing and
automount.

Signed-off-by: default avatarAl Viro <viro@zeniv.linux.org.uk>
parent 40b39136
Loading
Loading
Loading
Loading
+56 −85
Original line number Diff line number Diff line
@@ -589,29 +589,6 @@ do_revalidate(struct dentry *dentry, struct nameidata *nd)
	return dentry;
}

static inline struct dentry *
do_revalidate_rcu(struct dentry *dentry, struct nameidata *nd)
{
	int status = d_revalidate(dentry, nd);
	if (likely(status > 0))
		return dentry;
	if (status == -ECHILD) {
		if (nameidata_dentry_drop_rcu(nd, dentry))
			return ERR_PTR(-ECHILD);
		return do_revalidate(dentry, nd);
	}
	if (status < 0)
		return ERR_PTR(status);
	/* Don't d_invalidate in rcu-walk mode */
	if (nameidata_dentry_drop_rcu(nd, dentry))
		return ERR_PTR(-ECHILD);
	if (!d_invalidate(dentry)) {
		dput(dentry);
		dentry = NULL;
	}
	return dentry;
}

/*
 * handle_reval_path - force revalidation of a dentry
 *
@@ -1213,7 +1190,8 @@ static int do_lookup(struct nameidata *nd, struct qstr *name,
{
	struct vfsmount *mnt = nd->path.mnt;
	struct dentry *dentry, *parent = nd->path.dentry;
	struct inode *dir;
	int need_reval = 1;
	int status = 1;
	int err;

	/*
@@ -1223,90 +1201,83 @@ static int do_lookup(struct nameidata *nd, struct qstr *name,
	 */
	if (nd->flags & LOOKUP_RCU) {
		unsigned seq;

		*inode = nd->inode;
		dentry = __d_lookup_rcu(parent, name, &seq, inode);
		if (!dentry) {
			if (nameidata_drop_rcu(nd))
				return -ECHILD;
			goto need_lookup;
		}
		if (!dentry)
			goto unlazy;

		/* Memory barrier in read_seqcount_begin of child is enough */
		if (__read_seqcount_retry(&parent->d_seq, nd->seq))
			return -ECHILD;

		nd->seq = seq;

		if (unlikely(dentry->d_flags & DCACHE_OP_REVALIDATE)) {
			dentry = do_revalidate_rcu(dentry, nd);
			if (!dentry)
				goto need_lookup;
			if (IS_ERR(dentry))
				goto fail;
			if (!(nd->flags & LOOKUP_RCU))
				goto done;
			status = d_revalidate(dentry, nd);
			if (unlikely(status <= 0)) {
				if (status != -ECHILD)
					need_reval = 0;
				goto unlazy;
			}
		}
		path->mnt = mnt;
		path->dentry = dentry;
		if (likely(__follow_mount_rcu(nd, path, inode, false)))
			return 0;
unlazy:
		if (dentry) {
			if (nameidata_dentry_drop_rcu(nd, dentry))
				return -ECHILD;
		} else {
			if (nameidata_drop_rcu(nd))
				return -ECHILD;
		/* fallthru */
		}
	} else {
		dentry = __d_lookup(parent, name);
	if (!dentry)
		goto need_lookup;
found:
	if (unlikely(dentry->d_flags & DCACHE_OP_REVALIDATE)) {
		dentry = do_revalidate(dentry, nd);
		if (!dentry)
			goto need_lookup;
		if (IS_ERR(dentry))
			goto fail;
	}
done:
	path->mnt = mnt;
	path->dentry = dentry;
	err = follow_managed(path, nd->flags);
	if (unlikely(err < 0)) {
		path_put_conditional(path, nd);
		return err;
	}
	*inode = path->dentry->d_inode;
	return 0;

need_lookup:
	dir = parent->d_inode;
retry:
	if (unlikely(!dentry)) {
		struct inode *dir = parent->d_inode;
		BUG_ON(nd->inode != dir);

		mutex_lock(&dir->i_mutex);
	/*
	 * First re-do the cached lookup just in case it was created
	 * while we waited for the directory semaphore, or the first
	 * lookup failed due to an unrelated rename.
	 *
	 * This could use version numbering or similar to avoid unnecessary
	 * cache lookups, but then we'd have to do the first lookup in the
	 * non-racy way. However in the common case here, everything should
	 * be hot in cache, so would it be a big win?
	 */
		dentry = d_lookup(parent, name);
		if (likely(!dentry)) {
			dentry = d_alloc_and_lookup(parent, name, nd);
			if (IS_ERR(dentry)) {
				mutex_unlock(&dir->i_mutex);
		if (IS_ERR(dentry))
			goto fail;
		goto done;
				return PTR_ERR(dentry);
			}
			/* known good */
			need_reval = 0;
			status = 1;
		}
	/*
	 * Uhhuh! Nasty case: the cache was re-populated while
	 * we waited on the semaphore. Need to revalidate.
	 */
		mutex_unlock(&dir->i_mutex);
	goto found;
	}
	if (unlikely(dentry->d_flags & DCACHE_OP_REVALIDATE) && need_reval)
		status = d_revalidate(dentry, nd);
	if (unlikely(status <= 0)) {
		if (status < 0) {
			dput(dentry);
			return status;
		}
		if (!d_invalidate(dentry)) {
			dput(dentry);
			dentry = NULL;
			need_reval = 1;
			goto retry;
		}
	}

fail:
	return PTR_ERR(dentry);
	path->mnt = mnt;
	path->dentry = dentry;
	err = follow_managed(path, nd->flags);
	if (unlikely(err < 0)) {
		path_put_conditional(path, nd);
		return err;
	}
	*inode = path->dentry->d_inode;
	return 0;
}

static inline int may_lookup(struct nameidata *nd)