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 = do_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); if (!(err = is_robranch_super(old_dir->i_sb, bindex))) 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, NULL, old_dentry->d_inode->i_size); if (!err) { dput(wh_old); bwh_old = bindex; err = do_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 *hidden_parent; BUG_ON(!wh_old || wh_old->d_inode || bwh_old < 0); hidden_parent = lock_parent(wh_old); local_err = vfs_create(hidden_parent->d_inode, wh_old, S_IRUGO, NULL); unlock_dir(hidden_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 "We can't create a whiteout for the " "source in rename!\n"); err = -EIO; } } out: dput(wh_old); return err; revert: /* Do revert here. */ local_err = unionfs_refresh_hidden_dentry(new_dentry, old_bstart); if (local_err) { printk(KERN_WARNING "Revert failed in rename: the new refresh " "failed.\n"); eio = -EIO; } local_err = unionfs_refresh_hidden_dentry(old_dentry, old_bstart); if (local_err) { printk(KERN_WARNING "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_WARNING "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_WARNING "Revert failed in rename: the object was " "created underneath us!\n"); eio = -EIO; goto revert_out; } local_err = do_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_WARNING "Revert failed in rename!\n"); eio = -EIO; } local_err = unionfs_refresh_hidden_dentry(new_dentry, bindex); if (local_err) eio = -EIO; local_err = unionfs_refresh_hidden_dentry(old_dentry, bindex); if (local_err) eio = -EIO; revert_out: if (eio) err = eio; return err; }
static int unionfs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) { int err = 0; struct dentry *lower_dentry = NULL; struct dentry *lower_parent_dentry = NULL; struct dentry *parent; int bindex = 0, bstart; char *name = NULL; int valid; unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD); parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT); unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD); valid = __unionfs_d_revalidate(dentry, parent, false, 0); if (unlikely(!valid)) { err = -ESTALE; /* same as what real_lookup does */ goto out; } bstart = dbstart(dentry); lower_dentry = unionfs_lower_dentry(dentry); /* check for a whiteout in new dentry branch, and delete it */ err = check_unlink_whiteout(dentry, lower_dentry, bstart); if (err > 0) /* whiteout found and removed successfully */ err = 0; if (err) { /* exit if the error returned was NOT -EROFS */ if (!IS_COPYUP_ERR(err)) goto out; bstart--; } /* check if copyup's needed, and mkdir */ for (bindex = bstart; bindex >= 0; bindex--) { int i; int bend = dbend(dentry); if (is_robranch_super(dentry->d_sb, bindex)) continue; lower_dentry = unionfs_lower_dentry_idx(dentry, bindex); if (!lower_dentry) { lower_dentry = create_parents(dir, dentry, dentry->d_name.name, bindex); if (!lower_dentry || IS_ERR(lower_dentry)) { printk(KERN_ERR "unionfs: lower dentry " " NULL for bindex = %d\n", bindex); continue; } } lower_parent_dentry = lock_parent(lower_dentry); if (IS_ERR(lower_parent_dentry)) { err = PTR_ERR(lower_parent_dentry); goto out; } err = vfs_mkdir(lower_parent_dentry->d_inode, lower_dentry, mode); unlock_dir(lower_parent_dentry); /* did the mkdir succeed? */ if (err) break; for (i = bindex + 1; i <= bend; i++) { /* XXX: use path_put_lowers? */ if (unionfs_lower_dentry_idx(dentry, i)) { dput(unionfs_lower_dentry_idx(dentry, i)); unionfs_set_lower_dentry_idx(dentry, i, NULL); } } dbend(dentry) = bindex; /* * Only INTERPOSE_LOOKUP can return a value other than 0 on * err. */ err = PTR_ERR(unionfs_interpose(dentry, dir->i_sb, 0)); if (!err) { unionfs_copy_attr_times(dir); fsstack_copy_inode_size(dir, lower_parent_dentry->d_inode); /* update number of links on parent directory */ set_nlink(dir, unionfs_get_nlinks(dir)); } err = make_dir_opaque(dentry, dbstart(dentry)); if (err) { printk(KERN_ERR "unionfs: mkdir: error creating " ".wh.__dir_opaque: %d\n", err); goto out; } /* we are done! */ break; } out: if (!dentry->d_inode) d_drop(dentry); kfree(name); if (!err) { unionfs_copy_attr_times(dentry->d_inode); unionfs_postcopyup_setmnt(dentry); } unionfs_check_inode(dir); unionfs_check_dentry(dentry); unionfs_unlock_dentry(dentry); unionfs_unlock_parent(dentry, parent); unionfs_read_unlock(dentry->d_sb); 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 dentry *old_parent, struct inode *new_dir, struct dentry *new_dentry, struct dentry *new_parent) { int err = 0; int bindex, bwh_old; int old_bstart, old_bend; int new_bstart, new_bend; int do_copyup = -1; int local_err = 0; int eio = 0; int revert = 0; old_bstart = dbstart(old_dentry); bwh_old = old_bstart; old_bend = dbend(old_dentry); new_bstart = dbstart(new_dentry); new_bend = dbend(new_dentry); /* Rename source to destination. */ err = __unionfs_rename(old_dir, old_dentry, old_parent, new_dir, new_dentry, new_parent, old_bstart); 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; BUG_ON(bindex < 0); 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_parent->d_inode, unlink_dir_dentry->d_inode); /* propagate number of hard-links */ new_parent->d_inode->i_nlink = unionfs_get_nlinks(new_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) { 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_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; bwh_old = bindex; err = __unionfs_rename(old_dir, old_dentry, old_parent, new_dir, new_dentry, new_parent, bindex); 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)) { if (bwh_old < 0) { printk(KERN_ERR "unionfs: rename error (bwh_old=%d)\n", bwh_old); err = -EIO; goto out; } err = create_whiteout(old_dentry, bwh_old); if (err) { /* can't fix anything now, so we exit with -EIO */ printk(KERN_ERR "unionfs: can't create a whiteout for " "%s in rename!\n", old_dentry->d_name.name); err = -EIO; } } out: return err; revert: /* Do revert here. */ local_err = unionfs_refresh_lower_dentry(new_dentry, new_parent, 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_parent, 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, new_parent, old_dir, old_dentry, old_parent, old_bstart); /* 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, new_parent, bindex); if (local_err) eio = -EIO; local_err = unionfs_refresh_lower_dentry(old_dentry, old_parent, bindex); if (local_err) eio = -EIO; revert_out: if (eio) err = eio; return err; }