Signed-off-by: Erez Zadok <ezk@xxxxxxxxxxxxx> --- fs/unionfs/rename.c | 531 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 531 insertions(+), 0 deletions(-) create mode 100644 fs/unionfs/rename.c diff --git a/fs/unionfs/rename.c b/fs/unionfs/rename.c new file mode 100644 index 0000000..9306a2b --- /dev/null +++ b/fs/unionfs/rename.c @@ -0,0 +1,531 @@ +/* + * Copyright (c) 2003-2007 Erez Zadok + * Copyright (c) 2003-2006 Charles P. Wright + * Copyright (c) 2005-2007 Josef 'Jeff' Sipek + * Copyright (c) 2005-2006 Junjiro Okajima + * Copyright (c) 2005 Arun M. Krishnakumar + * Copyright (c) 2004-2006 David P. Quigley + * Copyright (c) 2003-2004 Mohammad Nayyer Zubair + * Copyright (c) 2003 Puja Gupta + * Copyright (c) 2003 Harikesavan Krishnan + * Copyright (c) 2003-2007 Stony Brook University + * Copyright (c) 2003-2007 The Research Foundation of SUNY + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include "union.h" + +static int __unionfs_rename(struct inode *old_dir, struct dentry *old_dentry, + struct inode *new_dir, struct dentry *new_dentry, + int bindex, struct dentry **wh_old) +{ + int err = 0; + struct dentry *lower_old_dentry; + struct dentry *lower_new_dentry; + struct dentry *lower_old_dir_dentry; + struct dentry *lower_new_dir_dentry; + struct dentry *lower_wh_dentry; + struct dentry *lower_wh_dir_dentry; + char *wh_name = NULL; + + lower_new_dentry = unionfs_lower_dentry_idx(new_dentry, bindex); + lower_old_dentry = unionfs_lower_dentry_idx(old_dentry, bindex); + + if (!lower_new_dentry) { + lower_new_dentry = + create_parents(new_dentry->d_parent->d_inode, + new_dentry, new_dentry->d_name.name, + bindex); + if (IS_ERR(lower_new_dentry)) { + err = PTR_ERR(lower_new_dentry); + if (IS_COPYUP_ERR(err)) + goto out; + printk(KERN_ERR "unionfs: error creating directory " + "tree for rename, bindex=%d err=%d\n", + bindex, err); + goto out; + } + } + + wh_name = alloc_whname(new_dentry->d_name.name, + new_dentry->d_name.len); + if (unlikely(IS_ERR(wh_name))) { + err = PTR_ERR(wh_name); + goto out; + } + + lower_wh_dentry = lookup_one_len(wh_name, lower_new_dentry->d_parent, + new_dentry->d_name.len + + UNIONFS_WHLEN); + if (IS_ERR(lower_wh_dentry)) { + err = PTR_ERR(lower_wh_dentry); + goto out; + } + + if (lower_wh_dentry->d_inode) { + /* get rid of the whiteout that is existing */ + if (lower_new_dentry->d_inode) { + printk(KERN_ERR "unionfs: both a whiteout and a " + "dentry exist when doing a rename!\n"); + err = -EIO; + + dput(lower_wh_dentry); + goto out; + } + + lower_wh_dir_dentry = lock_parent_wh(lower_wh_dentry); + err = is_robranch_super(old_dentry->d_sb, bindex); + if (!err) + err = vfs_unlink(lower_wh_dir_dentry->d_inode, + lower_wh_dentry); + + dput(lower_wh_dentry); + unlock_dir(lower_wh_dir_dentry); + if (err) + goto out; + } else { + dput(lower_wh_dentry); + } + + err = is_robranch_super(old_dentry->d_sb, bindex); + if (err) + goto out; + + dget(lower_old_dentry); + lower_old_dir_dentry = dget_parent(lower_old_dentry); + lower_new_dir_dentry = dget_parent(lower_new_dentry); + + /* + * ready to whiteout for old_dentry. caller will create the actual + * whiteout, and must dput(*wh_old) + */ + if (wh_old) { + char *whname; + whname = alloc_whname(old_dentry->d_name.name, + old_dentry->d_name.len); + err = PTR_ERR(whname); + if (unlikely(IS_ERR(whname))) + goto out_dput; + *wh_old = lookup_one_len(whname, lower_old_dir_dentry, + old_dentry->d_name.len + + UNIONFS_WHLEN); + kfree(whname); + err = PTR_ERR(*wh_old); + if (IS_ERR(*wh_old)) { + *wh_old = NULL; + goto out_dput; + } + } + + /* see Documentation/filesystems/unionfs/issues.txt */ + lockdep_off(); + lock_rename(lower_old_dir_dentry, lower_new_dir_dentry); + err = vfs_rename(lower_old_dir_dentry->d_inode, lower_old_dentry, + lower_new_dir_dentry->d_inode, lower_new_dentry); + unlock_rename(lower_old_dir_dentry, lower_new_dir_dentry); + lockdep_on(); + +out_dput: + dput(lower_old_dir_dentry); + dput(lower_new_dir_dentry); + dput(lower_old_dentry); + +out: + if (!err) { + /* Fixup the new_dentry. */ + if (bindex < dbstart(new_dentry)) + set_dbstart(new_dentry, bindex); + else if (bindex > dbend(new_dentry)) + set_dbend(new_dentry, bindex); + } + + kfree(wh_name); + + return err; +} + +/* + * Main rename code. This is sufficiently complex, that it's documented in + * Documentation/filesystems/unionfs/rename.txt. This routine calls + * __unionfs_rename() above to perform some of the work. + */ +static int do_unionfs_rename(struct inode *old_dir, + struct dentry *old_dentry, + struct inode *new_dir, + struct dentry *new_dentry) +{ + int err = 0; + int bindex, bwh_old; + int old_bstart, old_bend; + int new_bstart, new_bend; + int do_copyup = -1; + struct dentry *parent_dentry; + int local_err = 0; + int eio = 0; + int revert = 0; + struct dentry *wh_old = NULL; + + old_bstart = dbstart(old_dentry); + bwh_old = old_bstart; + old_bend = dbend(old_dentry); + parent_dentry = old_dentry->d_parent; + + new_bstart = dbstart(new_dentry); + new_bend = dbend(new_dentry); + + /* Rename source to destination. */ + err = __unionfs_rename(old_dir, old_dentry, new_dir, new_dentry, + old_bstart, &wh_old); + if (err) { + if (!IS_COPYUP_ERR(err)) + goto out; + do_copyup = old_bstart - 1; + } else { + revert = 1; + } + + /* + * Unlink all instances of destination that exist to the left of + * bstart of source. On error, revert back, goto out. + */ + for (bindex = old_bstart - 1; bindex >= new_bstart; bindex--) { + struct dentry *unlink_dentry; + struct dentry *unlink_dir_dentry; + + unlink_dentry = unionfs_lower_dentry_idx(new_dentry, bindex); + if (!unlink_dentry) + continue; + + unlink_dir_dentry = lock_parent(unlink_dentry); + err = is_robranch_super(old_dir->i_sb, bindex); + if (!err) + err = vfs_unlink(unlink_dir_dentry->d_inode, + unlink_dentry); + + fsstack_copy_attr_times(new_dentry->d_parent->d_inode, + unlink_dir_dentry->d_inode); + /* propagate number of hard-links */ + new_dentry->d_parent->d_inode->i_nlink = + unionfs_get_nlinks(new_dentry->d_parent->d_inode); + + unlock_dir(unlink_dir_dentry); + if (!err) { + if (bindex != new_bstart) { + dput(unlink_dentry); + unionfs_set_lower_dentry_idx(new_dentry, + bindex, NULL); + } + } else if (IS_COPYUP_ERR(err)) { + do_copyup = bindex - 1; + } else if (revert) { + dput(wh_old); + goto revert; + } + } + + if (do_copyup != -1) { + for (bindex = do_copyup; bindex >= 0; bindex--) { + /* + * copyup the file into some left directory, so that + * you can rename it + */ + err = copyup_dentry(old_dentry->d_parent->d_inode, + old_dentry, old_bstart, bindex, + old_dentry->d_name.name, + old_dentry->d_name.len, NULL, + i_size_read(old_dentry->d_inode)); + /* if copyup failed, try next branch to the left */ + if (err) + continue; + dput(wh_old); + bwh_old = bindex; + err = __unionfs_rename(old_dir, old_dentry, + new_dir, new_dentry, + bindex, &wh_old); + break; + } + } + + /* make it opaque */ + if (S_ISDIR(old_dentry->d_inode->i_mode)) { + err = make_dir_opaque(old_dentry, dbstart(old_dentry)); + if (err) + goto revert; + } + + /* + * Create whiteout for source, only if: + * (1) There is more than one underlying instance of source. + * (2) We did a copy_up + */ + if ((old_bstart != old_bend) || (do_copyup != -1)) { + struct dentry *lower_parent; + struct nameidata nd; + if (!wh_old || wh_old->d_inode || bwh_old < 0) { + printk(KERN_ERR "unionfs: rename error " + "(wh_old=%p/%p bwh_old=%d)\n", wh_old, + (wh_old ? wh_old->d_inode : NULL), bwh_old); + err = -EIO; + goto out; + } + err = init_lower_nd(&nd, LOOKUP_CREATE); + if (unlikely(err < 0)) + goto out; + lower_parent = lock_parent_wh(wh_old); + local_err = vfs_create(lower_parent->d_inode, wh_old, S_IRUGO, + &nd); + unlock_dir(lower_parent); + if (!local_err) { + set_dbopaque(old_dentry, bwh_old); + } else { + /* + * we can't fix anything now, so we cop-out and use + * -EIO. + */ + printk(KERN_ERR "unionfs: can't create a whiteout for " + "the source in rename!\n"); + err = -EIO; + } + release_lower_nd(&nd, local_err); + } + +out: + dput(wh_old); + return err; + +revert: + /* Do revert here. */ + local_err = unionfs_refresh_lower_dentry(new_dentry, old_bstart); + if (local_err) { + printk(KERN_ERR "unionfs: revert failed in rename: " + "the new refresh failed\n"); + eio = -EIO; + } + + local_err = unionfs_refresh_lower_dentry(old_dentry, old_bstart); + if (local_err) { + printk(KERN_ERR "unionfs: revert failed in rename: " + "the old refresh failed\n"); + eio = -EIO; + goto revert_out; + } + + if (!unionfs_lower_dentry_idx(new_dentry, bindex) || + !unionfs_lower_dentry_idx(new_dentry, bindex)->d_inode) { + printk(KERN_ERR "unionfs: revert failed in rename: " + "the object disappeared from under us!\n"); + eio = -EIO; + goto revert_out; + } + + if (unionfs_lower_dentry_idx(old_dentry, bindex) && + unionfs_lower_dentry_idx(old_dentry, bindex)->d_inode) { + printk(KERN_ERR "unionfs: revert failed in rename: " + "the object was created underneath us!\n"); + eio = -EIO; + goto revert_out; + } + + local_err = __unionfs_rename(new_dir, new_dentry, + old_dir, old_dentry, old_bstart, NULL); + + /* If we can't fix it, then we cop-out with -EIO. */ + if (local_err) { + printk(KERN_ERR "unionfs: revert failed in rename!\n"); + eio = -EIO; + } + + local_err = unionfs_refresh_lower_dentry(new_dentry, bindex); + if (local_err) + eio = -EIO; + local_err = unionfs_refresh_lower_dentry(old_dentry, bindex); + if (local_err) + eio = -EIO; + +revert_out: + if (eio) + err = eio; + return err; +} + +static struct dentry *lookup_whiteout(struct dentry *dentry) +{ + char *whname; + int bindex = -1, bstart = -1, bend = -1; + struct dentry *parent, *lower_parent, *wh_dentry; + + whname = alloc_whname(dentry->d_name.name, dentry->d_name.len); + if (unlikely(IS_ERR(whname))) + return (void *)whname; + + parent = dget_parent(dentry); + unionfs_lock_dentry(parent, UNIONFS_DMUTEX_WHITEOUT); + bstart = dbstart(parent); + bend = dbend(parent); + wh_dentry = ERR_PTR(-ENOENT); + for (bindex = bstart; bindex <= bend; bindex++) { + lower_parent = unionfs_lower_dentry_idx(parent, bindex); + if (!lower_parent) + continue; + wh_dentry = lookup_one_len(whname, lower_parent, + dentry->d_name.len + UNIONFS_WHLEN); + if (IS_ERR(wh_dentry)) + continue; + if (wh_dentry->d_inode) + break; + dput(wh_dentry); + wh_dentry = ERR_PTR(-ENOENT); + } + unionfs_unlock_dentry(parent); + dput(parent); + kfree(whname); + return wh_dentry; +} + +/* + * We can't copyup a directory, because it may involve huge numbers of + * children, etc. Doing that in the kernel would be bad, so instead we + * return EXDEV to the user-space utility that caused this, and let the + * user-space recurse and ask us to copy up each file separately. + */ +static int may_rename_dir(struct dentry *dentry) +{ + int err, bstart; + + err = check_empty(dentry, NULL); + if (err == -ENOTEMPTY) { + if (is_robranch(dentry)) + return -EXDEV; + } else if (err) { + return err; + } + + bstart = dbstart(dentry); + if (dbend(dentry) == bstart || dbopaque(dentry) == bstart) + return 0; + + set_dbstart(dentry, bstart + 1); + err = check_empty(dentry, NULL); + set_dbstart(dentry, bstart); + if (err == -ENOTEMPTY) + err = -EXDEV; + return err; +} + +int unionfs_rename(struct inode *old_dir, struct dentry *old_dentry, + struct inode *new_dir, struct dentry *new_dentry) +{ + int err = 0; + struct dentry *wh_dentry; + + unionfs_read_lock(old_dentry->d_sb, UNIONFS_SMUTEX_CHILD); + unionfs_double_lock_dentry(old_dentry, new_dentry); + + if (unlikely(!__unionfs_d_revalidate_chain(old_dentry, NULL, false))) { + err = -ESTALE; + goto out; + } + if (unlikely(!d_deleted(new_dentry) && new_dentry->d_inode && + !__unionfs_d_revalidate_chain(new_dentry, NULL, false))) { + err = -ESTALE; + goto out; + } + + if (!S_ISDIR(old_dentry->d_inode->i_mode)) + err = unionfs_partial_lookup(old_dentry); + else + err = may_rename_dir(old_dentry); + + if (err) + goto out; + + err = unionfs_partial_lookup(new_dentry); + if (err) + goto out; + + /* + * if new_dentry is already lower because of whiteout, + * simply override it even if the whited-out dir is not empty. + */ + wh_dentry = lookup_whiteout(new_dentry); + if (!IS_ERR(wh_dentry)) { + dput(wh_dentry); + } else if (new_dentry->d_inode) { + if (S_ISDIR(old_dentry->d_inode->i_mode) != + S_ISDIR(new_dentry->d_inode->i_mode)) { + err = S_ISDIR(old_dentry->d_inode->i_mode) ? + -ENOTDIR : -EISDIR; + goto out; + } + + if (S_ISDIR(new_dentry->d_inode->i_mode)) { + struct unionfs_dir_state *namelist = NULL; + /* check if this unionfs directory is empty or not */ + err = check_empty(new_dentry, &namelist); + if (err) + goto out; + + if (!is_robranch(new_dentry)) + err = delete_whiteouts(new_dentry, + dbstart(new_dentry), + namelist); + + free_rdstate(namelist); + + if (err) + goto out; + } + } + + err = do_unionfs_rename(old_dir, old_dentry, new_dir, new_dentry); + if (err) + goto out; + + /* + * force re-lookup since the dir on ro branch is not renamed, and + * lower dentries still indicate the un-renamed ones. + */ + if (S_ISDIR(old_dentry->d_inode->i_mode)) + atomic_dec(&UNIONFS_D(old_dentry)->generation); + else + unionfs_postcopyup_release(old_dentry); + if (new_dentry->d_inode && !S_ISDIR(new_dentry->d_inode->i_mode)) { + unionfs_postcopyup_release(new_dentry); + unionfs_postcopyup_setmnt(new_dentry); + if (!unionfs_lower_inode(new_dentry->d_inode)) { + /* + * If we get here, it means that no copyup was + * needed, and that a file by the old name already + * existing on the destination branch; that file got + * renamed earlier in this function, so all we need + * to do here is set the lower inode. + */ + struct inode *inode; + inode = unionfs_lower_inode(old_dentry->d_inode); + igrab(inode); + unionfs_set_lower_inode_idx(new_dentry->d_inode, + dbstart(new_dentry), + inode); + } + } + /* if all of this renaming succeeded, update our times */ + unionfs_copy_attr_times(old_dir); + unionfs_copy_attr_times(new_dir); + unionfs_copy_attr_times(old_dentry->d_inode); + unionfs_copy_attr_times(new_dentry->d_inode); + unionfs_check_inode(old_dir); + unionfs_check_inode(new_dir); + unionfs_check_dentry(old_dentry); + unionfs_check_dentry(new_dentry); + +out: + if (err) /* clear the new_dentry stuff created */ + d_drop(new_dentry); + unionfs_unlock_dentry(new_dentry); + unionfs_unlock_dentry(old_dentry); + unionfs_read_unlock(old_dentry->d_sb); + return err; +} -- 1.5.2.2 - To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html