btrfs: add new BTRFS_IOC_SNAP_DESTROY_V2 ioctl

This ioctl will be responsible for deleting a subvolume using its id.
This can be used when a system has a file system mounted from a
subvolume, rather than the root file system, like below:

/
@subvol1/
@subvol2/
@subvol_default/

If only @subvol_default is mounted, we have no path to reach @subvol1
and @subvol2, thus no way to delete them. Current subvolume delete ioctl
takes a file handle point as argument, and if @subvol_default is
mounted, we can't reach @subvol1 and @subvol2 from the same mount point.

This patch introduces a new ioctl BTRFS_IOC_SNAP_DESTROY_V2 that takes
the extended structure with flags to allow to delete subvolume using
subvolid.

Now, we can use this new ioctl specifying the subvolume id and refer to
the same mount point. It doesn't matter which subvolume was mounted,
since we can reach to the desired one using the subvolume id, and then
delete it.

The full path to the subvolume id is resolved internally and access is
verified as if the subvolume was accessed by path.

The volume args v2 structure is extended to use the existing union for
subvolume id specification, that's valid in case the
BTRFS_SUBVOL_SPEC_BY_ID is set.

Signed-off-by: Marcos Paulo de Souza <mpdesouza@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
[ update changelog ]
Signed-off-by: David Sterba <dsterba@suse.com>
This commit is contained in:
Marcos Paulo de Souza 2020-02-07 10:05:46 -03:00 committed by David Sterba
parent c0c907a47d
commit 949964c928
2 changed files with 127 additions and 21 deletions

View File

