lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Date:	Tue, 17 Apr 2007 18:53:15 +0530
From:	Bharata B Rao <bharata@...ux.vnet.ibm.com>
To:	linux-kernel@...r.kernel.org
Cc:	linux-fsdevel@...r.kernel.org, Jan Blunck <j.blunck@...harburg.de>
Subject: [RFC][PATCH 11/15] VFS whiteout handling

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

Introduce white-out handling in the VFS.

Signed-off-by: Jan Blunck <j.blunck@...harburg.de>
Signed-off-by: Bharata B Rao <bharata@...ux.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
@@ -1418,6 +1418,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;
@@ -1431,6 +1446,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)
@@ -1521,7 +1527,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;
@@ -1588,6 +1594,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)
 {
@@ -1603,6 +1685,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)
@@ -1798,7 +1887,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;
@@ -1914,6 +2010,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);
 	/*
@@ -1933,6 +2040,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 -
@@ -1967,6 +2083,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)
@@ -2032,6 +2154,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;
@@ -2044,10 +2167,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;
 }
 
@@ -2089,6 +2225,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
@@ -2118,8 +2473,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;
 
@@ -2135,11 +2494,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);
@@ -2180,8 +2543,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:
@@ -2260,10 +2656,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 */
@@ -2276,6 +2706,7 @@ exit:
 slashes:
 	error = !dentry->d_inode ? -ENOENT :
 		S_ISDIR(dentry->d_inode->i_mode) ? -EISDIR : -ENOTDIR;
+	dput(dentry);
 	goto exit2;
 }
 
@@ -2309,6 +2740,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)
@@ -2363,7 +2800,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);
@@ -2639,7 +3076,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)) {
@@ -2661,6 +3098,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
@@ -580,6 +580,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;
@@ -644,6 +647,8 @@ lookup_union:
 		dentry->d_topmost = topmost;
 		last->d_overlaid = dentry;
 		last = dentry;
+		if (IS_OPAQUE(last->d_inode))
+			break;
 		parent = parent->d_overlaid;
 	}
 
@@ -826,6 +831,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;
 	}
 
@@ -900,6 +907,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",
@@ -977,6 +987,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",
@@ -1739,3 +1752,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
@@ -151,6 +151,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
@@ -184,6 +185,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. */
@@ -1043,6 +1045,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.
@@ -1168,6 +1171,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
@@ -39,6 +39,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)
@@ -50,6 +54,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 */
 
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