diff mbox series

[V3,1/2] btrfs-progs: Add the single-dev feature (to both mkfs/tune)

Message ID 20230831001544.3379273-2-gpiccoli@igalia.com (mailing list archive)
State New, archived
Headers show
Series Supporting same fsid mounting through the single-dev compat_ro feature | expand

Commit Message

Guilherme G. Piccoli Aug. 31, 2023, 12:12 a.m. UTC
The single-dev feature allows a device to be mounted regardless of
its fsid already being present in another device - in other words,
this feature disables RAID modes / metadata_uuid, allowing a single
device per filesystem. Its goal is mainly to allow mounting the
same fsid at the same time in the system.

Introduce hereby the feature to both mkfs (-O single-dev) and
btrfstune (--convert-to-single-device), syncing the kernel-shared
headers as well. The feature is a compat_ro, its kernel version was
set to v6.6.

Suggested-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: Guilherme G. Piccoli <gpiccoli@igalia.com>
---

V3:

- Changed the small '-s' option on btrfstune to the
long version "--convert-to-single-device" (thanks Josef!).

- Moved the kernel version to v6.6.


 common/fsfeatures.c        |  7 ++++
 kernel-shared/ctree.h      |  3 +-
 kernel-shared/uapi/btrfs.h |  7 ++++
 mkfs/main.c                |  4 +-
 tune/main.c                | 76 ++++++++++++++++++++++++--------------
 5 files changed, 67 insertions(+), 30 deletions(-)

Comments

Anand Jain Sept. 12, 2023, 9:27 a.m. UTC | #1
We may need to fix the command 'btrfs filesystem show' aswell.
  Could you test having more than one single-devices with
  the same fsid and running 'btrfs filesystem show' to ensure
  it can still display all the devices?

Thx.
Anand