@ -28,6 +28,7 @@
#include <linux/iversion.h> #include <linux/iversion.h>
#include "ctree.h" #include "ctree.h"
#include "disk-io.h" #include "disk-io.h"
#include "export.h"
#include "transaction.h" #include "transaction.h"
#include "btrfs_inode.h" #include "btrfs_inode.h"
#include "print-tree.h" #include "print-tree.h"
@ -2842,7 +2843,8 @@ static int btrfs_ioctl_get_subvol_rootref(struct file *file, void __user *argp)
} }
static noinline int btrfs_ioctl_snap_destroy(struct file *file, static noinline int btrfs_ioctl_snap_destroy(struct file *file,
void __user *arg) void __user *arg,
bool destroy_v2)
{ {
struct dentry *parent = file->f_path.dentry; struct dentry *parent = file->f_path.dentry;
struct btrfs_fs_info *fs_info = btrfs_sb(parent->d_sb); struct btrfs_fs_info *fs_info = btrfs_sb(parent->d_sb);
@ -2851,21 +2853,36 @@ static noinline int btrfs_ioctl_snap_destroy(struct file *file,
struct inode *inode; struct inode *inode;
struct btrfs_root *root = BTRFS_I(dir)->root; struct btrfs_root *root = BTRFS_I(dir)->root;
struct btrfs_root *dest = NULL; struct btrfs_root *dest = NULL;
struct btrfs_ioctl_vol_args *vol_args; struct btrfs_ioctl_vol_args *vol_args = NULL;
int namelen; struct btrfs_ioctl_vol_args_v2 *vol_args2 = NULL;
char *subvol_name, *subvol_name_ptr = NULL;
int subvol_namelen;
int err = 0; int err = 0;
bool destroy_parent = false;
if (!S_ISDIR(dir->i_mode)) if (destroy_v2) {
return -ENOTDIR; vol_args2 = memdup_user(arg, sizeof(*vol_args2));
if (IS_ERR(vol_args2))
return PTR_ERR(vol_args2);
vol_args = memdup_user(arg, sizeof(*vol_args)); if (vol_args2->flags & ~BTRFS_SUBVOL_DELETE_ARGS_MASK) {
if (IS_ERR(vol_args)) err = -EOPNOTSUPP;
return PTR_ERR(vol_args); goto out;
}
vol_args->name[BTRFS_PATH_NAME_MAX] = '\0'; /*
namelen = strlen(vol_args->name); * If SPEC_BY_ID is not set, we are looking for the subvolume by
if (strchr(vol_args->name, '/') || * name, same as v1 currently does.
strncmp(vol_args->name, "..", namelen) == 0) { */
if (!(vol_args2->flags & BTRFS_SUBVOL_SPEC_BY_ID)) {
vol_args2->name[BTRFS_SUBVOL_NAME_MAX] = 0;
subvol_name = vol_args2->name;
err = mnt_want_write_file(file);
if (err)
goto out;
} else {
if (vol_args2->subvolid < BTRFS_FIRST_FREE_OBJECTID) {
err = -EINVAL; err = -EINVAL;
goto out; goto out;
} }
@ -2874,11 +2891,82 @@ static noinline int btrfs_ioctl_snap_destroy(struct file *file,
if (err) if (err)
goto out; goto out;
dentry = btrfs_get_dentry(fs_info->sb,
BTRFS_FIRST_FREE_OBJECTID,
vol_args2->subvolid, 0, 0);
if (IS_ERR(dentry)) {
err = PTR_ERR(dentry);
goto out_drop_write;
}
/*
* Change the default parent since the subvolume being
* deleted can be outside of the current mount point.
*/
parent = btrfs_get_parent(dentry);
/*
* At this point dentry->d_name can point to '/' if the
* subvolume we want to destroy is outsite of the
* current mount point, so we need to release the
* current dentry and execute the lookup to return a new
* one with ->d_name pointing to the
* <mount point>/subvol_name.
*/
dput(dentry);
if (IS_ERR(parent)) {
err = PTR_ERR(parent);
goto out_drop_write;
}
dir = d_inode(parent);
/*
* If v2 was used with SPEC_BY_ID, a new parent was
* allocated since the subvolume can be outside of the
* current mount point. Later on we need to release this
* new parent dentry.
*/
destroy_parent = true;
subvol_name_ptr = btrfs_get_subvol_name_from_objectid(
fs_info, vol_args2->subvolid);
if (IS_ERR(subvol_name_ptr)) {
err = PTR_ERR(subvol_name_ptr);
goto free_parent;
}
/* subvol_name_ptr is already NULL termined */
subvol_name = (char *)kbasename(subvol_name_ptr);
}
} else {
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;
subvol_name = vol_args->name;
err = mnt_want_write_file(file);
if (err)
goto out;
}
subvol_namelen = strlen(subvol_name);
if (strchr(subvol_name, '/') ||
strncmp(subvol_name, "..", subvol_namelen) == 0) {
err = -EINVAL;
goto free_subvol_name;
}
if (!S_ISDIR(dir->i_mode)) {
err = -ENOTDIR;
goto free_subvol_name;
}
err = down_write_killable_nested(&dir->i_rwsem, I_MUTEX_PARENT); err = down_write_killable_nested(&dir->i_rwsem, I_MUTEX_PARENT);
if (err == -EINTR) if (err == -EINTR)
goto out_drop_write; goto free_subvol_name;
dentry = lookup_one_len(vol_args->name, parent, namelen); dentry = lookup_one_len(subvol_name, parent, subvol_namelen);
if (IS_ERR(dentry)) { if (IS_ERR(dentry)) {
err = PTR_ERR(dentry); err = PTR_ERR(dentry);
goto out_unlock_dir; goto out_unlock_dir;
@ -2947,9 +3035,15 @@ static noinline int btrfs_ioctl_snap_destroy(struct file *file,
dput(dentry); dput(dentry);
out_unlock_dir: out_unlock_dir:
inode_unlock(dir); inode_unlock(dir);
free_subvol_name:
kfree(subvol_name_ptr);
free_parent:
if (destroy_parent)
dput(parent);
out_drop_write: out_drop_write:
mnt_drop_write_file(file); mnt_drop_write_file(file);
out: out:
kfree(vol_args2);
kfree(vol_args); kfree(vol_args);
return err; return err;
} }
@ -5474,7 +5568,9 @@ long btrfs_ioctl(struct file *file, unsigned int
case BTRFS_IOC_SUBVOL_CREATE_V2: case BTRFS_IOC_SUBVOL_CREATE_V2:
return btrfs_ioctl_snap_create_v2(file, argp, 1); return btrfs_ioctl_snap_create_v2(file, argp, 1);
case BTRFS_IOC_SNAP_DESTROY: case BTRFS_IOC_SNAP_DESTROY:
return btrfs_ioctl_snap_destroy(file, argp); return btrfs_ioctl_snap_destroy(file, argp, false);
case BTRFS_IOC_SNAP_DESTROY_V2:
return btrfs_ioctl_snap_destroy(file, argp, true);
case BTRFS_IOC_SUBVOL_GETFLAGS: case BTRFS_IOC_SUBVOL_GETFLAGS:
return btrfs_ioctl_subvol_getflags(file, argp); return btrfs_ioctl_subvol_getflags(file, argp);
case BTRFS_IOC_SUBVOL_SETFLAGS: case BTRFS_IOC_SUBVOL_SETFLAGS:

View File

@ -42,11 +42,14 @@ struct btrfs_ioctl_vol_args {
#define BTRFS_DEVICE_SPEC_BY_ID (1ULL << 3) #define BTRFS_DEVICE_SPEC_BY_ID (1ULL << 3)
#define BTRFS_SUBVOL_SPEC_BY_ID (1ULL << 4)
#define BTRFS_VOL_ARG_V2_FLAGS_SUPPORTED \ #define BTRFS_VOL_ARG_V2_FLAGS_SUPPORTED \
(BTRFS_SUBVOL_CREATE_ASYNC | \ (BTRFS_SUBVOL_CREATE_ASYNC | \
BTRFS_SUBVOL_RDONLY | \ BTRFS_SUBVOL_RDONLY | \
BTRFS_SUBVOL_QGROUP_INHERIT | \ BTRFS_SUBVOL_QGROUP_INHERIT | \
BTRFS_DEVICE_SPEC_BY_ID) BTRFS_DEVICE_SPEC_BY_ID | \
BTRFS_SUBVOL_SPEC_BY_ID)
#define BTRFS_FSID_SIZE 16 #define BTRFS_FSID_SIZE 16
#define BTRFS_UUID_SIZE 16 #define BTRFS_UUID_SIZE 16
@ -117,6 +120,10 @@ struct btrfs_ioctl_qgroup_limit_args {
BTRFS_SUBVOL_RDONLY | \ BTRFS_SUBVOL_RDONLY | \
BTRFS_SUBVOL_QGROUP_INHERIT) BTRFS_SUBVOL_QGROUP_INHERIT)
/* Supported flags for BTRFS_IOC_SNAP_DESTROY_V2 */
#define BTRFS_SUBVOL_DELETE_ARGS_MASK \
(BTRFS_SUBVOL_SPEC_BY_ID)
struct btrfs_ioctl_vol_args_v2 { struct btrfs_ioctl_vol_args_v2 {
__s64 fd; __s64 fd;
__u64 transid; __u64 transid;
@ -131,6 +138,7 @@ struct btrfs_ioctl_vol_args_v2 {
union { union {
char name[BTRFS_SUBVOL_NAME_MAX + 1]; char name[BTRFS_SUBVOL_NAME_MAX + 1];
__u64 devid; __u64 devid;
__u64 subvolid;
}; };
}; };
@ -959,5 +967,7 @@ enum btrfs_err_code {
struct btrfs_ioctl_get_subvol_rootref_args) struct btrfs_ioctl_get_subvol_rootref_args)
#define BTRFS_IOC_INO_LOOKUP_USER _IOWR(BTRFS_IOCTL_MAGIC, 62, \ #define BTRFS_IOC_INO_LOOKUP_USER _IOWR(BTRFS_IOCTL_MAGIC, 62, \
struct btrfs_ioctl_ino_lookup_user_args) struct btrfs_ioctl_ino_lookup_user_args)
#define BTRFS_IOC_SNAP_DESTROY_V2 _IOW(BTRFS_IOCTL_MAGIC, 63, \
struct btrfs_ioctl_vol_args_v2)
#endif /* _UAPI_LINUX_BTRFS_H */ #endif /* _UAPI_LINUX_BTRFS_H */