All of lore.kernel.org
 help / color / mirror / Atom feed
From: Bharata B Rao <bharata@linux.vnet.ibm.com>
To: linux-kernel@vger.kernel.org
Cc: linux-fsdevel@vger.kernel.org, Jan Blunck <j.blunck@tu-harburg.de>
Subject: [RFC][PATCH 11/15] VFS whiteout handling
Date: Tue, 17 Apr 2007 18:53:15 +0530	[thread overview]
Message-ID: <20070417132315.GL4001@in.ibm.com> (raw)
In-Reply-To: <20070417131459.GA4001@in.ibm.com>

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
@@ -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 */
 

  parent reply	other threads:[~2007-04-17 13:16 UTC|newest]

Thread overview: 22+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2007-04-17 13:14 [RFC][PATCH 0/15] VFS based Union Mount Bharata B Rao
2007-04-17 13:16 ` [RFC][PATCH 1/15] Add union mount documentation Bharata B Rao
2007-04-17 13:17 ` [RFC][PATCH 2/15] Add a new mount flag (MNT_UNION) for union mount Bharata B Rao
2007-04-17 13:17 ` [RFC][PATCH 3/15] Add the whiteout file type Bharata B Rao
2007-04-17 13:18 ` [RFC][PATCH 4/15] Add config options for union mount Bharata B Rao
2007-04-17 13:19 ` [RFC][PATCH 5/15] Introduce union stack Bharata B Rao
2007-04-17 22:08   ` Serge E. Hallyn
2007-04-18  3:27     ` Bharata B Rao
2007-04-17 13:20 ` [RFC][PATCH 6/15] Union-mount dentry reference counting Bharata B Rao
2007-04-17 13:20 ` [RFC][PATCH 7/15] Union-mount mounting Bharata B Rao
2007-04-17 13:21 ` [RFC][PATCH 8/15] Union-mount lookup Bharata B Rao
2007-04-17 13:22 ` [RFC][PATCH 9/15] Simple union-mount readdir Bharata B Rao
2007-04-17 13:22 ` [RFC][PATCH 10/15] In-kernel file copy between union mounted filesystems Bharata B Rao
2007-04-17 13:23 ` Bharata B Rao [this message]
2007-04-17 13:23 ` [RFC][PATCH 12/15] ext2 whiteout support Bharata B Rao
2007-04-17 13:24 ` [RFC][PATCH 13/15] ext3 " Bharata B Rao
2007-04-17 13:24 ` [RFC][PATCH 14/15] tmpfs " Bharata B Rao
2007-04-17 13:25 ` [RFC][PATCH 15/15] Union-mount changes for NFS Bharata B Rao
2007-04-17 14:35 ` [RFC][PATCH 0/15] VFS based Union Mount Shaya Potter
2007-04-17 16:30   ` Bharata B Rao
2007-04-17 16:56     ` Shaya Potter
2007-04-18  7:19       ` Bharata B Rao

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20070417132315.GL4001@in.ibm.com \
    --to=bharata@linux.vnet.ibm.com \
    --cc=j.blunck@tu-harburg.de \
    --cc=linux-fsdevel@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.