[RFC][PATCH 11/14] VFS whiteout handling

Bharata B Rao
2007-05-14T09:37:07Z

From: Jan Blunck <j.blunck@tu-harburg.de>
Subject: VFS whiteout handling

Introduce white-out handling in the VFS.

Signed-off-by: Jan Blunck <j.blunck@tu-harburg.de>
Signed-off-by: Bharata B Rao <bharata@linux.vnet.ibm.com>
---
fs/inode.c | 17 +
fs/namei.c | 476 ++++++++++++++++++++++++++++++++++++++++++++++++--
fs/readdir.c | 10 +
fs/union.c | 104 ++++++++++
include/linux/fs.h | 4
include/linux/union.h | 6
6 files changed, 605 insertions(+), 12 deletions(-)

--- a/fs/inode.c
+++ b/fs/inode.c
@@ -1421,6 +1421,21 @@ void __init inode_init(unsigned long mem
INIT_HLIST_HEAD(&inode_hashtable[loop]);
}

+/*
+ * Dummy default file-operations:
+ * Never open a whiteout. This is always a bug.
+ */
+static int whiteout_no_open(struct inode *irrelevant, struct file *dontcare)
+{
+ printk("Attemp to open a whiteout!\n");
+ WARN_ON(1);
+ return -ENXIO;
+}
+
+static struct file_operations def_wht_fops = {
+ .open = whiteout_no_open,
+};
+
void init_special_inode(struct inode *inode, umode_t mode, dev_t rdev)
{
inode->i_mode = mode;
@@ -1434,6 +1449,8 @@ void init_special_inode(struct inode *in
inode->i_fop = &def_fifo_fops;
else if (S_ISSOCK(mode))
inode->i_fop = &bad_sock_fops;
+ else if (S_ISWHT(mode))
+ inode->i_fop = &def_wht_fops;
else
printk(KERN_DEBUG "init_special_inode: bogus i_mode (%o)\n",
mode);
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -969,7 +969,7 @@ static fastcall int __link_path_walk(con

err = -ENOENT;
inode = next.dentry->d_inode;
- if (!inode)
+ if (!inode || S_ISWHT(inode->i_mode))
goto out_dput;
err = -ENOTDIR;
if (!inode->i_op)
@@ -1043,6 +1043,12 @@ last_component:
err = -ENOENT;
if (!inode)
break;
+ if (S_ISWHT(inode->i_mode)) {
+ UM_DEBUG_UID("found a whiteout\n");
+ break;
+ //if (!(nd->flags & LOOKUP_WHT))
+ // break;
+ }
if (lookup_flags & LOOKUP_DIRECTORY) {
err = -ENOTDIR;
if (!inode->i_op || !inode->i_op->lookup)
@@ -1556,7 +1562,7 @@ static int may_delete(struct inode *dir,
static inline int may_create(struct inode *dir, struct dentry *child,
struct nameidata *nd)
{
- if (child->d_inode)
+ if (child->d_inode && !S_ISWHT(child->d_inode->i_mode))
return -EEXIST;
if (IS_DEADDIR(dir))
return -ENOENT;
@@ -1623,6 +1629,82 @@ void unlock_rename(struct dentry *p1, st
}
}

+/*
+ * __vfs_unlink_whiteout - Unlink a single whiteout from the system
+ * @dir: parent directory
+ * @dentry: the whiteout itself
+ *
+ * This is for unlinking a single whiteout. Don't use vfs_unlink() because we
+ * don't want any notification stuff etc. but basically it is the same stuff.
+ */
+static int
+__vfs_unlink_whiteout(struct inode *dir, struct dentry *dentry)
+{
+ int error = may_delete(dir, dentry, 0);
+
+ if (error)
+ return error;
+
+ if (!dir->i_op || !dir->i_op->unlink)
+ return -EPERM;
+
+ DQUOT_INIT(dir);
+
+ mutex_lock(&dentry->d_inode->i_mutex);
+ if (d_mountpoint(dentry))
+ error = -EBUSY;
+ else {
+ error = security_inode_unlink(dir, dentry);
+ if (!error)
+ error = dir->i_op->unlink(dir, dentry);
+ }
+ mutex_unlock(&dentry->d_inode->i_mutex);
+
+ /* We don't d_delete() NFS sillyrenamed files--they still exist. */
+ if (!error && !(dentry->d_flags & DCACHE_NFSFS_RENAMED)) {
+ d_delete(dentry);
+ //inode_dir_notify(dir, DN_DELETE);
+ }
+ return error;
+}
+
+/*
+ * vfs_unlink_whiteout - unlink and relookup the whiteout
+ *
+ * This is what you want to call from vfs_* functions to remove a whiteout. It
+ * unlinks the whiteout dentry and relookups it afterwards.
+ */
+static int
+vfs_unlink_whiteout(struct inode *dir, struct dentry **dp)
+{
+ struct dentry *dentry = *dp;
+ struct dentry *parent = dentry->d_parent;
+ struct qstr name;
+ int error;
+
+ BUG_ON(dir != parent->d_inode);
+
+ error = -ENOMEM;
+ name.name = kmalloc(dentry->d_name.len, GFP_KERNEL);
+ if (!name.name)
+ goto out;
+ strncpy((char *)name.name, dentry->d_name.name, dentry->d_name.len);
+ name.len = dentry->d_name.len;
+ name.hash = dentry->d_name.hash;
+
+ error = __vfs_unlink_whiteout(dir, dentry);
+ if (error)
+ goto out_freename;
+
+ __dput_single(dentry);
+ *dp = __lookup_hash_single(&name, parent, NULL);
+ BUG_ON(IS_ERR(*dp)); /* Hmm, very hard response here */
+out_freename:
+ kfree(name.name);
+out:
+ return error;
+}
+
int vfs_create(struct inode *dir, struct dentry *dentry, int mode,
struct nameidata *nd)
{
@@ -1638,6 +1720,13 @@ int vfs_create(struct inode *dir, struct
error = security_inode_create(dir, dentry, mode);
if (error)
return error;
+
+ if (dentry->d_inode && S_ISWHT(dentry->d_inode->i_mode)) {
+ error = vfs_unlink_whiteout(dir, &dentry);
+ if (error)
+ return error;
+ }
+
DQUOT_INIT(dir);
error = dir->i_op->create(dir, dentry, mode, nd);
if (!error)
@@ -1833,7 +1922,14 @@ do_last:
}

/* Negative dentry, just create the file */
- if (!path.dentry->d_inode) {
+ if (!path.dentry->d_inode || S_ISWHT(path.dentry->d_inode->i_mode)) {
+ if (path.dentry->d_parent != dir) {
+ UM_DEBUG_UID("found a lower layers whiteout\n");
+ dput(path.dentry);
+ path.dentry = __lookup_hash_single(&nd->last, dir, nd);
+ goto do_last;
+ }
+
error = open_namei_create(nd, &path, flag, mode);
if (error)
goto exit;
@@ -1949,6 +2045,17 @@ do_link:
struct dentry *lookup_create(struct nameidata *nd, int is_dir)
{
struct dentry *dentry = ERR_PTR(-EEXIST);
+ int error;
+
+ if (union_is_member(nd->dentry, nd->mnt)) {
+ error = union_relookup_topmost(nd, nd->flags & ~LOOKUP_PARENT);
+ if (error) {
+ /* FIXME: This really sucks */
+ mutex_lock_nested(&nd->dentry->d_inode->i_mutex,
+ I_MUTEX_PARENT);
+ goto fail;
+ }
+ }

mutex_lock_nested(&nd->dentry->d_inode->i_mutex, I_MUTEX_PARENT);
/*
@@ -1968,6 +2075,15 @@ struct dentry *lookup_create(struct name
if (IS_ERR(dentry))
goto fail;

+ /* Special case - we found a whiteout */
+ if (dentry->d_inode && S_ISWHT(dentry->d_inode->i_mode)) {
+ if (dentry->d_parent != nd->dentry) {
+ UM_DEBUG_UID("found a lower layers whiteout\n");
+ dput(dentry);
+ dentry = __lookup_hash_single(&nd->last,nd->dentry,nd);
+ }
+ }
+
/*
* Special case - lookup gave negative, but... we had foo/bar/
* From the vfs_mknod() POV we just have a negative dentry -
@@ -2002,6 +2118,12 @@ int vfs_mknod(struct inode *dir, struct
if (error)
return error;

+ if (dentry->d_inode && S_ISWHT(dentry->d_inode->i_mode)) {
+ error = vfs_unlink_whiteout(dir, &dentry);
+ if (error)
+ return error;
+ }
+
DQUOT_INIT(dir);
error = dir->i_op->mknod(dir, dentry, mode, dev);
if (!error)
@@ -2067,6 +2189,7 @@ asmlinkage long sys_mknod(const char __u
int vfs_mkdir(struct inode *dir, struct dentry *dentry, int mode)
{
int error = may_create(dir, dentry, NULL);
+ int opaque;

if (error)
return error;
@@ -2079,10 +2202,23 @@ int vfs_mkdir(struct inode *dir, struct
if (error)
return error;

+ if (dentry->d_inode && S_ISWHT(dentry->d_inode->i_mode)) {
+ error = vfs_unlink_whiteout(dir, &dentry);
+ if (error)
+ return error;
+ opaque = 1;
+ } else
+ opaque = 0;
+
DQUOT_INIT(dir);
error = dir->i_op->mkdir(dir, dentry, mode);
- if (!error)
+ if (!error) {
fsnotify_mkdir(dir, dentry);
+#ifdef CONFIG_UNION_MOUNT
+ if (opaque && dentry->d_parent->d_overlaid)
+ dentry->d_inode->i_flags |= S_OPAQUE;
+#endif
+ }
return error;
}

@@ -2124,6 +2260,225 @@ asmlinkage long sys_mkdir(const char __u
return sys_mkdirat(AT_FDCWD, pathname, mode);
}

+/* Checks on the victiom for whiteout */
+static inline int may_whiteout(struct dentry *victim, int isdir)
+{
+ if (!victim->d_inode || S_ISWHT(victim->d_inode->i_mode))
+ return -ENOENT;
+ if (IS_APPEND(victim->d_inode) || IS_IMMUTABLE(victim->d_inode))
+ return -EPERM;
+ if (isdir) {
+ if (!S_ISDIR(victim->d_inode->i_mode))
+ return -ENOTDIR;
+ if (IS_ROOT(victim))
+ return -EBUSY;
+ if (!union_dir_is_empty(victim))
+ return -ENOTEMPTY;
+ } else if (S_ISDIR(victim->d_inode->i_mode))
+ return -EISDIR;
+ if (victim->d_flags & DCACHE_NFSFS_RENAMED)
+ return -EBUSY;
+ return 0;
+}
+
+/*
+ * We try to whiteout a dentry. dir is the parent of the whiteout.
+ * Whiteouts can be vfs_unlink'ed.
+ */
+int vfs_whiteout(struct inode *dir, struct dentry *dentry)
+{
+ int err;
+
+ BUG_ON(dentry->d_parent->d_inode != dir);
+
+ /* from may_create() */
+ if (dentry->d_inode)
+ return -EEXIST;
+ if (IS_DEADDIR(dir))
+ return -ENOENT;
+ err = permission(dir, MAY_WRITE | MAY_EXEC, NULL);
+ if (err)
+ return err;
+
+ /* from may_delete() */
+ if (IS_APPEND(dir))
+ return -EPERM;
+ /* We don't call check_sticky() here because d_inode == NULL */
+
+ if (!dir->i_op || !dir->i_op->whiteout)
+ return -EOPNOTSUPP;
+
+ err = dir->i_op->whiteout(dir, dentry);
+ /* Ignore quota and fsnotify */
+ return err;
+}
+
+/*
+ * do_whiteout - whiteout a dentry, either when removing or renaming
+ * @dentry: the dentry to whiteout
+ *
+ * This is called by the VFS when removing or renaming files on an union mount.
+ */
+static int do_whiteout(struct dentry *parent, struct dentry *dentry, int isdir)
+{
+ int err;
+ struct qstr name;
+
+ UM_DEBUG_UID("parent=\"%s\", dentry=\"%s\", isdir=%d\n",
+ parent->d_name.name, dentry->d_name.name, isdir);
+
+ err = may_whiteout(dentry, isdir);
+ if (err)
+ goto out;
+
+ err = -ENOMEM;
+ name.name = kmalloc(dentry->d_name.len, GFP_KERNEL);
+ if (!name.name)
+ goto out;
+ strncpy((char *)name.name, dentry->d_name.name, dentry->d_name.len);
+ name.len = dentry->d_name.len;
+ name.hash = dentry->d_name.hash;
+
+ /*
+ * TODO: Should we BUG_ON(dentry->d_parent != parent) ?
+ */
+ if (dentry->d_parent == parent) {
+ if (isdir)
+ err = vfs_rmdir(parent->d_inode, dentry);
+ else
+ err = vfs_unlink(parent->d_inode, dentry);
+ dput(dentry);
+ if (err)
+ goto out_freename;
+ }
+
+ /*
+ * Relookup the dentry to whiteout now. By this time, the dentry is
+ * dput'ed in vfs_rmdir or vfs_unlink and we should find a fresh
+ * negative dentry.
+ */
+ dentry = __lookup_hash_single(&name, parent, NULL);
+ err = PTR_ERR(dentry);
+ if (IS_ERR(dentry))
+ goto out_freename;
+
+ err = vfs_whiteout(parent->d_inode, dentry);
+ __dput_single(dentry);
+out_freename:
+ kfree(name.name);
+out:
+ return err;
+}
+
+static int
+__hash_one_len(const char *name, int len, struct qstr *this)
+{
+ unsigned long hash;
+ unsigned int c;
+
+ hash = init_name_hash();
+ while (len--) {
+ c = *(const unsigned char *)name++;
+ if (c == '/' || c == '\0')
+ return -EINVAL;
+ hash = partial_name_hash(c, hash);
+ }
+ this->hash = end_name_hash(hash);
+ return 0;
+}
+
+static int unlink_whiteouts_filldir(void *buf, const char *name, int namlen,
+ loff_t offset, u64 ino, unsigned int d_type)
+{
+ struct dentry *parent = buf;
+ struct dentry *dentry;
+ struct qstr this;
+ int res;
+
+ switch (namlen) {
+ case 2:
+ if (name[1] != '.')
+ break;
+ case 1:
+ if (name[0] != '.')
+ break;
+ return 0;
+ }
+
+ UM_DEBUG_UID("name=\"%s\", d_type=%d\n", name, d_type);
+
+ if (d_type != DT_WHT)
+ return 0;
+
+ this.name = name;
+ this.len = namlen;
+ res = __hash_one_len(name, namlen, &this);
+ if (res)
+ return res;
+
+ dentry = __lookup_hash_single(&this, parent, NULL);
+ if (IS_ERR(dentry))
+ return PTR_ERR(dentry);
+
+ res = __vfs_unlink_whiteout(parent->d_inode, dentry);
+ __dput_single(dentry);
+ return res;
+}
+
+/*
+ * do_unlink_whiteouts - remove all whiteouts of an "empty" directory
+ * @dentry: the directories dentry
+ *
+ * Before removing a directory from the file system, we have to make sure
+ * that there are no stale whiteouts in it. Therefore we call readdir() with
+ * a special filldir helper to remove all the whiteouts.
+ *
+ * XXX: Don't call any security and permission checks here (If we aren't
+ * allowed to go here, we shouldn't be here at all). Same with i_mutex, don't
+ * touch it here.
+ */
+static int do_unlink_whiteouts(struct dentry *dentry)
+{
+ struct file *file;
+ struct vfsmount *mnt;
+ struct inode *inode;
+ int res;
+
+ dget(dentry);
+ mnt = find_mnt(dentry);
+
+ /*
+ * FIXME: This is bad, because we really don't want to open a new
+ * file in the kernel but readdir needs a file pointer
+ */
+ file = dentry_open(dentry, mnt, O_RDWR);
+ if (IS_ERR(file)) {
+ printk(KERN_ERR "%s: dentry_open failed (%ld)\n",
+ __FUNCTION__, PTR_ERR(file));
+ return PTR_ERR(file);
+ }
+
+ inode = file->f_path.dentry->d_inode;
+
+ res = -ENOTDIR;
+ if (!file->f_op || !file->f_op->readdir)
+ goto out_fput;
+
+ res = -ENOENT;
+ if (!IS_DEADDIR(inode)) {
+ res = file->f_op->readdir(file, (void *)file->f_path.dentry,
+ unlink_whiteouts_filldir);
+ file_accessed(file);
+ }
+out_fput:
+ fput(file);
+ if (unlikely(res))
+ printk(KERN_ERR "%s: readdir failed (%d)\n",
+ __FUNCTION__, res);
+ return res;
+}
+
+
/*
* We try to drop the dentry early: we should have
* a usage count of 2 if we're the only user of this
@@ -2153,8 +2508,12 @@ void dentry_unhash(struct dentry *dentry

int vfs_rmdir(struct inode *dir, struct dentry *dentry)
{
- int error = may_delete(dir, dentry, 1);
+ int error;

+ if (!dentry->d_inode || S_ISWHT(dentry->d_inode->i_mode))
+ return -ENOENT;
+
+ error = may_delete(dir, dentry, 1);
if (error)
return error;

@@ -2170,11 +2529,15 @@ int vfs_rmdir(struct inode *dir, struct
else {
error = security_inode_rmdir(dir, dentry);
if (!error) {
+ error = do_unlink_whiteouts(dentry);
+ if (error)
+ goto out;
error = dir->i_op->rmdir(dir, dentry);
if (!error)
dentry->d_inode->i_flags |= S_DEAD;
}
}
+ out:
mutex_unlock(&dentry->d_inode->i_mutex);
if (!error) {
d_delete(dentry);
@@ -2215,8 +2578,41 @@ static long do_rmdir(int dfd, const char
error = PTR_ERR(dentry);
if (IS_ERR(dentry))
goto exit2;
- error = vfs_rmdir(nd.dentry->d_inode, dentry);
- dput(dentry);
+
+ if (!union_is_member(nd.dentry, nd.mnt)) {
+ /* Not a member of union, normal removal */
+ error = vfs_rmdir(nd.dentry->d_inode, dentry);
+ dput(dentry);
+ goto exit2;
+ }
+
+ if (dentry->d_parent == nd.dentry) {
+ /*
+ * Topmost dentry of the union. Check if there
+ * is a dentry of same name in the lower layers.
+ * If so create a whiteout before unlinking.
+ * Else normal removal.
+ */
+ if (present_in_lower(dentry, &nd))
+ error = do_whiteout(nd.dentry, dentry, 1);
+ else {
+ error = vfs_rmdir(nd.dentry->d_inode, dentry);
+ dput(dentry);
+ }
+ } else {
+ /*
+ * Lower layer dentry of the union. Relookup
+ * the dentry in the top layer(which should return
+ * a negative dentry) create a whiteout there.
+ */
+ dput(dentry);
+ dentry = __lookup_hash_single(&nd.last, nd.dentry, &nd);
+ error = PTR_ERR(dentry);
+ if (IS_ERR(dentry))
+ goto exit2;
+ error = vfs_whiteout(nd.dentry->d_inode, dentry);
+ __dput_single(dentry);
+ }
exit2:
mutex_unlock(&nd.dentry->d_inode->i_mutex);
exit1:
@@ -2295,10 +2691,44 @@ static long do_unlinkat(int dfd, const c
inode = dentry->d_inode;
if (inode)
atomic_inc(&inode->i_count);
- error = vfs_unlink(nd.dentry->d_inode, dentry);
- exit2:
- dput(dentry);
+
+ if (!union_is_member(nd.dentry, nd.mnt)) {
+ /* Not a member of union, normal removal */
+ error = vfs_unlink(nd.dentry->d_inode, dentry);
+ dput(dentry);
+ goto exit2;
+ }
+
+ /* TODO: fix this code duplication with do_rmdir() */
+ if (dentry->d_parent == nd.dentry) {
+ /*
+ * Topmost dentry of the union. Check if there
+ * is a dentry of same name in the lower layers.
+ * If so create a whiteout before unlinking.
+ * Else normal removal.
+ */
+ if (present_in_lower(dentry, &nd))
+ error = do_whiteout(nd.dentry, dentry, 0);
+ else {
+ error = vfs_unlink(nd.dentry->d_inode, dentry);
+ dput(dentry);
+ }
+ } else {
+ /*
+ * Lower layer dentry of the union. Relookup
+ * the dentry in the top layer(which should return
+ * a negative dentry) create a whiteout there.
+ */
+ dput(dentry);
+ dentry = __lookup_hash_single(&nd.last, nd.dentry, &nd);
+ error = PTR_ERR(dentry);
+ if (IS_ERR(dentry))
+ goto exit2;
+ error = vfs_whiteout(nd.dentry->d_inode, dentry);
+ __dput_single(dentry);
+ }
}
+exit2:
mutex_unlock(&nd.dentry->d_inode->i_mutex);
if (inode)
iput(inode); /* truncate the inode here */
@@ -2311,6 +2741,7 @@ exit:
slashes:
error = !dentry->d_inode ? -ENOENT :
S_ISDIR(dentry->d_inode->i_mode) ? -EISDIR : -ENOTDIR;
+ dput(dentry);
goto exit2;
}

@@ -2344,6 +2775,12 @@ int vfs_symlink(struct inode *dir, struc
if (error)
return error;

+ if (dentry->d_inode && S_ISWHT(dentry->d_inode->i_mode)) {
+ error = vfs_unlink_whiteout(dir, &dentry);
+ if (error)
+ return error;
+ }
+
DQUOT_INIT(dir);
error = dir->i_op->symlink(dir, dentry, oldname);
if (!error)
@@ -2398,7 +2835,7 @@ int vfs_link(struct dentry *old_dentry,
struct inode *inode = old_dentry->d_inode;
int error;

- if (!inode)
+ if (!inode || S_ISWHT(inode->i_mode))
return -ENOENT;

error = may_create(dir, new_dentry, NULL);
@@ -2674,7 +3111,7 @@ static int do_rename(int olddfd, const c
goto exit3;
/* source must exist */
error = -ENOENT;
- if (!old_dentry->d_inode)
+ if (!old_dentry->d_inode || S_ISWHT(old_dentry->d_inode->i_mode))
goto exit4;
/* unless the source is a directory trailing slashes give -ENOTDIR */
if (!S_ISDIR(old_dentry->d_inode->i_mode)) {
@@ -2696,6 +3133,21 @@ static int do_rename(int olddfd, const c
error = -ENOTEMPTY;
if (new_dentry == trap)
goto exit5;
+ error = -EXDEV;
+ /* renaming of directories on unions isn't implemented, yet */
+ if (union_is_member(old_dentry, oldnd.mnt)) {
+ error = -EOPNOTSUPP;
+ if (S_ISDIR(old_dentry->d_inode->i_mode))
+ goto exit5;
+ error = -EXDEV;
+ if (oldnd.um_flags & LAST_LOWLEVEL)
+ goto exit5;
+ }
+ if (union_is_member(new_dentry, newnd.mnt)) {
+ error = -EXDEV;
+ if (newnd.um_flags & LAST_LOWLEVEL)
+ goto exit5;
+ }

error = vfs_rename(old_dir->d_inode, old_dentry,
new_dir->d_inode, new_dentry);
--- a/fs/readdir.c
+++ b/fs/readdir.c
@@ -148,6 +148,11 @@ static int filldir(void * __buf, const c
unsigned long d_ino;
int reclen = ALIGN(NAME_OFFSET(dirent) + namlen + 2, sizeof(long));

+#ifdef CONFIG_UNION_MOUNT
+ if (d_type == DT_WHT)
+ return 0;
+#endif /* CONFIG_UNION_MOUNT */
+
buf->error = -EINVAL; /* only used if we fail.. */
if (reclen > buf->count)
return -EINVAL;
@@ -233,6 +238,11 @@ static int filldir64(void * __buf, const
struct getdents_callback64 * buf = (struct getdents_callback64 *) __buf;
int reclen = ALIGN(NAME_OFFSET(dirent) + namlen + 1, sizeof(u64));

+#ifdef CONFIG_UNION_MOUNT
+ if (d_type == DT_WHT)
+ return 0;
+#endif /* CONFIG_UNION_MOUNT */
+
buf->error = -EINVAL; /* only used if we fail.. */
if (reclen > buf->count)
return -EINVAL;
--- a/fs/union.c
+++ b/fs/union.c
@@ -594,6 +594,9 @@ lookup_union:
if (!S_ISDIR(topmost->d_inode->i_mode))
goto out;

+ if (IS_OPAQUE(topmost->d_inode))
+ goto out;
+
if (!revalidate_union(topmost)) {
__dput_single(topmost);
topmost = NULL;
@@ -664,6 +667,8 @@ lookup_union:
dentry->d_topmost = topmost;
last->d_overlaid = dentry;
last = dentry;
+ if (IS_OPAQUE(last->d_inode))
+ break;
parent = parent->d_overlaid;
}

@@ -822,6 +827,8 @@ static int __lookup_union(struct dentry
loop:
__dput(nd.dentry);
mntput(nd.mnt);
+ if (IS_OPAQUE(last->d_inode))
+ break;
parent = parent->d_overlaid;
}

@@ -912,6 +919,9 @@ lookup_union:
if (!parent->d_overlaid || !S_ISDIR(topmost->d_inode->i_mode))
goto out;

+ if (IS_OPAQUE(topmost->d_inode))
+ goto out;
+
do {
struct vfsmount *mnt = find_mnt(topmost);
UM_DEBUG_UID("name=\"%s\", inode=%p, device=%s\n",
@@ -988,6 +998,9 @@ lookup_union:
if (!parent->d_overlaid || !S_ISDIR(topmost->d_inode->i_mode))
goto out;

+ if (IS_OPAQUE(topmost->d_inode))
+ goto out;
+
do {
struct vfsmount *mnt = find_mnt(topmost);
UM_DEBUG_UID("name=\"%s\", inode=%p, device=%s\n",
@@ -1787,3 +1800,94 @@ exit_dput:
dput(dentry);
return err;
}
+
+static int
+filldir_dummy(void *__buf, const char *name, int namlen, loff_t offset,
+ u64 ino, unsigned int d_type)
+{
+ int *is_empty = (int *)__buf;
+
+ switch (namlen) {
+ case 2:
+ if (name[1] != '.')
+ break;
+ case 1:
+ if (name[0] != '.')
+ break;
+ return 0;
+ }
+
+ if (d_type == DT_WHT)
+ return 0;
+
+ (*is_empty) = 0;
+ return 0;
+}
+
+int
+union_dir_is_empty(struct dentry *dentry)
+{
+ struct file *file;
+ struct vfsmount *mnt;
+ int err;
+ int is_empty = 1;
+
+ BUG_ON(!S_ISDIR(dentry->d_inode->i_mode));
+
+ dget(dentry);
+ mnt = find_mnt(dentry);
+
+ file = dentry_open(dentry, mnt, O_RDONLY);
+ if (IS_ERR(file))
+ return 0;
+
+ err = vfs_readdir(file, filldir_dummy, &is_empty);
+ UM_DEBUG("err=%d, is_empty=%d\n", err, is_empty);
+
+ fput(file);
+ return is_empty;
+}
+
+int present_in_lower(struct dentry *dentry, struct nameidata *nd)
+{
+ int err = 0;
+ struct dentry *parent = nd->dentry->d_overlaid;
+ struct dentry *tmp;
+ struct nameidata nd_tmp;
+ struct qstr this;
+
+ this.name = nd->last.name;
+ this.len = nd->last.len;
+
+ while (parent) {
+ this.hash = nd->last.hash;
+ nd_tmp.dentry = dget(parent);
+ nd_tmp.mnt = find_mnt(parent);
+ mutex_lock(&parent->d_inode->i_mutex);
+ tmp = __lookup_hash_single(&this, nd_tmp.dentry, &nd_tmp);
+ mutex_unlock(&parent->d_inode->i_mutex);
+ /*
+ * If there is an error in lookup, we return 0 concluding
+ * that this dentry is not present in lower layers.
+ */
+ if (IS_ERR(tmp))
+ goto out;
+
+ if (tmp->d_inode) {
+ __dput_single(tmp);
+ err = 1;
+ goto out;
+ }
+
+ __dput_single(tmp);
+ mntput(nd_tmp.mnt);
+ dput(nd_tmp.dentry);
+ parent = parent->d_overlaid;
+ }
+
+ return err;
+out:
+ mntput(nd_tmp.mnt);
+ dput(nd_tmp.dentry);
+ return err;
+}
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -157,6 +157,7 @@ extern int dir_notify_enable;
#define S_NOCMTIME 128 /* Do not update file c/mtime */
#define S_SWAPFILE 256 /* Do not truncate: swapon got its bmaps */
#define S_PRIVATE 512 /* Inode is fs-internal */
+#define S_OPAQUE 1024 /* Directory is opaque */

/*
* Note that nosuid etc flags are inode-specific: setting some file-system
@@ -190,6 +191,7 @@ extern int dir_notify_enable;
#define IS_NOCMTIME(inode) ((inode)->i_flags & S_NOCMTIME)
#define IS_SWAPFILE(inode) ((inode)->i_flags & S_SWAPFILE)
#define IS_PRIVATE(inode) ((inode)->i_flags & S_PRIVATE)
+#define IS_OPAQUE(inode) ((inode)->i_flags & S_OPAQUE)

/* the read-only stuff doesn't really belong here, but any other place is
probably as bad and I don't want to create yet another include file. */
@@ -1050,6 +1052,7 @@ extern int vfs_link(struct dentry *, str
extern int vfs_rmdir(struct inode *, struct dentry *);
extern int vfs_unlink(struct inode *, struct dentry *);
extern int vfs_rename(struct inode *, struct dentry *, struct inode *, struct dentry *);
+extern int vfs_whiteout(struct inode *, struct dentry *);

/*
* VFS dentry helper functions.
@@ -1175,6 +1178,7 @@ struct inode_operations {
int (*mkdir) (struct inode *,struct dentry *,int);
int (*rmdir) (struct inode *,struct dentry *);
int (*mknod) (struct inode *,struct dentry *,int,dev_t);
+ int (*whiteout) (struct inode *, struct dentry *);
int (*rename) (struct inode *, struct dentry *,
struct inode *, struct dentry *);
int (*readlink) (struct dentry *, char __user *,int);
--- a/include/linux/union.h
+++ b/include/linux/union.h
@@ -43,6 +43,10 @@ extern int union_copy_file(struct dentry
extern int union_copyup(struct nameidata *, int);
extern int union_relookup_topmost(struct nameidata *, int);

+/* vfs whiteout support */
+extern int union_dir_is_empty(struct dentry *);
+extern int present_in_lower(struct dentry *, struct nameidata *);
+
#else /* CONFIG_UNION_MOUNT */

#define attach_mnt_union(mnt,nd) do { /* empty */ } while (0)
@@ -54,6 +58,8 @@ extern int union_relookup_topmost(struct
#define union_copy_file(dentry1,mnt1,dentry2,mnt2) ({ (0); })
#define union_copyup(x,y) ({ (0); })
#define union_relookup_topmost(x,y) ({ (0); })
+#define union_dir_is_empty(x) ({ (1); })
+#define present_in_lower(x, y) ({ (0); })

#endif /* CONFIG_UNION_MOUNT */