[PATCH 29/35] union-mount: Implement union-aware rename()

From: Valerie Aurora
Date: Thu Apr 15 2010 - 19:10:36 EST


On rename() of a file on union mount, copyup and whiteout the source
file. Both are done under the rename mutex. I believe this is
actually atomic.

XXX - May not need to do file copyup under the lock.
---
fs/namei.c | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
1 files changed, 70 insertions(+), 5 deletions(-)

diff --git a/fs/namei.c b/fs/namei.c
index 5f6dcd4..a6f7d5d 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -3233,6 +3233,7 @@ SYSCALL_DEFINE4(renameat, int, olddfd, const char __user *, oldname,
{
struct dentry *old_dir, *new_dir;
struct path old, new;
+ struct path to_whiteout = {NULL, NULL};
struct dentry *trap;
struct nameidata oldnd, newnd;
char *from;
@@ -3248,12 +3249,9 @@ SYSCALL_DEFINE4(renameat, int, olddfd, const char __user *, oldname,
goto exit1;

error = -EXDEV;
+ /* Union mounts will pass below test - dirs always on topmost */
if (oldnd.path.mnt != newnd.path.mnt)
goto exit2;
- /* Rename on union mounts not implemented yet */
- /* XXX much harsher check than necessary - can do some renames */
- if (IS_UNIONED_DIR(&oldnd.path) || IS_UNIONED_DIR(&newnd.path))
- goto exit2;
old_dir = oldnd.path.dentry;
error = -EBUSY;
if (oldnd.last_type != LAST_NORM)
@@ -3276,7 +3274,7 @@ SYSCALL_DEFINE4(renameat, int, olddfd, const char __user *, oldname,
error = -ENOENT;
if (!old.dentry->d_inode)
goto exit4;
- /* unless the source is a directory trailing slashes give -ENOTDIR */
+ /* unless the source is a directory, trailing slashes give -ENOTDIR */
if (!S_ISDIR(old.dentry->d_inode->i_mode)) {
error = -ENOTDIR;
if (oldnd.last.name[oldnd.last.len])
@@ -3288,6 +3286,11 @@ SYSCALL_DEFINE4(renameat, int, olddfd, const char __user *, oldname,
error = -EINVAL;
if (old.dentry == trap)
goto exit4;
+ error = -EXDEV;
+ /* Can't rename a directory from a lower layer */
+ if (IS_UNIONED_DIR(&oldnd.path) &&
+ IS_UNIONED_DIR(&old))
+ goto exit4;
error = lookup_hash(&newnd, &newnd.last, &new);
if (error)
goto exit4;
@@ -3295,6 +3298,48 @@ SYSCALL_DEFINE4(renameat, int, olddfd, const char __user *, oldname,
error = -ENOTEMPTY;
if (new.dentry == trap)
goto exit5;
+ error = -EXDEV;
+ /* Can't rename over directories on the lower layer */
+ if (IS_UNIONED_DIR(&newnd.path) &&
+ IS_UNIONED_DIR(&new))
+ goto exit4;
+
+ /* If source is on lower layer, copy up */
+ if (IS_UNIONED_DIR(&oldnd.path) &&
+ (old.mnt != oldnd.path.mnt)) {
+ /* Save the lower path to avoid a second lookup for whiteout */
+ to_whiteout.dentry = dget(old.dentry);
+ to_whiteout.mnt = mntget(old.mnt);
+ error = __union_copyup(&oldnd, &old);
+ if (error)
+ goto exit5;
+ }
+
+ /* If target is on lower layer, get negative dentry for topmost */
+ if (IS_UNIONED_DIR(&newnd.path) &&
+ (new.mnt != newnd.path.mnt)) {
+ struct dentry *dentry;
+ /*
+ * At this point, source and target are both files,
+ * the source is on the topmost layer, and the target
+ * is on a lower layer. We want the target dentry to
+ * disappear from the namespace, and give vfs_rename a
+ * negative dentry from the topmost layer.
+ */
+ /* We already did lookup once, no need to check perm */
+ dentry = __lookup_hash(&newnd.last, newnd.path.dentry, &newnd);
+ if (IS_ERR(dentry)) {
+ error = PTR_ERR(dentry);
+ goto exit5;
+ }
+ /* We no longer need the lower target dentry. It
+ * definitely should be removed from the hash table */
+ /* XXX what about failure case? */
+ d_delete(new.dentry);
+ mntput(new.mnt);
+ new.mnt = mntget(newnd.path.mnt);
+ new.dentry = dentry;
+ }

error = mnt_want_write(oldnd.path.mnt);
if (error)
@@ -3305,6 +3350,26 @@ SYSCALL_DEFINE4(renameat, int, olddfd, const char __user *, oldname,
goto exit6;
error = vfs_rename(old_dir->d_inode, old.dentry,
new_dir->d_inode, new.dentry);
+ if (error)
+ goto exit6;
+ /* Now whiteout the source */
+ if (IS_UNIONED_DIR(&oldnd.path)) {
+ if (!to_whiteout.dentry) {
+ struct dentry *dentry;
+ /* We could have exposed a lower level entry */
+ dentry = __lookup_hash(&oldnd.last, oldnd.path.dentry, &oldnd);
+ if (IS_ERR(dentry)) {
+ error = PTR_ERR(dentry);
+ goto exit6;
+ }
+ to_whiteout.dentry = dentry;
+ to_whiteout.mnt = mntget(oldnd.path.mnt);
+ }
+
+ if (to_whiteout.dentry->d_inode)
+ error = do_whiteout(&oldnd, &to_whiteout, 0);
+ path_put(&to_whiteout);
+ }
exit6:
mnt_drop_write(oldnd.path.mnt);
exit5:
--
1.6.3.3

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