Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
262 changes: 253 additions & 9 deletions fs/btrfs/inode.c
Original file line number Diff line number Diff line change
Expand Up @@ -9110,17 +9110,208 @@ 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;
struct dentry *parent;
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) {
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) {
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)
struct inode *new_dir, struct dentry *new_dentry,
unsigned int flags)
{
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 = d_inode(new_dentry);
struct inode *old_inode = d_inode(old_dentry);
struct inode *whiteout_inode = NULL;
struct timespec ctime = CURRENT_TIME;
u64 index = 0;
u64 whiteout_index;
u64 root_objectid;
u64 whiteout_objectid;
int ret;
u64 old_ino = btrfs_ino(old_inode);

Expand Down Expand Up @@ -9173,15 +9364,15 @@ static int btrfs_rename(struct inode *old_dir, struct dentry *old_dentry,
* 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
* would require 5 item modifications, so we'll assume they are normal
* inodes. So 5 * 2 is 10, plus 1 for the new link, so 11 total items
* should cover the worst case number of items we'll modify.
*/
trans = btrfs_start_transaction(root, 11);
if (IS_ERR(trans)) {
ret = PTR_ERR(trans);
goto out_notrans;
}
ret = PTR_ERR(trans);
goto out_notrans;
}

if (dest != root)
btrfs_record_root_in_trans(trans, dest);
Expand Down Expand Up @@ -9281,6 +9472,55 @@ static int btrfs_rename(struct inode *old_dir, struct dentry *old_dentry,
btrfs_log_new_name(trans, old_inode, old_dir, parent);
btrfs_end_log_trans(root);
}

if (flags & RENAME_WHITEOUT) {
ret = btrfs_find_free_ino(root, &whiteout_objectid);
if (ret) {
btrfs_abort_transaction(trans, root, ret);
goto out_fail;
}

whiteout_inode = btrfs_new_inode(trans, root, old_dir,
old_dentry->d_name.name,
old_dentry->d_name.len,
btrfs_ino(old_dir),
whiteout_objectid,
S_IFCHR | WHITEOUT_MODE,
&whiteout_index);

if (IS_ERR(whiteout_inode)) {
ret = PTR_ERR(whiteout_inode);
btrfs_abort_transaction(trans, root, ret);
goto out_fail;
}

whiteout_inode->i_op = &btrfs_special_inode_operations;
init_special_inode(whiteout_inode, whiteout_inode->i_mode,
WHITEOUT_DEV);

ret = btrfs_init_inode_security(trans, whiteout_inode, old_dir,
&old_dentry->d_name);
if (ret) {
btrfs_abort_transaction(trans, root, ret);
goto out_fail;
}

ret = btrfs_add_nondir(trans, old_dir, old_dentry,
whiteout_inode, 0, whiteout_index);
if (ret) {
btrfs_abort_transaction(trans, root, ret);
goto out_fail;
}

ret = btrfs_update_inode(trans, root, whiteout_inode);
if (ret) {
btrfs_abort_transaction(trans, root, ret);
goto out_fail;
}

unlock_new_inode(whiteout_inode);
iput(whiteout_inode);
}
out_fail:
btrfs_end_transaction(trans, root);
out_notrans:
Expand All @@ -9294,10 +9534,14 @@ 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 | RENAME_WHITEOUT))
return -EINVAL;

return btrfs_rename(old_dir, old_dentry, new_dir, new_dentry);
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, flags);
}

static void btrfs_run_delalloc_work(struct btrfs_work *work)
Expand Down Expand Up @@ -9882,7 +10126,7 @@ static const struct inode_operations btrfs_dir_inode_operations = {
.get_acl = btrfs_get_acl,
.set_acl = btrfs_set_acl,
.update_time = btrfs_update_time,
.tmpfile = btrfs_tmpfile,
.tmpfile = btrfs_tmpfile,
};
static const struct inode_operations btrfs_dir_ro_inode_operations = {
.lookup = btrfs_lookup,
Expand All @@ -9900,7 +10144,7 @@ static const struct file_operations btrfs_dir_file_operations = {
#ifdef CONFIG_COMPAT
.compat_ioctl = btrfs_ioctl,
#endif
.release = btrfs_release_file,
.release = btrfs_release_file,
.fsync = btrfs_sync_file,
};

Expand Down