[RFC PATCH 3/4] Lookup changes to support union mount.

From: Bharata B Rao
Date: Wed Jun 20 2007 - 01:46:20 EST


From: Bharata B Rao <bharata@xxxxxxxxxxxxxxxxxx>
Subject: Lookup changes to support union mount.

Adds support for looking up dentries inside a union mount point.

This patch modifies the do_lookup() routine to look beyond the top layer for
union mount points/directories. Union mount versions of dcache and real lookup
routines are added to support this. The lookup routines are modified to build a
new union stack when looking up a common named directory in the union stack.

TODO: Check if lookup_hash() and friends also need to know about union mount.

Signed-off-by: Bharata B Rao <bharata@xxxxxxxxxxxxxxxxxx>
---
fs/dcache.c | 53 ++++++++++++++++++
fs/namei.c | 137 +++++++++++++++++++++++++++++++++++++++++++++++--
include/linux/dcache.h | 1
3 files changed, 187 insertions(+), 4 deletions(-)

--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -1312,6 +1312,59 @@ next:
return found;
}

+/*
+ * Looks for the given @name in dcache by walking through all the layers
+ * of the union stack, starting from the top.
+ * FIXME: If we don't find the dentry in a upper layer, we descend to the
+ * next layer. So there is a chance to miss this dentry in the top layer
+ * if this is the _first_ time lookup of the dentry in this layer. A real
+ * lookup might have fetched a valid dentry in this layer itself, while we
+ * chose to descend to the next lower layer. One solution is not have this
+ * function itself, do the toplevel lookup in dcache and if it fails proceed
+ * to real_lookup_union() directly.
+ */
+struct dentry *__d_lookup_union(struct nameidata *nd, struct qstr *name)
+{
+ struct dentry *dentry;
+ struct nameidata nd_tmp;
+ struct vfsmount *mnt = mntget(nd->mnt);
+ struct qstr this;
+ int err;
+
+ nd_tmp.mnt = nd->mnt;
+ nd_tmp.dentry = nd->dentry;
+
+ this.name = name->name;
+ this.len = name->len;
+ this.hash = name->hash;
+
+ do {
+ /* d_hash() is a repetition for the top layer. */
+ if (nd_tmp.dentry->d_op && nd_tmp.dentry->d_op->d_hash) {
+ err = nd_tmp.dentry->d_op->d_hash(nd_tmp.dentry, &this);
+ if (err < 0)
+ goto out;
+ }
+
+ dentry = __d_lookup(nd_tmp.dentry, &this);
+ if (dentry) {
+ if (dentry->d_inode) {
+ if (nd->mnt != nd_tmp.mnt) {
+ mntput(nd->mnt);
+ nd->mnt = mntget(nd_tmp.mnt);
+ }
+ mntput(mnt);
+ return dentry;
+ } else {
+ dput(dentry);
+ }
+ }
+ } while (next_union_mount(&nd_tmp));
+out:
+ mntput(mnt);
+ return NULL;
+}
+
/**
* d_hash_and_lookup - hash the qstr then search for a dentry
* @dir: Directory to search in
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -515,6 +515,127 @@ static struct dentry * real_lookup(struc
return result;
}

+/*
+ * Walks down the parent's union stack looking for the given @name.
+ * Builds unions at subdirectory levels if needed.
+ */
+static struct dentry *real_lookup_union(struct nameidata *nd, struct qstr *name)
+{
+ struct dentry *dentry, *topmost, *prev = NULL;
+ struct nameidata nd_tmp;
+ struct vfsmount *mnt_prev = NULL;
+ struct vfsmount *mnt = mntget(nd->mnt);
+
+ topmost = real_lookup(nd->dentry, name, nd);
+ if (IS_ERR(topmost))
+ goto out;
+
+ /* Found a vaild non-directory dentry in the topmost layer, return. */
+ if (topmost->d_inode && !S_ISDIR(topmost->d_inode->i_mode))
+ goto out;
+
+ nd_tmp.mnt = nd->mnt;
+ nd_tmp.dentry = nd->dentry;
+
+ /*
+ * At this point we either have a negative dentry or a directory
+ * as the topmost dentry. Continue to lookup in the bottom layers.
+ */
+ while (next_union_mount(&nd_tmp)) {
+ dentry = real_lookup(nd_tmp.dentry, name, &nd_tmp);
+ /*
+ * If there is an error, return the dentry from
+ * the topmost layer.
+ */
+ if (IS_ERR(dentry))
+ goto out;
+
+ /*
+ * Found a negative dentry in this layer, release it and
+ * continue with the next layer.
+ */
+ if (!dentry->d_inode) {
+ dput(dentry);
+ continue;
+ }
+
+ /*
+ * If we find a vaild non-directory dentry in this layer, And
+ * - if the topmost dentry is negative, return this.
+ * - or if the topmost dentry is valid (dir), return topmost.
+ */
+ if (!S_ISDIR(dentry->d_inode->i_mode)) {
+ if (!topmost->d_inode) {
+ mntput(nd->mnt);
+ nd->mnt = mntget(nd_tmp.mnt);
+ dput(topmost);
+ topmost = dentry;
+ } else {
+ dput(dentry);
+ }
+ goto out;
+ }
+
+ /*
+ * At this point, we have a directory in this layer. And
+ * - if the topmost dentry is negative, make this directory
+ * the topmost dentry.
+ * - or if topmost dentry is valid, union the two directories.
+ */
+ if (!topmost->d_inode) {
+ mntput(nd->mnt);
+ nd->mnt = mntget(nd_tmp.mnt);
+ dput(topmost);
+ topmost = dget(dentry);
+ } else {
+ struct union_mount *u;
+ struct dentry *top_dentry = prev ? prev : topmost;
+ struct vfsmount *top_mnt = mnt_prev ? mnt_prev :
+ nd->mnt;
+ u = alloc_union_mount(top_mnt, top_dentry, nd_tmp.mnt,
+ dentry);
+ if (!u) {
+ dput(dentry);
+ goto out;
+ }
+ spin_lock(&vfsmount_lock);
+ attach_mnt_union(u);
+ spin_unlock(&vfsmount_lock);
+ /*
+ * If the lower layer we found has a layer below
+ * it, no need to continue the lookup, return
+ * with the topmost we found.
+ * Else remember this layer so that this becomes
+ * the top layer for the next union.
+ */
+ if (next_union_mount_exists(nd_tmp.mnt, dentry)) {
+ dput(dentry);
+ goto out;
+ } else {
+ if (prev)
+ dput(prev);
+ prev = dget(dentry);
+ if (mnt_prev)
+ mntput(mnt_prev);
+ mnt_prev = mntget(nd_tmp.mnt);
+ }
+ }
+
+ /*
+ * Release the dentry from this layer and continue
+ * the lookup in the next lower layer.
+ */
+ dput(dentry);
+ }
+out:
+ if (prev)
+ dput(prev);
+ if (mnt_prev)
+ mntput(mnt_prev);
+ mntput(mnt);
+ return topmost;
+}
+
static int __emul_lookup_dentry(const char *, struct nameidata *);

