Btrfs: add snapshot/subvolume destroy ioctl

This patch adds snapshot/subvolume destroy ioctl.  A subvolume that isn't being
used and doesn't contains links to other subvolumes can be destroyed.

Signed-off-by: Yan Zheng <zheng.yan@oracle.com>
Signed-off-by: Chris Mason <chris.mason@oracle.com>
This commit is contained in:
Yan, Zheng
2009-09-21 16:00:26 -04:00
committed by Chris Mason
parent 4df27c4d5c
commit 76dda93c6a
12 changed files with 610 additions and 238 deletions

View File

@@ -230,8 +230,8 @@ static noinline int create_subvol(struct btrfs_root *root,
struct btrfs_root_item root_item;
struct btrfs_inode_item *inode_item;
struct extent_buffer *leaf;
struct btrfs_root *new_root = root;
struct inode *dir;
struct btrfs_root *new_root;
struct inode *dir = dentry->d_parent->d_inode;
int ret;
int err;
u64 objectid;
@@ -241,7 +241,7 @@ static noinline int create_subvol(struct btrfs_root *root,
ret = btrfs_check_metadata_free_space(root);
if (ret)
goto fail_commit;
return ret;
trans = btrfs_start_transaction(root, 1);
BUG_ON(!trans);
@@ -304,11 +304,17 @@ static noinline int create_subvol(struct btrfs_root *root,
if (ret)
goto fail;
key.offset = (u64)-1;
new_root = btrfs_read_fs_root_no_name(root->fs_info, &key);
BUG_ON(IS_ERR(new_root));
btrfs_record_root_in_trans(trans, new_root);
ret = btrfs_create_subvol_root(trans, new_root, new_dirid,
BTRFS_I(dir)->block_group);
/*
* insert the directory item
*/
key.offset = (u64)-1;
dir = dentry->d_parent->d_inode;
ret = btrfs_set_inode_index(dir, &index);
BUG_ON(ret);
@@ -325,30 +331,15 @@ static noinline int create_subvol(struct btrfs_root *root,
ret = btrfs_add_root_ref(trans, root->fs_info->tree_root,
objectid, root->root_key.objectid,
dir->i_ino, index, name, namelen);
BUG_ON(ret);
ret = btrfs_commit_transaction(trans, root);
if (ret)
goto fail_commit;
new_root = btrfs_read_fs_root_no_name(root->fs_info, &key);
BUG_ON(!new_root);
trans = btrfs_start_transaction(new_root, 1);
BUG_ON(!trans);
ret = btrfs_create_subvol_root(trans, new_root, dentry, new_dirid,
BTRFS_I(dir)->block_group);
if (ret)
goto fail;
d_instantiate(dentry, btrfs_lookup_dentry(dir, dentry));
fail:
nr = trans->blocks_used;
err = btrfs_commit_transaction(trans, new_root);
err = btrfs_commit_transaction(trans, root);
if (err && !ret)
ret = err;
fail_commit:
btrfs_btree_balance_dirty(root, nr);
return ret;
}
@@ -409,14 +400,15 @@ static inline int btrfs_may_create(struct inode *dir, struct dentry *child)
* sys_mkdirat and vfs_mkdir, but we only do a single component lookup
* inside this filesystem so it's quite a bit simpler.
*/
static noinline int btrfs_mksubvol(struct path *parent, char *name,
int mode, int namelen,
static noinline int btrfs_mksubvol(struct path *parent,
char *name, int namelen,
struct btrfs_root *snap_src)
{
struct inode *dir = parent->dentry->d_inode;
struct dentry *dentry;
int error;
mutex_lock_nested(&parent->dentry->d_inode->i_mutex, I_MUTEX_PARENT);
mutex_lock_nested(&dir->i_mutex, I_MUTEX_PARENT);
dentry = lookup_one_len(name, parent->dentry, namelen);
error = PTR_ERR(dentry);
@@ -427,99 +419,39 @@ static noinline int btrfs_mksubvol(struct path *parent, char *name,
if (dentry->d_inode)
goto out_dput;
if (!IS_POSIXACL(parent->dentry->d_inode))
mode &= ~current_umask();
error = mnt_want_write(parent->mnt);
if (error)
goto out_dput;
error = btrfs_may_create(parent->dentry->d_inode, dentry);
error = btrfs_may_create(dir, dentry);
if (error)
goto out_drop_write;
/*
* Actually perform the low-level subvolume creation after all
* this VFS fuzz.
*
* Eventually we want to pass in an inode under which we create this
* subvolume, but for now all are under the filesystem root.
*
* Also we should pass on the mode eventually to allow creating new
* subvolume with specific mode bits.
*/
down_read(&BTRFS_I(dir)->root->fs_info->subvol_sem);
if (btrfs_root_refs(&BTRFS_I(dir)->root->root_item) == 0)
goto out_up_read;
if (snap_src) {
struct dentry *dir = dentry->d_parent;
struct dentry *test = dir->d_parent;
struct btrfs_path *path = btrfs_alloc_path();
int ret;
u64 test_oid;
u64 parent_oid = BTRFS_I(dir->d_inode)->root->root_key.objectid;
test_oid = snap_src->root_key.objectid;
ret = btrfs_find_root_ref(snap_src->fs_info->tree_root,
path, parent_oid, test_oid);
if (ret == 0)
goto create;
btrfs_release_path(snap_src->fs_info->tree_root, path);
/* we need to make sure we aren't creating a directory loop
* by taking a snapshot of something that has our current
* subvol in its directory tree. So, this loops through
* the dentries and checks the forward refs for each subvolume
* to see if is references the subvolume where we are
* placing this new snapshot.
*/
while (1) {
if (!test ||
dir == snap_src->fs_info->sb->s_root ||
test == snap_src->fs_info->sb->s_root ||
test->d_inode->i_sb != snap_src->fs_info->sb) {
break;
}
if (S_ISLNK(test->d_inode->i_mode)) {
printk(KERN_INFO "Btrfs symlink in snapshot "
"path, failed\n");
error = -EMLINK;
btrfs_free_path(path);
goto out_drop_write;
}
test_oid =
BTRFS_I(test->d_inode)->root->root_key.objectid;
ret = btrfs_find_root_ref(snap_src->fs_info->tree_root,
path, test_oid, parent_oid);
if (ret == 0) {
printk(KERN_INFO "Btrfs snapshot creation "
"failed, looping\n");
error = -EMLINK;
btrfs_free_path(path);
goto out_drop_write;
}
btrfs_release_path(snap_src->fs_info->tree_root, path);
test = test->d_parent;
}
create:
btrfs_free_path(path);
error = create_snapshot(snap_src, dentry, name, namelen);
error = create_snapshot(snap_src, dentry,
name, namelen);
} else {
error = create_subvol(BTRFS_I(parent->dentry->d_inode)->root,
dentry, name, namelen);
error = create_subvol(BTRFS_I(dir)->root, dentry,
name, namelen);
}
if (error)
goto out_drop_write;
fsnotify_mkdir(parent->dentry->d_inode, dentry);
if (!error)
fsnotify_mkdir(dir, dentry);
out_up_read:
up_read(&BTRFS_I(dir)->root->fs_info->subvol_sem);
out_drop_write:
mnt_drop_write(parent->mnt);
out_dput:
dput(dentry);
out_unlock:
mutex_unlock(&parent->dentry->d_inode->i_mutex);
mutex_unlock(&dir->i_mutex);
return error;
}
static int btrfs_defrag_file(struct file *file)
{
struct inode *inode = fdentry(file)->d_inode;
@@ -597,7 +529,8 @@ out_unlock:
return 0;
}
static int btrfs_ioctl_resize(struct btrfs_root *root, void __user *arg)
static noinline int btrfs_ioctl_resize(struct btrfs_root *root,
void __user *arg)
{
u64 new_size;
u64 old_size;
@@ -706,10 +639,7 @@ static noinline int btrfs_ioctl_snap_create(struct file *file,
{
struct btrfs_root *root = BTRFS_I(fdentry(file)->d_inode)->root;
struct btrfs_ioctl_vol_args *vol_args;
struct btrfs_dir_item *di;
struct btrfs_path *path;
struct file *src_file;
u64 root_dirid;
int namelen;
int ret = 0;
@@ -727,32 +657,9 @@ static noinline int btrfs_ioctl_snap_create(struct file *file,
goto out;
}
path = btrfs_alloc_path();
if (!path) {
ret = -ENOMEM;
goto out;
}
root_dirid = root->fs_info->sb->s_root->d_inode->i_ino,
di = btrfs_lookup_dir_item(NULL, root->fs_info->tree_root,
path, root_dirid,
vol_args->name, namelen, 0);
btrfs_free_path(path);
if (di && !IS_ERR(di)) {
ret = -EEXIST;
goto out;
}
if (IS_ERR(di)) {
ret = PTR_ERR(di);
goto out;
}
if (subvol) {
ret = btrfs_mksubvol(&file->f_path, vol_args->name,
file->f_path.dentry->d_inode->i_mode,
namelen, NULL);
ret = btrfs_mksubvol(&file->f_path, vol_args->name, namelen,
NULL);
} else {
struct inode *src_inode;
src_file = fget(vol_args->fd);
@@ -769,17 +676,156 @@ static noinline int btrfs_ioctl_snap_create(struct file *file,
fput(src_file);
goto out;
}
ret = btrfs_mksubvol(&file->f_path, vol_args->name,
file->f_path.dentry->d_inode->i_mode,
namelen, BTRFS_I(src_inode)->root);
ret = btrfs_mksubvol(&file->f_path, vol_args->name, namelen,
BTRFS_I(src_inode)->root);
fput(src_file);
}
out:
kfree(vol_args);
return ret;
}
/*
* helper to check if the subvolume references other subvolumes
*/
static noinline int may_destroy_subvol(struct btrfs_root *root)
{
struct btrfs_path *path;
struct btrfs_key key;
int ret;
path = btrfs_alloc_path();
if (!path)
return -ENOMEM;
key.objectid = root->root_key.objectid;
key.type = BTRFS_ROOT_REF_KEY;
key.offset = (u64)-1;
ret = btrfs_search_slot(NULL, root->fs_info->tree_root,
&key, path, 0, 0);
if (ret < 0)
goto out;
BUG_ON(ret == 0);
ret = 0;
if (path->slots[0] > 0) {
path->slots[0]--;
btrfs_item_key_to_cpu(path->nodes[0], &key, path->slots[0]);
if (key.objectid == root->root_key.objectid &&
key.type == BTRFS_ROOT_REF_KEY)
ret = -ENOTEMPTY;
}
out:
btrfs_free_path(path);
return ret;
}
static noinline int btrfs_ioctl_snap_destroy(struct file *file,
void __user *arg)
{
struct dentry *parent = fdentry(file);
struct dentry *dentry;
struct inode *dir = parent->d_inode;
struct inode *inode;
struct btrfs_root *root = BTRFS_I(dir)->root;
struct btrfs_root *dest = NULL;
struct btrfs_ioctl_vol_args *vol_args;
struct btrfs_trans_handle *trans;
int namelen;
int ret;
int err = 0;
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
vol_args = memdup_user(arg, sizeof(*vol_args));
if (IS_ERR(vol_args))
return PTR_ERR(vol_args);
vol_args->name[BTRFS_PATH_NAME_MAX] = '\0';
namelen = strlen(vol_args->name);
if (strchr(vol_args->name, '/') ||
strncmp(vol_args->name, "..", namelen) == 0) {
err = -EINVAL;
goto out;
}
err = mnt_want_write(file->f_path.mnt);
if (err)
goto out;
mutex_lock_nested(&dir->i_mutex, I_MUTEX_PARENT);
dentry = lookup_one_len(vol_args->name, parent, namelen);
if (IS_ERR(dentry)) {
err = PTR_ERR(dentry);
goto out_unlock_dir;
}
if (!dentry->d_inode) {
err = -ENOENT;
goto out_dput;
}
inode = dentry->d_inode;
if (inode->i_ino != BTRFS_FIRST_FREE_OBJECTID) {
err = -EINVAL;
goto out_dput;
}
dest = BTRFS_I(inode)->root;
mutex_lock(&inode->i_mutex);
err = d_invalidate(dentry);
if (err)
goto out_unlock;
down_write(&root->fs_info->subvol_sem);
err = may_destroy_subvol(dest);
if (err)
goto out_up_write;
trans = btrfs_start_transaction(root, 1);
ret = btrfs_unlink_subvol(trans, root, dir,
dest->root_key.objectid,
dentry->d_name.name,
dentry->d_name.len);
BUG_ON(ret);
btrfs_record_root_in_trans(trans, dest);
memset(&dest->root_item.drop_progress, 0,
sizeof(dest->root_item.drop_progress));
dest->root_item.drop_level = 0;
btrfs_set_root_refs(&dest->root_item, 0);
ret = btrfs_insert_orphan_item(trans,
root->fs_info->tree_root,
dest->root_key.objectid);
BUG_ON(ret);
ret = btrfs_commit_transaction(trans, root);
BUG_ON(ret);
inode->i_flags |= S_DEAD;
out_up_write:
up_write(&root->fs_info->subvol_sem);
out_unlock:
mutex_unlock(&inode->i_mutex);
if (!err) {
btrfs_invalidate_inodes(dest);
d_delete(dentry);
}
out_dput:
dput(dentry);
out_unlock_dir:
mutex_unlock(&dir->i_mutex);
mnt_drop_write(file->f_path.mnt);
out:
kfree(vol_args);
return err;
}
static int btrfs_ioctl_defrag(struct file *file)
{
struct inode *inode = fdentry(file)->d_inode;
@@ -853,8 +899,8 @@ static long btrfs_ioctl_rm_dev(struct btrfs_root *root, void __user *arg)
return ret;
}
static long btrfs_ioctl_clone(struct file *file, unsigned long srcfd,
u64 off, u64 olen, u64 destoff)
static noinline long btrfs_ioctl_clone(struct file *file, unsigned long srcfd,
u64 off, u64 olen, u64 destoff)
{
struct inode *inode = fdentry(file)->d_inode;
struct btrfs_root *root = BTRFS_I(inode)->root;
@@ -1246,6 +1292,8 @@ long btrfs_ioctl(struct file *file, unsigned int
return btrfs_ioctl_snap_create(file, argp, 0);
case BTRFS_IOC_SUBVOL_CREATE:
return btrfs_ioctl_snap_create(file, argp, 1);
case BTRFS_IOC_SNAP_DESTROY:
return btrfs_ioctl_snap_destroy(file, argp);
case BTRFS_IOC_DEFRAG:
return btrfs_ioctl_defrag(file);
case BTRFS_IOC_RESIZE: