diff mbox

[2/2,v2] utils: add support for getting/changing file system, features

Message ID 522EA170.1030003@suse.com (mailing list archive)
State New, archived
Headers show

Commit Message

Jeff Mahoney Sept. 10, 2013, 4:34 a.m. UTC
This patch adds support for getting and changing file system
feature bits. It supports both unmounted and mounted operation via
a new set of ioctls.

Changing bits not supported by the tools directly can be forced with -f
when the file system is unmounted.

v2: Implemented new ioctl methods from newer ioctl patchset; Cleanup

Signed-off-by: Jeff Mahoney <jeffm@suse.com>
---
 cmds-filesystem.c |  43 ++++
 ioctl.h           |  12 ++
 man/btrfs.8.in    |  28 +++
 utils.c           | 588 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 utils.h           |   9 +
 5 files changed, 680 insertions(+)
diff mbox

Patch

diff --git a/cmds-filesystem.c b/cmds-filesystem.c
index f41a72a..8e2e693 100644
--- a/cmds-filesystem.c
+++ b/cmds-filesystem.c
@@ -515,6 +515,48 @@  static int cmd_label(int argc, char **argv)
 		return get_label(argv[1]);
 }
 
+static const char * const cmd_features_usage[] = {
+	"btrfs filesystem features [<device>|<mountpoint>] [[-f] <features>]",
+	"Get or change the list of features currently enabled by a filesystem",
+	"With one argument, get the currently enabled features of filesystem",
+	"on <device> or <mountpoint>.", "",
+	"If <features> is passed, add or remove new features to the ",
+	"filesystem.  The format of features can be a comma separated list ",
+	"of names or or a comma-separated list of specifiers of the following",
+	"format: A prefix of compat, compat_ro, or incompat and a decimal",
+	"number, separated by a colon: (e.g. compat:10).  Prefixing the ",
+	"feature name with a caret (^) will clear the flag.", "",
+	"The kernel has a defined set of feature flags that it will allow",
+	"to be set or cleared at runtime.  Features not supported by the",
+	"tools can be changed by using the -f (force) flag when operating",
+	"on an unmounted filesystem.", "",
+	"A list of features supported by the tools can be found in the manual.",
+	NULL
+};
+
+static int cmd_features(int argc, char **argv)
+{
+	if (check_argc_min(argc, 2) || check_argc_max(argc, 4))
+		usage(cmd_features_usage);
+
+	if (argc > 2) {
+		char *features = argv[2];
+		int force = 0;
+		if (argc > 3) {
+			if (!strcmp(argv[3], "-f"))
+				force = 1;
+			else if (!strcmp(argv[2], "-f")) {
+				force = 1;
+				features = argv[3];
+			} else
+				usage(cmd_features_usage);
+		}
+
+		return parse_and_set_features(argv[1], features, force);
+	} else
+		return get_features(argv[1]);
+}
+
 const struct cmd_group filesystem_cmd_group = {
 	filesystem_cmd_group_usage, NULL, {
 		{ "df", cmd_df, cmd_df_usage, NULL, 0 },
@@ -524,6 +566,7 @@  const struct cmd_group filesystem_cmd_group = {
 		{ "balance", cmd_balance, NULL, &balance_cmd_group, 1 },
 		{ "resize", cmd_resize, cmd_resize_usage, NULL, 0 },
 		{ "label", cmd_label, cmd_label_usage, NULL, 0 },
+		{ "features", cmd_features, cmd_features_usage, NULL, 0 },
 		{ 0, 0, 0, 0, 0 },
 	}
 };
diff --git a/ioctl.h b/ioctl.h
index abe6dd4..3605c4a 100644
--- a/ioctl.h
+++ b/ioctl.h
@@ -172,6 +172,12 @@  struct btrfs_ioctl_fs_info_args {
 	__u64 reserved[124];			/* pad to 1k */
 };
 
+struct btrfs_ioctl_feature_flags {
+	__u64 compat_flags;
+	__u64 compat_ro_flags;
+	__u64 incompat_flags;
+};
+
 /* balance control ioctl modes */
 #define BTRFS_BALANCE_CTL_PAUSE		1
 #define BTRFS_BALANCE_CTL_CANCEL	2
@@ -537,6 +543,12 @@  struct btrfs_ioctl_clone_range_args {
 				      struct btrfs_ioctl_get_dev_stats)
 #define BTRFS_IOC_DEV_REPLACE _IOWR(BTRFS_IOCTL_MAGIC, 53, \
 				    struct btrfs_ioctl_dev_replace_args)
