lkml.org 
[lkml]   [2008]   [Jan]   [10]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    From
    Subject[PATCH 12/29] Unionfs: rename method and helpers
    Date
    Signed-off-by: Erez Zadok <ezk@cs.sunysb.edu>
    ---
    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


    \
     
     \ /
      Last update: 2008-01-10 16:15    [W:0.052 / U:1.200 seconds]
    ©2003-2016 Jasper Spaans. hosted at Digital OceanAdvertise on this site