On 31/08/2023 08:12, Guilherme G. Piccoli wrote:
> The single-dev feature allows a device to be mounted regardless of
> its fsid already being present in another device - in other words,
> this feature disables RAID modes / metadata_uuid, allowing a single
> device per filesystem. Its goal is mainly to allow mounting the
> same fsid at the same time in the system.
> 
> Introduce hereby the feature to both mkfs (-O single-dev) and
> btrfstune (--convert-to-single-device), syncing the kernel-shared
> headers as well. The feature is a compat_ro, its kernel version was
> set to v6.6.
> 
> Suggested-by: Qu Wenruo <wqu@suse.com>
> Signed-off-by: Guilherme G. Piccoli <gpiccoli@igalia.com>
> ---
> 
> V3:
> 
> - Changed the small '-s' option on btrfstune to the
> long version "--convert-to-single-device" (thanks Josef!).
> 
> - Moved the kernel version to v6.6.
> 
> 
>   common/fsfeatures.c        |  7 ++++
>   kernel-shared/ctree.h      |  3 +-
>   kernel-shared/uapi/btrfs.h |  7 ++++
>   mkfs/main.c                |  4 +-
>   tune/main.c                | 76 ++++++++++++++++++++++++--------------
>   5 files changed, 67 insertions(+), 30 deletions(-)
> 
> diff --git a/common/fsfeatures.c b/common/fsfeatures.c
> index 00658fa5159f..8813de01d618 100644
> --- a/common/fsfeatures.c
> +++ b/common/fsfeatures.c
> @@ -160,6 +160,13 @@ static const struct btrfs_feature mkfs_features[] = {
>   		VERSION_NULL(default),
>   		.desc		= "RAID1 with 3 or 4 copies"
>   	},
> +	{
> +		.name		= "single-dev",
> +		.compat_ro_flag	= BTRFS_FEATURE_COMPAT_RO_SINGLE_DEV,
> +		.sysfs_name	= "single_dev",
> +		VERSION_TO_STRING2(compat, 6,6),
> +		.desc		= "single device (allows same fsid mounting)"
> +	},
>   #ifdef BTRFS_ZONED
>   	{
>   		.name		= "zoned",
> diff --git a/kernel-shared/ctree.h b/kernel-shared/ctree.h
> index 59533879b939..e3fd834aa6dd 100644
> --- a/kernel-shared/ctree.h
> +++ b/kernel-shared/ctree.h
> @@ -86,7 +86,8 @@ static inline u32 __BTRFS_LEAF_DATA_SIZE(u32 nodesize)
>   	(BTRFS_FEATURE_COMPAT_RO_FREE_SPACE_TREE |	\
>   	 BTRFS_FEATURE_COMPAT_RO_FREE_SPACE_TREE_VALID | \
>   	 BTRFS_FEATURE_COMPAT_RO_VERITY |		\
> -	 BTRFS_FEATURE_COMPAT_RO_BLOCK_GROUP_TREE)
> +	 BTRFS_FEATURE_COMPAT_RO_BLOCK_GROUP_TREE |	\
> +	 BTRFS_FEATURE_COMPAT_RO_SINGLE_DEV)
>   
>   #if EXPERIMENTAL
>   #define BTRFS_FEATURE_INCOMPAT_SUPP			\
> diff --git a/kernel-shared/uapi/btrfs.h b/kernel-shared/uapi/btrfs.h
> index 85b04f89a2a9..2e0ee6ef6446 100644
> --- a/kernel-shared/uapi/btrfs.h
> +++ b/kernel-shared/uapi/btrfs.h
> @@ -336,6 +336,13 @@ _static_assert(sizeof(struct btrfs_ioctl_fs_info_args) == 1024);
>    */
>   #define BTRFS_FEATURE_COMPAT_RO_BLOCK_GROUP_TREE	(1ULL << 3)
>   
> +/*
> + * Single devices (as flagged by the corresponding compat_ro flag) only
> + * gets scanned during mount time; also, a random fsid is generated for
> + * them, in order to cope with same-fsid filesystem mounts.
> + */
> +#define BTRFS_FEATURE_COMPAT_RO_SINGLE_DEV		(1ULL << 4)
> +
>   #define BTRFS_FEATURE_INCOMPAT_MIXED_BACKREF	(1ULL << 0)
>   #define BTRFS_FEATURE_INCOMPAT_DEFAULT_SUBVOL	(1ULL << 1)
>   #define BTRFS_FEATURE_INCOMPAT_MIXED_GROUPS	(1ULL << 2)
> diff --git a/mkfs/main.c b/mkfs/main.c
> index 972ed1112ea6..429799932224 100644
> --- a/mkfs/main.c
> +++ b/mkfs/main.c
> @@ -1025,6 +1025,7 @@ int BOX_MAIN(mkfs)(int argc, char **argv)
>   	char *label = NULL;
>   	int nr_global_roots = sysconf(_SC_NPROCESSORS_ONLN);
>   	char *source_dir = NULL;
> +	bool single_dev;
>   
>   	cpu_detect_flags();
>   	hash_init_accel();
> @@ -1218,6 +1219,7 @@ int BOX_MAIN(mkfs)(int argc, char **argv)
>   		usage(&mkfs_cmd, 1);
>   
>   	opt_zoned = !!(features.incompat_flags & BTRFS_FEATURE_INCOMPAT_ZONED);
> +	single_dev = !!(features.compat_ro_flags & BTRFS_FEATURE_COMPAT_RO_SINGLE_DEV);
>   
>   	if (source_dir && device_count > 1) {
>   		error("the option -r is limited to a single device");
> @@ -1815,7 +1817,7 @@ out:
>   		device_count = argc - optind;
>   		while (device_count-- > 0) {
>   			file = argv[optind++];
> -			if (path_is_block_device(file) == 1)
> +			if (path_is_block_device(file) == 1 && !single_dev)
>   				btrfs_register_one_device(file);
>   		}
>   	}
> diff --git a/tune/main.c b/tune/main.c
> index 0ca1e01282c9..7b8706274fcc 100644
> --- a/tune/main.c
> +++ b/tune/main.c
> @@ -42,27 +42,31 @@
>   #include "tune/tune.h"
>   #include "check/clear-cache.h"
>   
> +#define SET_SUPER_FLAGS(type) \
> +static int set_super_##type##_flags(struct btrfs_root *root, u64 flags) \
> +{									\
> +	struct btrfs_trans_handle *trans;				\
> +	struct btrfs_super_block *disk_super;				\
> +	u64 super_flags;						\
> +	int ret;							\
> +									\
> +	disk_super = root->fs_info->super_copy;				\
> +	super_flags = btrfs_super_##type##_flags(disk_super);		\
> +	super_flags |= flags;						\
> +	trans = btrfs_start_transaction(root, 1);			\
> +	BUG_ON(IS_ERR(trans));						\
> +	btrfs_set_super_##type##_flags(disk_super, super_flags);	\
> +	ret = btrfs_commit_transaction(trans, root);			\
> +									\
> +	return ret;							\
> +}
> +
> +SET_SUPER_FLAGS(incompat)
> +SET_SUPER_FLAGS(compat_ro)
> +
>   static char *device;
>   static int force = 0;
>   
> -static int set_super_incompat_flags(struct btrfs_root *root, u64 flags)
> -{
> -	struct btrfs_trans_handle *trans;
> -	struct btrfs_super_block *disk_super;
> -	u64 super_flags;
> -	int ret;
> -
> -	disk_super = root->fs_info->super_copy;
> -	super_flags = btrfs_super_incompat_flags(disk_super);
> -	super_flags |= flags;
> -	trans = btrfs_start_transaction(root, 1);
> -	BUG_ON(IS_ERR(trans));
> -	btrfs_set_super_incompat_flags(disk_super, super_flags);
> -	ret = btrfs_commit_transaction(trans, root);
> -
> -	return ret;
> -}
> -
>   static int convert_to_fst(struct btrfs_fs_info *fs_info)
>   {
>   	int ret;
> @@ -108,6 +112,8 @@ static const char * const tune_usage[] = {
>   	OPTLINE("--convert-from-block-group-tree",
>   			"convert the block group tree back to extent tree (remove the incompat bit)"),
>   	OPTLINE("--convert-to-free-space-tree", "convert filesystem to use free space tree (v2 cache)"),
> +	OPTLINE("--convert-to-single-device", "enable the single device feature "
> +			"(mkfs: single-dev, allows same fsid mounting)"),
>   	"",
>   	"UUID changes:",
>   	OPTLINE("-u", "rewrite fsid, use a random one"),
> @@ -146,7 +152,8 @@ int BOX_MAIN(btrfstune)(int argc, char *argv[])
>   	int csum_type = -1;
>   	char *new_fsid_str = NULL;
>   	int ret;
> -	u64 super_flags = 0;
> +	u64 compat_ro_flags = 0;
> +	u64 incompat_flags = 0;
>   	int fd = -1;
>   
>   	btrfs_config_init();
> @@ -155,7 +162,8 @@ int BOX_MAIN(btrfstune)(int argc, char *argv[])
>   		enum { GETOPT_VAL_CSUM = GETOPT_VAL_FIRST,
>   		       GETOPT_VAL_ENABLE_BLOCK_GROUP_TREE,
>   		       GETOPT_VAL_DISABLE_BLOCK_GROUP_TREE,
> -		       GETOPT_VAL_ENABLE_FREE_SPACE_TREE };
> +		       GETOPT_VAL_ENABLE_FREE_SPACE_TREE,
> +		       GETOPT_VAL_SINGLE_DEV };
>   		static const struct option long_options[] = {
>   			{ "help", no_argument, NULL, GETOPT_VAL_HELP},
>   			{ "convert-to-block-group-tree", no_argument, NULL,
> @@ -164,6 +172,8 @@ int BOX_MAIN(btrfstune)(int argc, char *argv[])
>   				GETOPT_VAL_DISABLE_BLOCK_GROUP_TREE},
>   			{ "convert-to-free-space-tree", no_argument, NULL,
>   				GETOPT_VAL_ENABLE_FREE_SPACE_TREE},
> +			{ "convert-to-single-device", no_argument, NULL,
> +				GETOPT_VAL_SINGLE_DEV},
>   #if EXPERIMENTAL
>   			{ "csum", required_argument, NULL, GETOPT_VAL_CSUM },
>   #endif
> @@ -179,13 +189,13 @@ int BOX_MAIN(btrfstune)(int argc, char *argv[])
>   			seeding_value = arg_strtou64(optarg);
>   			break;
>   		case 'r':
> -			super_flags |= BTRFS_FEATURE_INCOMPAT_EXTENDED_IREF;
> +			incompat_flags |= BTRFS_FEATURE_INCOMPAT_EXTENDED_IREF;
>   			break;
>   		case 'x':
> -			super_flags |= BTRFS_FEATURE_INCOMPAT_SKINNY_METADATA;
> +			incompat_flags |= BTRFS_FEATURE_INCOMPAT_SKINNY_METADATA;
>   			break;
>   		case 'n':
> -			super_flags |= BTRFS_FEATURE_INCOMPAT_NO_HOLES;
> +			incompat_flags |= BTRFS_FEATURE_INCOMPAT_NO_HOLES;
>   			break;
>   		case 'f':
>   			force = 1;
> @@ -216,6 +226,9 @@ int BOX_MAIN(btrfstune)(int argc, char *argv[])
>   		case GETOPT_VAL_ENABLE_FREE_SPACE_TREE:
>   			to_fst = true;
>   			break;
> +		case GETOPT_VAL_SINGLE_DEV:
> +			compat_ro_flags |= BTRFS_FEATURE_COMPAT_RO_SINGLE_DEV;
> +			break;
>   #if EXPERIMENTAL
>   		case GETOPT_VAL_CSUM:
>   			btrfs_warn_experimental(
> @@ -239,9 +252,9 @@ int BOX_MAIN(btrfstune)(int argc, char *argv[])
>   		error("random fsid can't be used with specified fsid");
>   		return 1;
>   	}
> -	if (!super_flags && !seeding_flag && !(random_fsid || new_fsid_str) &&
> -	    !change_metadata_uuid && csum_type == -1 && !to_bg_tree &&
> -	    !to_extent_tree && !to_fst) {
> +	if (!compat_ro_flags && !incompat_flags && !seeding_flag &&
> +	    !(random_fsid || new_fsid_str) && !change_metadata_uuid &&
> +	    csum_type == -1 && !to_bg_tree && !to_extent_tree && !to_fst) {
>   		error("at least one option should be specified");
>   		usage(&tune_cmd, 1);
>   		return 1;
> @@ -363,8 +376,15 @@ int BOX_MAIN(btrfstune)(int argc, char *argv[])
>   		total++;
>   	}
>   
> -	if (super_flags) {
> -		ret = set_super_incompat_flags(root, super_flags);
> +	if (incompat_flags) {
> +		ret = set_super_incompat_flags(root, incompat_flags);
> +		if (!ret)
> +			success++;
> +		total++;
> +	}
> +
> +	if (compat_ro_flags) {
> +		ret = set_super_compat_ro_flags(root, compat_ro_flags);
>   		if (!ret)
>   			success++;
>   		total++;
Guilherme G. Piccoli Sept. 13, 2023, 11 p.m. UTC | #2
On 12/09/2023 06:27, Anand Jain wrote:
> 
>   We may need to fix the command 'btrfs filesystem show' aswell.
>   Could you test having more than one single-devices with
>   the same fsid and running 'btrfs filesystem show' to ensure
>   it can still display all the devices?
> 
> Thx.
> Anand
> 

Hi Anand, thanks for noticing that. I've made this test (with the
patches V4), the result:


$ lsblk | grep nvme
nvme0n1     259:0    0    1G  0 disk
└─nvme0n1p1 259:1    0 1022M  0 part /mnt
nvme1n1     259:2    0    1G  0 disk
└─nvme1n1p1 259:3    0 1022M  0 part /mnt2


$ dmesg | grep TEMP
[  802.818873] BTRFS info: random fsid
(c80a52e3-8f16-4095-bdc2-cc24bd01cf7d) set for TEMP_FSID device
/dev/nvme0n1p1 (real fsid 94b67f81-b51f-479e-9f44-0d33d5cec2d4)
[  805.761222] BTRFS info: random fsid
(5a0a6628-8cd0-4353-8daf-b01ca254c10d) set for TEMP_FSID device
/dev/nvme1n1p1 (real fsid 94b67f81-b51f-479e-9f44-0d33d5cec2d4)


$ btrfs filesystem show
Label: none  uuid: c80a52e3-8f16-4095-bdc2-cc24bd01cf7d
        Total devices 1 FS bytes used 144.00KiB
        devid    1 size 1022.00MiB used 126.12MiB path /dev/nvme0n1p1

Label: none  uuid: 5a0a6628-8cd0-4353-8daf-b01ca254c10d
        Total devices 1 FS bytes used 144.00KiB
        devid    1 size 1022.00MiB used 126.12MiB path /dev/nvme1n1p1

Label: none  uuid: 94b67f81-b51f-479e-9f44-0d33d5cec2d4
        Total devices 1 FS bytes used 144.00KiB
        devid    1 size 1022.00MiB used 126.12MiB path /dev/nvme1n1p1


It seems to me it's correct "enough" right? It shows the mounted
filesystems according to the temporary fsid.

Also, I've noticed that the real fsid is omitted for device nvme0n1p1,
i.e., the command de-duplicates devices with the same fsid - tested here
without the TEMP_FSID feature and it behaves the same way.

In case you think we could improve such output, I appreciate
suggestions, and I'd be glad if that could be considered an improvement
(i.e., not blocking the patch merge on misc-next) since I might not have
the time to work on this for some weeks...

Cheers,


Guilherme
Anand Jain Sept. 19, 2023, 5:15 a.m. UTC | #3
> $ btrfs filesystem show
> Label: none  uuid: c80a52e3-8f16-4095-bdc2-cc24bd01cf7d
>          Total devices 1 FS bytes used 144.00KiB
>          devid    1 size 1022.00MiB used 126.12MiB path /dev/nvme0n1p1
> 
> Label: none  uuid: 5a0a6628-8cd0-4353-8daf-b01ca254c10d
>          Total devices 1 FS bytes used 144.00KiB
>          devid    1 size 1022.00MiB used 126.12MiB path /dev/nvme1n1p1
> 
> Label: none  uuid: 94b67f81-b51f-479e-9f44-0d33d5cec2d4
>          Total devices 1 FS bytes used 144.00KiB
>          devid    1 size 1022.00MiB used 126.12MiB path /dev/nvme1n1p1
> 
> 
> It seems to me it's correct "enough" right? It shows the mounted
> filesystems according to the temporary fsid.
> 
> Also, I've noticed that the real fsid is omitted for device nvme0n1p1,
> i.e., the command de-duplicates devices with the same fsid - tested here
> without the TEMP_FSID feature and it behaves the same way.

  Mounted devices should be fine with those unique temporary fsids in
  place. However, my main concern lies with those cloned devices before
  they are mounted. Btrfs-progs build the in-memory device list from
  the fsid list, and that part requires some fixing.

  In the past, we did not handle duplicate fsids. If we had cloned
  devices, they had to be fixed with their fsid using 'btrfstune -u|m'
  before mounting. Sorting them by fsid worked fine. However, if we want
  to allow duplicate fsids, we might need to consider how the end user
  will identify the btrfs devices before they are mounted.

  I believe that currently, it only displays the last device that was
  scanned.

> In case you think we could improve such output, I appreciate
> suggestions, and I'd be glad if that could be considered an improvement
> (i.e., not blocking the patch merge on misc-next) since I might not have
> the time to work on this for some weeks...

  These commands represent the basic steps users take when trying out
  the new feature. We need to make sure it works.

Thanks, Anand
diff mbox series

Patch

diff --git a/common/fsfeatures.c b/common/fsfeatures.c
index 00658fa5159f..8813de01d618 100644
--- a/common/fsfeatures.c
+++ b/common/fsfeatures.c
@@ -160,6 +160,13 @@  static const struct btrfs_feature mkfs_features[] = {
 		VERSION_NULL(default),
 		.desc		= "RAID1 with 3 or 4 copies"
 	},
+	{
+		.name		= "single-dev",
+		.compat_ro_flag	= BTRFS_FEATURE_COMPAT_RO_SINGLE_DEV,
+		.sysfs_name	= "single_dev",
+		VERSION_TO_STRING2(compat, 6,6),
+		.desc		= "single device (allows same fsid mounting)"
+	},
 #ifdef BTRFS_ZONED
 	{
 		.name		= "zoned",
diff --git a/kernel-shared/ctree.h b/kernel-shared/ctree.h
index 59533879b939..e3fd834aa6dd 100644
--- a/kernel-shared/ctree.h
+++ b/kernel-shared/ctree.h
@@ -86,7 +86,8 @@  static inline u32 __BTRFS_LEAF_DATA_SIZE(u32 nodesize)
 	(BTRFS_FEATURE_COMPAT_RO_FREE_SPACE_TREE |	\
 	 BTRFS_FEATURE_COMPAT_RO_FREE_SPACE_TREE_VALID | \
 	 BTRFS_FEATURE_COMPAT_RO_VERITY |		\
-	 BTRFS_FEATURE_COMPAT_RO_BLOCK_GROUP_TREE)
+	 BTRFS_FEATURE_COMPAT_RO_BLOCK_GROUP_TREE |	\
+	 BTRFS_FEATURE_COMPAT_RO_SINGLE_DEV)
 
 #if EXPERIMENTAL
 #define BTRFS_FEATURE_INCOMPAT_SUPP			\
diff --git a/kernel-shared/uapi/btrfs.h b/kernel-shared/uapi/btrfs.h
index 85b04f89a2a9..2e0ee6ef6446 100644
--- a/kernel-shared/uapi/btrfs.h
+++ b/kernel-shared/uapi/btrfs.h
@@ -336,6 +336,13 @@  _static_assert(sizeof(struct btrfs_ioctl_fs_info_args) == 1024);
  */
 #define BTRFS_FEATURE_COMPAT_RO_BLOCK_GROUP_TREE	(1ULL << 3)
 
+/*
+ * Single devices (as flagged by the corresponding compat_ro flag) only
+ * gets scanned during mount time; also, a random fsid is generated for
+ * them, in order to cope with same-fsid filesystem mounts.
+ */
+#define BTRFS_FEATURE_COMPAT_RO_SINGLE_DEV		(1ULL << 4)
+
 #define BTRFS_FEATURE_INCOMPAT_MIXED_BACKREF	(1ULL << 0)
 #define BTRFS_FEATURE_INCOMPAT_DEFAULT_SUBVOL	(1ULL << 1)
 #define BTRFS_FEATURE_INCOMPAT_MIXED_GROUPS	(1ULL << 2)
diff --git a/mkfs/main.c b/mkfs/main.c
index 972ed1112ea6..429799932224 100644
--- a/mkfs/main.c
+++ b/mkfs/main.c
@@ -1025,6 +1025,7 @@  int BOX_MAIN(mkfs)(int argc, char **argv)
 	char *label = NULL;
 	int nr_global_roots = sysconf(_SC_NPROCESSORS_ONLN);
 	char *source_dir = NULL;
+	bool single_dev;
 
 	cpu_detect_flags();
 	hash_init_accel();
@@ -1218,6 +1219,7 @@  int BOX_MAIN(mkfs)(int argc, char **argv)
 		usage(&mkfs_cmd, 1);
 
 	opt_zoned = !!(features.incompat_flags & BTRFS_FEATURE_INCOMPAT_ZONED);
+	single_dev = !!(features.compat_ro_flags & BTRFS_FEATURE_COMPAT_RO_SINGLE_DEV);
 
 	if (source_dir && device_count > 1) {
 		error("the option -r is limited to a single device");
@@ -1815,7 +1817,7 @@  out:
 		device_count = argc - optind;
 		while (device_count-- > 0) {
 			file = argv[optind++];
-			if (path_is_block_device(file) == 1)
+			if (path_is_block_device(file) == 1 && !single_dev)
 				btrfs_register_one_device(file);
 		}
 	}
diff --git a/tune/main.c b/tune/main.c
index 0ca1e01282c9..7b8706274fcc 100644
--- a/tune/main.c
+++ b/tune/main.c
@@ -42,27 +42,31 @@ 
 #include "tune/tune.h"
 #include "check/clear-cache.h"
 
+#define SET_SUPER_FLAGS(type) \
+static int set_super_##type##_flags(struct btrfs_root *root, u64 flags) \
+{									\
+	struct btrfs_trans_handle *trans;				\
+	struct btrfs_super_block *disk_super;				\
+	u64 super_flags;						\
+	int ret;							\
+									\
+	disk_super = root->fs_info->super_copy;				\
+	super_flags = btrfs_super_##type##_flags(disk_super);		\
+	super_flags |= flags;						\
+	trans = btrfs_start_transaction(root, 1);			\
+	BUG_ON(IS_ERR(trans));						\
+	btrfs_set_super_##type##_flags(disk_super, super_flags);	\
+	ret = btrfs_commit_transaction(trans, root);			\
+									\
+	return ret;							\
+}
+
+SET_SUPER_FLAGS(incompat)
+SET_SUPER_FLAGS(compat_ro)
+
 static char *device;
 static int force = 0;
 
-static int set_super_incompat_flags(struct btrfs_root *root, u64 flags)
-{
-	struct btrfs_trans_handle *trans;
-	struct btrfs_super_block *disk_super;
-	u64 super_flags;
-	int ret;
-
-	disk_super = root->fs_info->super_copy;
-	super_flags = btrfs_super_incompat_flags(disk_super);
-	super_flags |= flags;
-	trans = btrfs_start_transaction(root, 1);
-	BUG_ON(IS_ERR(trans));
-	btrfs_set_super_incompat_flags(disk_super, super_flags);
-	ret = btrfs_commit_transaction(trans, root);
-
-	return ret;
-}
-
 static int convert_to_fst(struct btrfs_fs_info *fs_info)
 {
 	int ret;
@@ -108,6 +112,8 @@  static const char * const tune_usage[] = {
 	OPTLINE("--convert-from-block-group-tree",
 			"convert the block group tree back to extent tree (remove the incompat bit)"),
 	OPTLINE("--convert-to-free-space-tree", "convert filesystem to use free space tree (v2 cache)"),
+	OPTLINE("--convert-to-single-device", "enable the single device feature "
+			"(mkfs: single-dev, allows same fsid mounting)"),
 	"",
 	"UUID changes:",
 	OPTLINE("-u", "rewrite fsid, use a random one"),
@@ -146,7 +152,8 @@  int BOX_MAIN(btrfstune)(int argc, char *argv[])
 	int csum_type = -1;
 	char *new_fsid_str = NULL;
 	int ret;
-	u64 super_flags = 0;
+	u64 compat_ro_flags = 0;
+	u64 incompat_flags = 0;
 	int fd = -1;
 
 	btrfs_config_init();
@@ -155,7 +162,8 @@  int BOX_MAIN(btrfstune)(int argc, char *argv[])
 		enum { GETOPT_VAL_CSUM = GETOPT_VAL_FIRST,
 		       GETOPT_VAL_ENABLE_BLOCK_GROUP_TREE,
 		       GETOPT_VAL_DISABLE_BLOCK_GROUP_TREE,
-		       GETOPT_VAL_ENABLE_FREE_SPACE_TREE };
+		       GETOPT_VAL_ENABLE_FREE_SPACE_TREE,
+		       GETOPT_VAL_SINGLE_DEV };
 		static const struct option long_options[] = {
 			{ "help", no_argument, NULL, GETOPT_VAL_HELP},
 			{ "convert-to-block-group-tree", no_argument, NULL,
@@ -164,6 +172,8 @@  int BOX_MAIN(btrfstune)(int argc, char *argv[])
 				GETOPT_VAL_DISABLE_BLOCK_GROUP_TREE},
 			{ "convert-to-free-space-tree", no_argument, NULL,
 				GETOPT_VAL_ENABLE_FREE_SPACE_TREE},
+			{ "convert-to-single-device", no_argument, NULL,
+				GETOPT_VAL_SINGLE_DEV},
 #if EXPERIMENTAL
 			{ "csum", required_argument, NULL, GETOPT_VAL_CSUM },
 #endif
@@ -179,13 +189,13 @@  int BOX_MAIN(btrfstune)(int argc, char *argv[])
 			seeding_value = arg_strtou64(optarg);
 			break;
 		case 'r':
-			super_flags |= BTRFS_FEATURE_INCOMPAT_EXTENDED_IREF;
+			incompat_flags |= BTRFS_FEATURE_INCOMPAT_EXTENDED_IREF;
 			break;
 		case 'x':
-			super_flags |= BTRFS_FEATURE_INCOMPAT_SKINNY_METADATA;
+			incompat_flags |= BTRFS_FEATURE_INCOMPAT_SKINNY_METADATA;
 			break;
 		case 'n':
-			super_flags |= BTRFS_FEATURE_INCOMPAT_NO_HOLES;
+			incompat_flags |= BTRFS_FEATURE_INCOMPAT_NO_HOLES;
 			break;
 		case 'f':
 			force = 1;
@@ -216,6 +226,9 @@  int BOX_MAIN(btrfstune)(int argc, char *argv[])
 		case GETOPT_VAL_ENABLE_FREE_SPACE_TREE:
 			to_fst = true;
 			break;
+		case GETOPT_VAL_SINGLE_DEV:
+			compat_ro_flags |= BTRFS_FEATURE_COMPAT_RO_SINGLE_DEV;
+			break;
 #if EXPERIMENTAL
 		case GETOPT_VAL_CSUM:
 			btrfs_warn_experimental(
@@ -239,9 +252,9 @@  int BOX_MAIN(btrfstune)(int argc, char *argv[])
 		error("random fsid can't be used with specified fsid");
 		return 1;
 	}
-	if (!super_flags && !seeding_flag && !(random_fsid || new_fsid_str) &&
-	    !change_metadata_uuid && csum_type == -1 && !to_bg_tree &&
-	    !to_extent_tree && !to_fst) {
+	if (!compat_ro_flags && !incompat_flags && !seeding_flag &&
+	    !(random_fsid || new_fsid_str) && !change_metadata_uuid &&
+	    csum_type == -1 && !to_bg_tree && !to_extent_tree && !to_fst) {
 		error("at least one option should be specified");
 		usage(&tune_cmd, 1);
 		return 1;
@@ -363,8 +376,15 @@  int BOX_MAIN(btrfstune)(int argc, char *argv[])
 		total++;
 	}
 
-	if (super_flags) {
-		ret = set_super_incompat_flags(root, super_flags);
+	if (incompat_flags) {
+		ret = set_super_incompat_flags(root, incompat_flags);
+		if (!ret)
+			success++;
+		total++;
+	}
+
+	if (compat_ro_flags) {
+		ret = set_super_compat_ro_flags(root, compat_ro_flags);
 		if (!ret)
 			success++;
 		total++;