+#define BTRFS_IOC_GET_FEATURES _IOR(BTRFS_IOCTL_MAGIC, 54, \
+				    struct btrfs_ioctl_feature_flags)
+#define BTRFS_IOC_SET_FEATURES _IOW(BTRFS_IOCTL_MAGIC, 54, \
+				    struct btrfs_ioctl_feature_flags[2])
+#define BTRFS_IOC_GET_SUPPORTED_FEATURES _IOR(BTRFS_IOCTL_MAGIC, 54, \
+				    struct btrfs_ioctl_feature_flags[3])
 
 #ifdef __cplusplus
 }
diff --git a/man/btrfs.8.in b/man/btrfs.8.in
index af7df4d..0b3815f 100644
--- a/man/btrfs.8.in
+++ b/man/btrfs.8.in
@@ -31,6 +31,8 @@  btrfs \- control a btrfs filesystem
 .PP
 \fBbtrfs\fP \fBfilesystem label\fP\fI <dev> [newlabel]\fP
 .PP
+\fBbtrfs\fP \fBfilesystem features\fP\fI <dev|mountpoint> [[-f ]newlabel]\fP
+.PP
 \fBbtrfs\fP \fBfilesystem balance\fP\fI <path> \fP
 .PP
 \fBbtrfs\fP \fBdevice scan\fP\fI [--all-devices|<device> [<device>...]]\fP
@@ -280,6 +282,32 @@  NOTE: Currently there are the following limitations:
 - the filesystem should not have more than one device.
 .TP
 
+\fBfilesystem features\fP\fI <dev|mountpoint> [[-f] features]\fP
+Show or update the features of a filesystem. \fI<dev>\fR is used to identify an umounted filesystem.  \fI<mountpoint>\fR is used to identify a mounted filesystem.
+If a \fIfeatures\fR optional argument is passed, the features are updated.
+The following features are currently supported by the tool:
+.br
+- mixed_backref
+.br
+- default_subvol
+.br
+- mixed_groups
+.br
+- compress_lzo
+.br
+- compress_lzov2
+.br
+- big_metadata
+.br
+- extended_iref
+.br
+- raid56
+.br
+- skinny_metadata
+.IP
+It is possible, but not recommended to set undocumented features using one of the following prefixes: compat, compat_ro, incompat and a bit number, separated by a colon. e.g. compat:12. Please note that changing unrecognized feature bits is a dangerous operation and may result in an umountable file system that needs to be manually repaired by an expert. It is also possible to clear a set flag by prefixing the flag name with a caret (^).
+.TP
+
 \fBfilesystem show\fR [--all-devices|<uuid>|<label>]\fR
 Show the btrfs filesystem with some additional info. If no \fIUUID\fP or 
 \fIlabel\fP is passed, \fBbtrfs\fR show info of all the btrfs filesystem.
diff --git a/utils.c b/utils.c
index 7b4cd74..447ca9c 100644
--- a/utils.c
+++ b/utils.c
@@ -1350,6 +1350,594 @@  int set_label(const char *btrfs_dev, const char *label)
 		set_label_mounted(btrfs_dev, label);
 }
 