/* SMP-safe */
@@ -769,21 +890,29 @@ static __always_inline void follow_dotdo
static int do_lookup(struct nameidata *nd, struct qstr *name,
struct path *path)
{
- struct vfsmount *mnt = nd->mnt;
- struct dentry *dentry = __d_lookup(nd->dentry, name);
+ struct dentry *dentry;
+
+ if (IS_MNT_UNION(nd->mnt))
+ dentry = __d_lookup_union(nd, name);
+ else
+ dentry = __d_lookup(nd->dentry, name);

if (!dentry)
goto need_lookup;
if (dentry->d_op && dentry->d_op->d_revalidate)
goto need_revalidate;
done:
- path->mnt = mnt;
+ path->mnt = nd->mnt;
path->dentry = dentry;
__follow_mount(path);
return 0;

need_lookup:
- dentry = real_lookup(nd->dentry, name, nd);
+ if (IS_MNT_UNION(nd->mnt))
+ dentry = real_lookup_union(nd, name);
+ else
+ dentry = real_lookup(nd->dentry, name, nd);
+
if (IS_ERR(dentry))
goto fail;
goto done;
--- a/include/linux/dcache.h
+++ b/include/linux/dcache.h
@@ -289,6 +289,7 @@ extern void d_move(struct dentry *, stru
/* appendix may either be NULL or be used for transname suffixes */
extern struct dentry * d_lookup(struct dentry *, struct qstr *);
extern struct dentry * __d_lookup(struct dentry *, struct qstr *);
+extern struct dentry *__d_lookup_union(struct nameidata *nd, struct qstr *name);
extern struct dentry * d_hash_and_lookup(struct dentry *, struct qstr *);

/* validate "insecure" dentry pointer */
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/