Skip to content

Commit

Permalink
btrfs: RENAME_EXCHANGE semantic for renameat2()
Browse files Browse the repository at this point in the history
  • Loading branch information
dcci authored and Dan Fuhry committed Jan 20, 2016
1 parent 6a13feb commit 9a7300d
Showing 1 changed file with 189 additions and 1 deletion.
190 changes: 189 additions & 1 deletion fs/btrfs/inode.c
Expand Up @@ -9110,6 +9110,190 @@ static int btrfs_getattr(struct vfsmount *mnt,
return 0;
}

static int btrfs_rename_exchange(struct inode *old_dir, struct dentry *old_dentry,
struct inode *new_dir, struct dentry *new_dentry)
{
struct btrfs_trans_handle *trans;
struct btrfs_root *root = BTRFS_I(old_dir)->root;
struct btrfs_root *dest = BTRFS_I(new_dir)->root;
struct inode *new_inode = new_dentry->d_inode;
struct inode *old_inode = old_dentry->d_inode;
struct timespec ctime = CURRENT_TIME;
u64 old_ino = btrfs_ino(old_inode);
u64 new_ino = btrfs_ino(new_inode);
u64 old_idx = 0;
u64 new_idx = 0;
u64 root_objectid;
int ret;

/* we only allow rename subvolume link between subvolumes */
if (old_ino != BTRFS_FIRST_FREE_OBJECTID && root != dest)
return -EXDEV;

/* close the racy window with snapshot create/destroy ioctl */
if (old_ino == BTRFS_FIRST_FREE_OBJECTID)
down_read(&root->fs_info->subvol_sem);
if (new_ino == BTRFS_FIRST_FREE_OBJECTID)
down_read(&dest->fs_info->subvol_sem);

/*
* We want to reserve the absolute worst case amount of items. So if
* both inodes are subvols and we need to unlink them then that would
* require 4 item modifications, but if they are both normal inodes it
* would require 5 item modifications, so we'll assume their normal
* inodes. So 5 * 2 is 10, plus 2 for the new links, so 12 total items
* should cover the worst case number of items we'll modify.
*/
trans = btrfs_start_transaction(root, 12);
if (IS_ERR(trans)) {
ret = PTR_ERR(trans);
goto out_notrans;
}

/*
* We need to find a free sequence number both in the source and
* in the destination directory for the exchange.
*/
ret = btrfs_set_inode_index(new_dir, &old_idx);
if (ret)
goto out_fail;
ret = btrfs_set_inode_index(old_dir, &new_idx);
if (ret)
goto out_fail;

BTRFS_I(old_inode)->dir_index = 0ULL;
BTRFS_I(new_inode)->dir_index = 0ULL;

/* Reference for the source. */
if (unlikely(old_ino == BTRFS_FIRST_FREE_OBJECTID)) {
/* force full log commit if subvolume involved. */
btrfs_set_log_full_commit(root->fs_info, trans);
} else {
ret = btrfs_insert_inode_ref(trans, dest,
new_dentry->d_name.name,
new_dentry->d_name.len,
old_ino,
btrfs_ino(new_dir), old_idx);
if (ret)
goto out_fail;
btrfs_pin_log_trans(root);
}

/* And now for the dest. */
if (unlikely(new_ino == BTRFS_FIRST_FREE_OBJECTID)) {
/* force full log commit if subvolume involved. */
btrfs_set_log_full_commit(dest->fs_info, trans);
} else {
ret = btrfs_insert_inode_ref(trans, root,
old_dentry->d_name.name,
old_dentry->d_name.len,
new_ino,
btrfs_ino(old_dir), new_idx);
if (ret)
goto out_fail;
btrfs_pin_log_trans(dest);
}

/*
* Update i-node version and ctime/mtime.
*/
inode_inc_iversion(old_dir);
inode_inc_iversion(new_dir);
inode_inc_iversion(old_inode);
inode_inc_iversion(new_inode);
old_dir->i_ctime = old_dir->i_mtime = ctime;
new_dir->i_ctime = new_dir->i_mtime = ctime;
old_inode->i_ctime = ctime;
new_inode->i_ctime = ctime;

if (old_dentry->d_parent != new_dentry->d_parent) {
btrfs_record_unlink_dir(trans, old_dir, old_inode, 1);
btrfs_record_unlink_dir(trans, new_dir, new_inode, 1);
}

/* src is a subvolume */
if (unlikely(old_ino == BTRFS_FIRST_FREE_OBJECTID)) {
root_objectid = BTRFS_I(old_inode)->root->root_key.objectid;
ret = btrfs_unlink_subvol(trans, root, old_dir,
root_objectid,
old_dentry->d_name.name,
old_dentry->d_name.len);
} else { /* src is a inode */
ret = __btrfs_unlink_inode(trans, root, old_dir,
old_dentry->d_inode,
old_dentry->d_name.name,
old_dentry->d_name.len);
if (!ret)
ret = btrfs_update_inode(trans, root, old_inode);
}
if (ret) {
btrfs_abort_transaction(trans, root, ret);
goto out_fail;
}

/* dest is a subvolume */
if (unlikely(new_ino == BTRFS_FIRST_FREE_OBJECTID)) {
root_objectid = BTRFS_I(new_inode)->root->root_key.objectid;
ret = btrfs_unlink_subvol(trans, dest, new_dir,
root_objectid,
new_dentry->d_name.name,
new_dentry->d_name.len);
} else { /* dest is an inode */
ret = __btrfs_unlink_inode(trans, dest, new_dir,
new_dentry->d_inode,
new_dentry->d_name.name,
new_dentry->d_name.len);
if (!ret)
ret = btrfs_update_inode(trans, dest, new_inode);
}
if (ret) {
btrfs_abort_transaction(trans, root, ret);
goto out_fail;
}

ret = btrfs_add_link(trans, new_dir, old_inode,
new_dentry->d_name.name,
new_dentry->d_name.len, 0, old_idx);
if (ret) {
btrfs_abort_transaction(trans, root, ret);
goto out_fail;
}

ret = btrfs_add_link(trans, old_dir, new_inode,
old_dentry->d_name.name,
old_dentry->d_name.len, 0, new_idx);
if (ret) {
btrfs_abort_transaction(trans, root, ret);
goto out_fail;
}

if (old_inode->i_nlink == 1)
BTRFS_I(old_inode)->dir_index = old_idx;
if (new_inode->i_nlink == 1)
BTRFS_I(new_inode)->dir_index = new_idx;

if (old_ino != BTRFS_FIRST_FREE_OBJECTID) {
struct dentry *parent = new_dentry->d_parent;
btrfs_log_new_name(trans, old_inode, old_dir, parent);
btrfs_end_log_trans(root);
}
if (new_ino != BTRFS_FIRST_FREE_OBJECTID) {
struct dentry *parent = old_dentry->d_parent;
btrfs_log_new_name(trans, new_inode, new_dir, parent);
btrfs_end_log_trans(dest);
}

out_fail:
btrfs_end_transaction(trans, root);
out_notrans:
/* Racy window with snapshot create/destroy ioctl */
if (new_ino == BTRFS_FIRST_FREE_OBJECTID)
up_read(&dest->fs_info->subvol_sem);
if (old_ino == BTRFS_FIRST_FREE_OBJECTID)
up_read(&root->fs_info->subvol_sem);
return 0;
}

static int btrfs_rename(struct inode *old_dir, struct dentry *old_dentry,
struct inode *new_dir, struct dentry *new_dentry)
{
Expand Down Expand Up @@ -9294,8 +9478,12 @@ static int btrfs_rename2(struct inode *old_dir, struct dentry *old_dentry,
struct inode *new_dir, struct dentry *new_dentry,
unsigned int flags)
{
if (flags & ~RENAME_NOREPLACE)
if (flags & ~(RENAME_NOREPLACE | RENAME_EXCHANGE))
return -EINVAL;

if (flags & RENAME_EXCHANGE)
return btrfs_rename_exchange(old_dir, old_dentry, new_dir,
new_dentry);

return btrfs_rename(old_dir, old_dentry, new_dir, new_dentry);
}
Expand Down

2 comments on commit 9a7300d

@fuhry
Copy link
Owner

@fuhry fuhry commented on 9a7300d Jan 20, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dcci Hi there, I found this patch you submitted some time ago. It doesn't look like it's been accepted yet. Is it alright with you if I submit that together with the RENAME_WHITEOUT support I'm working on for btrfs?

@dcci
Copy link
Author

@dcci dcci commented on 9a7300d Jan 21, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Be my guest.

Please sign in to comment.