+enum {
+	FEAT_COMPAT = 0,
+	FEAT_COMPAT_RO,
+	FEAT_INCOMPAT,
+};
+
+static const char * const feature_type[] = {
+	"compat",
+	"compat_ro",
+	"incompat",
+};
+
+static const char * const compat_features[] = {};
+static const char * const compat_ro_features[] = {};
+static const char * const incompat_features[] = {
+	"mixed_backref",
+	"default_subvol",
+	"mixed_groups",
+	"compress_lzo",
+	"compress_lzov2",
+	"big_metadata",
+	"extended_iref",
+	"raid56",
+	"skinny_metadata",
+};
+
+#define NAMEBUFSZ 4096 /* 64 names * 64 bytes/name (safe max) */
+static char *feature_names(int type, u64 flags)
+{
+	int i;
+	int name_count;
+	const char **names;
+	int count = 0;
+	char *buf = malloc(NAMEBUFSZ);
+	char *ptr = buf;
+	char typename[10]; /* compat_ro + 1 */
+
+	if (!buf)
+		return NULL;
+
+	*buf = 0;
+
+	switch (type) {
+	case FEAT_COMPAT:
+		name_count = ARRAY_SIZE(compat_features);
+		names = compat_features;
+		break;
+	case FEAT_COMPAT_RO:
+		name_count = ARRAY_SIZE(compat_ro_features);
+		names = compat_ro_features;
+		break;
+	case FEAT_INCOMPAT:
+		name_count = ARRAY_SIZE(incompat_features);
+		names = incompat_features;
+		break;
+	default:
+		BUG_ON(1);
+	};
+
+	strcpy(typename, feature_type[type]);
+	for (i = 0; i < sizeof(typename); i++)
+		typename[i] = tolower(typename[i]);
+
+	for (i = 0; i < sizeof(flags) << 3; i++) {
+		if (!(flags & (1ULL << i)))
+			continue;
+
+		if (i >= name_count)
+			ptr += snprintf(ptr, NAMEBUFSZ, "%s%s:%u",
+				       count ? "," : "", typename, i);
+		else
+			ptr += snprintf(ptr, NAMEBUFSZ, "%s%s",
+				       count ? "," : "", names[i]);
+		count++;
+
+		BUG_ON(ptr > buf + NAMEBUFSZ);
+	}
+
+	return buf;
+}
+
+static int print_features(const char *message,
+			  struct btrfs_ioctl_feature_flags *flags)
+{
+	char *compat = NULL, *compat_ro = NULL, *incompat = NULL;
+
+	compat = feature_names(FEAT_COMPAT, flags->compat_flags);
+	compat_ro = feature_names(FEAT_COMPAT_RO, flags->compat_ro_flags);
+	incompat = feature_names(FEAT_INCOMPAT, flags->incompat_flags);
+	if (!compat || !compat_ro || !incompat)
+		goto enomem;
+
+	printf("%s: %s%s%s%s%s\n", message,
+	       compat, compat[0] ? "," : "", compat_ro,
+	       compat_ro[0] ? "," : "", incompat);
+
+	free(compat);
+	free(compat_ro);
+	free(incompat);
+
+	return 0;
+
+enomem:
+	fprintf(stderr,
+		"ERROR: Couldn't allocate memory while printing features.\n");
+	free(compat);
+	free(compat_ro);
+	free(incompat);
+	return -ENOMEM;
+}
+
+static int get_features_mounted(const char *mount_path)
+{
+	int fd;
+	struct btrfs_ioctl_feature_flags flags[3];
+	int ret;
+
+	fd = open(mount_path, O_RDONLY | O_DIRECTORY);
+	if (fd < 0) {
+		fprintf(stderr, "ERROR: unable to access '%s': %s\n",
+			mount_path, strerror(errno));
+		return -1;
+	}
+
+	if (ioctl(fd, BTRFS_IOC_GET_SUPPORTED_FEATURES, &flags) < 0) {
+		ret = -errno;
+		fprintf(stderr,
+			"ERROR: unable to get supported features from kernel: %s\n",
+			strerror(errno));
+		close(fd);
+		return errno;
+	}
+
+	ret = print_features("Features supported by running kernel",
+			     &flags[0]);
+	if (ret) {
+		close(fd);
+		return ret;
+	}
+
+	ret = print_features("Features that can be enabled by running kernel",
+			     &flags[1]);
+	if (ret) {
+		close(fd);
+		return ret;
+	}
+
+	ret = print_features("Features that can be cleared by running kernel",
+			     &flags[2]);
+	if (ret) {
+		close(fd);
+		return ret;
+	}
+
+	if (ioctl(fd, BTRFS_IOC_GET_FEATURES, &flags[0]) < 0) {
+		ret = -errno;
+		fprintf(stderr,
+			"ERROR: unable to get features from kernel: %s\n",
+			strerror(errno));
+		close(fd);
+		return ret;
+	}
+	close(fd);
+
+	return print_features("Features enabled on filesystem", &flags[0]);
+}
+
+static int get_features_unmounted(const char *btrfs_dev)
+{
+	int ret;
+	struct btrfs_root *root;
+	struct btrfs_super_block *disk_super;
+	struct btrfs_ioctl_feature_flags flags;
+
+	ret = check_mounted(btrfs_dev);
+	if (ret < 0) {
+		fprintf(stderr, "FATAL: error checking %s mount status\n",
+			btrfs_dev);
+		return -1;
+	}
+	if (ret > 0) {
+		fprintf(stderr, "ERROR: dev %s is mounted, use mount point\n",
+			btrfs_dev);
+		return -1;
+	}
+
+	/* Open the super_block at the default location
+	 * and as read-only.
+	 */
+	root = open_ctree(btrfs_dev, 0, 0);
+	if (!root)
+		return -1;
+
+	disk_super = root->fs_info->super_copy;
+
+	flags.compat_flags = btrfs_super_compat_flags(disk_super);
+	flags.compat_ro_flags = btrfs_super_compat_ro_flags(disk_super);
+	flags.incompat_flags = btrfs_super_incompat_flags(disk_super);
+
+	close_ctree(root);
+
+	return print_features("Features enabled on filesystem: ", &flags);
+}
+
+int get_features(const char *btrfs_dev)
+{
+	return is_existing_blk_or_reg_file(btrfs_dev) ?
+		get_features_unmounted(btrfs_dev) :
+		get_features_mounted(btrfs_dev);
+
+}
+
+static int __check_feature_bits(const char *source,
+				int type, u64 change_mask,
+				u64 flags, u64 supported_flags,
+				u64 allowed_set, u64 allowed_clear,
+				int force)
+{
+	u64 unsupported, set, clear;
+	const char *errlevel = force ? "WARNING" : "ERROR";
+
+	unsupported = flags & change_mask & ~supported_flags;
+	if (unsupported) {
+		char *names = feature_names(type, unsupported);
+		if (names) {
+			fprintf(stderr,
+				"%s: %s does not support %s features: %s\n",
+				errlevel, source, feature_type[type], names);
+			free(names);
+		} else
+			fprintf(stderr,
+				"%s: %s does not support %s feature bits: %llx\n",
+				errlevel, source, feature_type[type],
+				unsupported);
+	}
+
+	set = flags & ~change_mask & allowed_set & ~unsupported;
+	if (set) {
+		char *names = feature_names(type, set);
+		if (names) {
+			fprintf(stderr,
+				"%s: %s does not support online setting of %s features: %s\n",
+				errlevel, source, feature_type[type], names);
+			free(names);
+		} else
+			fprintf(stderr,
+				"%s: %s does not support online setting of %s feature bits: %llx\n",
+				errlevel, source, feature_type[type], set);
+	}
+
+	clear = ~flags & ~change_mask & allowed_clear & ~unsupported;
+	if (clear) {
+		char *names = feature_names(type, clear);
+		if (names) {
+			fprintf(stderr,
+				"%s: %s does not support online clearing of %s features: %s\n",
+				errlevel, source, feature_type[type], names);
+			free(names);
+		} else
+			fprintf(stderr,
+				"%s: %s does not support online clearing of %s feature bits: %llx\n",
+				errlevel, source, feature_type[type], clear);
+	}
+
+	if (unsupported || set || clear) {
+		if (type == FEAT_INCOMPAT)
+			fprintf(stderr,
+				"Changing an unrecognized incompatible feature may result in a\nfile system that must be manually repaired.\n");
+		return -1;
+	}
+	return 0;
+}
+
+#define check_feature_bits_mounted(a, b, c, d, e, f) \
+	__check_feature_bits("kernel", a, b, c, d, e, f, 0)
+
+#define check_feature_bits_unmounted(a, b, c, d, e) \
+	__check_feature_bits("progs", a, b, c, d, d, d, e)
+
+static int set_features_mounted(const char *mount_path,
+			const struct btrfs_ioctl_feature_flags *change_mask,
+			const struct btrfs_ioctl_feature_flags *flags)
+{
+	int fd;
+	int ret;
+	struct btrfs_ioctl_feature_flags kernel_flags[3];
+	struct btrfs_ioctl_feature_flags our_flags[2];
+
+	fd = open(mount_path, O_RDONLY | O_NOATIME);
+	if (fd < 0) {
+		fprintf(stderr, "ERROR: unable to access '%s': %s\n",
+			mount_path, strerror(errno));
+		return -1;
+	}
+
+	ret = ioctl(fd, BTRFS_IOC_GET_SUPPORTED_FEATURES, kernel_flags);
+	if (ret) {
+		fprintf(stderr,
+			"ERROR: unable to get the kernel's supported feature list: %s\n",
+			strerror(errno));
+		goto out;
+	}
+
+	/*
+	 * The kernel will perform these checks as well. We repeat the checks
+	 * in order to give the user something more helpful than -EPERM
+	 * in the failure case.
+	 */
+	ret = check_feature_bits_mounted(FEAT_COMPAT,
+					 change_mask->compat_flags,
+					 flags->compat_flags,
+					 kernel_flags[0].compat_flags,
+					 kernel_flags[1].compat_flags,
+					 kernel_flags[2].compat_flags);
+
+	ret |= check_feature_bits_mounted(FEAT_COMPAT_RO,
+					  change_mask->compat_ro_flags,
+					  flags->compat_ro_flags,
+					  kernel_flags[0].compat_ro_flags,
+					  kernel_flags[1].compat_ro_flags,
+					  kernel_flags[2].compat_ro_flags);
+
+	ret |= check_feature_bits_mounted(FEAT_INCOMPAT,
+					  change_mask->incompat_flags,
+					  flags->incompat_flags,
+					  kernel_flags[0].incompat_flags,
+					  kernel_flags[1].incompat_flags,
+					  kernel_flags[2].incompat_flags);
+	if (ret)
+		goto out;
+
+	our_flags[0].compat_flags = change_mask->compat_flags;
+	our_flags[1].compat_flags = flags->compat_flags;
+	our_flags[0].compat_ro_flags = change_mask->compat_ro_flags;
+	our_flags[1].compat_ro_flags = flags->compat_ro_flags;
+	our_flags[0].incompat_flags = change_mask->incompat_flags;
+	our_flags[1].incompat_flags = flags->incompat_flags;
+
+	ret = ioctl(fd, BTRFS_IOC_SET_FEATURES, our_flags);
+	if (ret)
+		fprintf(stderr, "ERROR: failed to change features: %s\n",
+			strerror(errno));
+out:
+	close(fd);
+	return ret;
+}
+
+static int set_features_unmounted(const char *device,
+			const struct btrfs_ioctl_feature_flags *change_mask,
+			const struct btrfs_ioctl_feature_flags *flags,
+			int force)
+{
+	struct btrfs_root *root;
+	struct btrfs_trans_handle *trans;
+	struct btrfs_super_block *disk_super;
+	u64 super_flags;
+	int ret;
+
+	ret = check_feature_bits_unmounted(FEAT_COMPAT,
+					   change_mask->compat_flags,
+					   flags->compat_flags,
+					   BTRFS_FEATURE_COMPAT_SUPP,
+					   force);
+	ret |= check_feature_bits_unmounted(FEAT_COMPAT_RO,
+					    change_mask->compat_ro_flags,
+					    flags->compat_ro_flags,
+					    BTRFS_FEATURE_COMPAT_RO_SUPP,
+					    force);
+	ret |= check_feature_bits_unmounted(FEAT_INCOMPAT,
+					    change_mask->incompat_flags,
+					    flags->incompat_flags,
+					    BTRFS_FEATURE_INCOMPAT_SUPP,
+					    force);
+
+	if (((change_mask->compat_flags & ~flags->compat_flags) ||
+	     (change_mask->compat_ro_flags & ~flags->compat_ro_flags) ||
+	     (change_mask->incompat_flags & ~flags->incompat_flags)) &&
+	    !force) {
+		fprintf(stderr, "ERROR\nclearing flags is dangerous and requires the -f option to continue.\nif clearing a flag that indicates the presence of an on-disk feature\nthat is in use, serious corruption and/or crashes may result.\nPROCEED WITH CAUTION.\n");
+		return -1;
+	}
+
+	if (ret && !force)
+		return 1;
+
+	ret = check_mounted(device);
+	if (ret < 0) {
+		fprintf(stderr, "FATAL: error checking %s mount status\n",
+			device);
+		return -1;
+	}
+	if (ret > 0) {
+		fprintf(stderr, "ERROR: dev %s is mounted, use mount point\n",
+			device);
+		return -1;
+	}
+
+	root = open_ctree(device, 0, 1);
+	if (!root) {
+		fprintf(stderr, "Open ctree failed\n");
+		return 1;
+	}
+
+	disk_super = root->fs_info->super_copy;
+
+	trans = btrfs_start_transaction(root, 1);
+
+	super_flags = btrfs_super_compat_flags(disk_super);
+	super_flags |= (change_mask->compat_flags & flags->compat_flags);
+	super_flags &= ~(change_mask->compat_flags &
+			 ~flags->compat_flags);
+	btrfs_set_super_compat_flags(disk_super, super_flags);
+
+	super_flags = btrfs_super_compat_ro_flags(disk_super);
+	super_flags |= (change_mask->compat_ro_flags & flags->compat_ro_flags);
+	super_flags &= ~(change_mask->compat_ro_flags &
+			 ~flags->compat_ro_flags);
+	btrfs_set_super_compat_ro_flags(disk_super, super_flags);
+
+	super_flags = btrfs_super_incompat_flags(disk_super);
+	super_flags |= (change_mask->incompat_flags & flags->incompat_flags);
+	super_flags &= ~(change_mask->incompat_flags &
+			 ~flags->incompat_flags);
+	btrfs_set_super_incompat_flags(disk_super, super_flags);
+
+	btrfs_commit_transaction(trans, root);
+
+	return 0;
+}
+
+static int parse_numbered_feature(const char *feature, int set,
+				  struct btrfs_ioctl_feature_flags *change_mask,
+				  struct btrfs_ioctl_feature_flags *flags)
+{
+	int ret = -1;
+	u64 *fptr = NULL;
+	u64 *mptr = NULL;
+	char *fname = strdup(feature);
+	char *colon = strchr(fname, ':');
+	unsigned long num;
+
+	if (!colon)
+		goto fail;
+
+	*colon++ = 0;
+
+	num = strtoul(colon, NULL, 10);
+	if (num == ULONG_MAX) {
+		fprintf(stderr,
+			"ERROR: invalid numbered feature predicate: %s\n",
+			colon);
+		goto fail;
+	}
+
+	if (!strcmp(fname, "compat")) {
+		mptr = &change_mask->compat_flags;
+		fptr = &flags->compat_flags;
+	} else if (!strcmp(fname, "compat_ro")) {
+		mptr = &change_mask->compat_ro_flags;
+		fptr = &flags->compat_ro_flags;
+	} else if (!strcmp(fname, "incompat")) {
+		mptr = &change_mask->incompat_flags;
+		fptr = &flags->incompat_flags;
+	}
+
+	if (!fptr) {
+		fprintf(stderr,
+			"ERROR: invalid numbered feature prefix: %s\n", fname);
+
+		goto fail;
+	}
+
+	*mptr |= (1ULL << num);
+	if (set)
+		*fptr |= (1ULL << num);
+	else
+		*fptr &= ~(1ULL << num);
+
+	ret = 0;
+fail:
+	free(fname);
+	return ret;
+
+}
+
+int parse_features(const char *features,
+		   struct btrfs_ioctl_feature_flags *change_mask,
+		   struct btrfs_ioctl_feature_flags *flags)
+{
+	char *s, *ptr, *tmp;
+	int ret;
+
+	memset(change_mask, 0, sizeof(*change_mask));
+	memset(flags, 0, sizeof(*flags));
+
+	ptr = tmp = strdup(features);
+
+	while ((s = strsep(&tmp, ","))) {
+		int i;
+		int set = 1;
+		u64 bit;
+		if (s[0] == '^') {
+			set = 0;
+			s++;
+		}
+
+		if (strchr(s, ':')) {
+			ret = parse_numbered_feature(s, set, change_mask,
+						     flags);
+			if (ret)
+				goto out;
+			continue;
+		}
+		for (i = 0; i < ARRAY_SIZE(compat_features); i++) {
+			if (!strcmp(compat_features[i], s)) {
+				bit = (1ULL << i);
+				change_mask->compat_flags |= bit;
+				if (set)
+					flags->compat_flags |= bit;
+				else
+					flags->compat_flags &= ~bit;
+				s = NULL;
+				break;
+			}
+		}
+
+		for (i = 0; i < ARRAY_SIZE(compat_ro_features); i++) {
+			if (!strcmp(compat_ro_features[i], s)) {
+				bit = (1ULL << i);
+				change_mask->compat_ro_flags |= bit;
+				if (set)
+					flags->compat_ro_flags |= bit;
+				else
+					flags->compat_ro_flags &= ~bit;
+				s = NULL;
+				break;
+			}
+		}
+
+		for (i = 0; i < ARRAY_SIZE(incompat_features); i++) {
+			if (!strcmp(incompat_features[i], s)) {
+				bit = (1ULL << i);
+				change_mask->incompat_flags |= bit;
+				if (set)
+					flags->incompat_flags |= bit;
+				else
+					flags->incompat_flags &= ~bit;
+				s = NULL;
+				break;
+			}
+		}
+
+		if (s) {
+			fprintf(stderr, "ERROR: unknown named feature %s\n", s);
+			ret = -1;
+			goto out;
+		}
+	}
+
+	ret = 0;
+out:
+	free(ptr);
+	return ret;
+}
+
+int set_features(const char *dev,
+		 const struct btrfs_ioctl_feature_flags *change_mask,
+		 const struct btrfs_ioctl_feature_flags *flags, int force)
+{
+	if (is_existing_blk_or_reg_file(dev))
+		return set_features_unmounted(dev, change_mask, flags, force);
+
+	return set_features_mounted(dev, change_mask, flags);
+}
+
+int parse_and_set_features(const char *btrfs_dev, const char *features,
+			   int force)
+{
+	int ret;
+	struct btrfs_ioctl_feature_flags change_mask, flags;
+
+	ret = parse_features(features, &change_mask, &flags);
+	if (ret)
+		return ret;
+
+	return set_features(btrfs_dev, &change_mask, &flags, force);
+}
+
 int btrfs_scan_block_devices(int run_ioctl)
 {
 
diff --git a/utils.h b/utils.h
index 3c17e14..6faad28 100644
--- a/utils.h
+++ b/utils.h
@@ -55,6 +55,15 @@  int get_fs_info(char *path, struct btrfs_ioctl_fs_info_args *fi_args,
 		struct btrfs_ioctl_dev_info_args **di_ret);
 int get_label(const char *btrfs_dev);
 int set_label(const char *btrfs_dev, const char *label);
+int get_features(const char *btrfs_dev);
+int parse_features(const char *features,
+		   struct btrfs_ioctl_feature_flags *change_mask,
+		   struct btrfs_ioctl_feature_flags *flags);
+int set_features(const char *btrfs_dev,
+		 const struct btrfs_ioctl_feature_flags *change_mask,
+		 const struct btrfs_ioctl_feature_flags *flags, int force);
+int parse_and_set_features(const char *btrfs_dev, const char *features,
+			   int force);
 
 char *__strncpy__null(char *dest, const char *src, size_t n);
 int is_block_device(const char *file);