diff --git a/c_src/cmd_device.c b/c_src/cmd_device.c
index c86fb7f1..a1e78e25 100644
--- a/c_src/cmd_device.c
+++ b/c_src/cmd_device.c
@@ -47,11 +47,11 @@ static void device_add_usage(void)
 	puts("bcachefs device add - add a device to an existing filesystem\n"
 	     "Usage: bcachefs device add [OPTION]... filesystem device\n"
 	     "\n"
-	     "Options:\n"
-	     "  -S, --fs_size=size          Size of filesystem on device\n"
-	     "  -B, --bucket=size           Bucket size\n"
-	     "  -D, --discard               Enable discards\n"
-	     "  -l, --label=label           Disk label\n"
+	     "Options:\n");
+
+	bch2_opts_usage(OPT_FORMAT|OPT_DEVICE);
+
+	puts("  -l, --label=label           Disk label\n"
 	     "  -f, --force                 Use device even if it appears to already be formatted\n"
 	     "  -h, --help                  Display this help and exit\n"
 	     "\n"
@@ -61,9 +61,6 @@ static void device_add_usage(void)
 int cmd_device_add(int argc, char *argv[])
 {
 	static const struct option longopts[] = {
-		{ "fs_size",		required_argument,	NULL, 'S' },
-		{ "bucket",		required_argument,	NULL, 'B' },
-		{ "discard",		no_argument,		NULL, 'D' },
 		{ "label",		required_argument,	NULL, 'l' },
 		{ "force",		no_argument,		NULL, 'f' },
 		{ "help",		no_argument,		NULL, 'h' },
@@ -72,22 +69,31 @@ int cmd_device_add(int argc, char *argv[])
 	struct format_opts format_opts	= format_opts_default();
 	struct dev_opts dev_opts	= dev_opts_default();
 	bool force = false;
-	int opt;
 
-	while ((opt = getopt_long(argc, argv, "S:B:Dl:fh",
-				  longopts, NULL)) != -1)
-		switch (opt) {
-		case 'S':
-			if (bch2_strtoull_h(optarg, &dev_opts.size))
-				die("invalid filesystem size");
-			break;
-		case 'B':
-			if (bch2_strtoull_h(optarg, &dev_opts.bucket_size))
-				die("bad bucket_size %s", optarg);
-			break;
-		case 'D':
-			dev_opts.discard = true;
+	while (true) {
+		const struct bch_option *opt =
+			bch2_cmdline_opt_parse(argc, argv, OPT_FORMAT|OPT_DEVICE);
+		if (opt) {
+			unsigned id = opt - bch2_opt_table;
+			u64 v;
+			struct printbuf err = PRINTBUF;
+			int ret = bch2_opt_parse(NULL, opt, optarg, &v, &err);
+			if (ret)
+				die("invalid %s: %s", opt->attr.name, err.buf);
+
+			if (opt->flags & OPT_DEVICE)
+				bch2_opt_set_by_id(&dev_opts.opts, id, v);
+			else
+				die("got bch_opt of wrong type %s", opt->attr.name);
+
+			continue;
+		}
+
+		int optid = getopt_long(argc, argv, "S:B:Dl:fh", longopts, NULL);
+		if (optid == -1)
 			break;
+
+		switch (optid) {
 		case 'l':
 			dev_opts.label = strdup(optarg);
 			break;
@@ -97,7 +103,11 @@ int cmd_device_add(int argc, char *argv[])
 		case 'h':
 			device_add_usage();
 			exit(EXIT_SUCCESS);
+		case '?':
+			exit(EXIT_FAILURE);
+			break;
 		}
+}
 	args_shift(optind);
 
 	char *fs_path = arg_pop();
diff --git a/c_src/cmd_format.c b/c_src/cmd_format.c
index b103d2bd..4fbf3a4b 100644
--- a/c_src/cmd_format.c
+++ b/c_src/cmd_format.c
@@ -39,11 +39,7 @@ x('L',	fs_label,		required_argument)	\
 x('U',	uuid,			required_argument)	\
 x(0,	fs_size,		required_argument)	\
 x(0,	superblock_size,	required_argument)	\
-x(0,	bucket_size,		required_argument)	\
 x('l',	label,			required_argument)	\
-x(0,	discard,		no_argument)		\
-x(0,	data_allowed,		required_argument)	\
-x(0,	durability,		required_argument)	\
 x(0,	version,		required_argument)	\
 x(0,	no_initialize,		no_argument)		\
 x(0,	source,			required_argument)	\
@@ -52,17 +48,16 @@ x('q',	quiet,			no_argument)		\
 x('v',	verbose,		no_argument)		\
 x('h',	help,			no_argument)
 
-static void usage(void)
+static void format_usage(void)
 {
 	puts("bcachefs format - create a new bcachefs filesystem on one or more devices\n"
 	     "Usage: bcachefs format [OPTION]... <devices>\n"
 	     "\n"
 	     "Options:");
 
-	bch2_opts_usage(OPT_FORMAT);
+	bch2_opts_usage(OPT_FORMAT|OPT_FS);
 
-	puts(
-	     "      --replicas=#            Sets both data and metadata replicas\n"
+	puts("      --replicas=#            Sets both data and metadata replicas\n"
 	     "      --encrypted             Enable whole filesystem encryption (chacha20/poly1305)\n"
 	     "      --no_passphrase         Don't encrypt master encryption key\n"
 	     "  -L, --fs_label=label\n"
@@ -72,9 +67,10 @@ static void usage(void)
 	     "\n"
 	     "Device specific options:");
 
-	bch2_opts_usage(OPT_DEVICE);
+	bch2_opts_usage(OPT_FORMAT|OPT_DEVICE);
 
-	puts("  -l, --label=label           Disk label\n"
+	puts("      --fs_size=size          Size of filesystem on device\n"
+	     "  -l, --label=label           Disk label\n"
 	     "\n"
 	     "  -f, --force\n"
 	     "  -q, --quiet                 Only print errors\n"
@@ -137,20 +133,48 @@ int cmd_format(int argc, char *argv[])
 	bool force = false, no_passphrase = false, quiet = false, initialize = true, verbose = false;
 	bool unconsumed_dev_option = false;
 	unsigned v;
-	int opt;
 
-	struct bch_opt_strs fs_opt_strs =
-		bch2_cmdline_opts_get(&argc, argv, OPT_FORMAT);
-	struct bch_opts fs_opts = bch2_parse_opts(fs_opt_strs);
+	struct bch_opt_strs fs_opt_strs = {};
+	struct bch_opts fs_opts = bch2_opts_empty();
 
 	if (getenv("BCACHEFS_KERNEL_ONLY"))
 		initialize = false;
 
-	while ((opt = getopt_long(argc, argv,
-				  "-L:l:U:g:fqhv",
-				  format_opts,
-				  NULL)) != -1)
-		switch (opt) {
+	while (true) {
+		const struct bch_option *opt =
+			bch2_cmdline_opt_parse(argc, argv, OPT_FORMAT|OPT_FS|OPT_DEVICE);
+		if (opt) {
+			unsigned id = opt - bch2_opt_table;
+			u64 v;
+			struct printbuf err = PRINTBUF;
+			int ret = bch2_opt_parse(NULL, opt, optarg, &v, &err);
+			if (ret == -BCH_ERR_option_needs_open_fs) {
+				fs_opt_strs.by_id[id] = strdup(optarg);
+				continue;
+			}
+			if (ret)
+				die("invalid option: %s", err.buf);
+
+			if (opt->flags & OPT_DEVICE) {
+				bch2_opt_set_by_id(&dev_opts.opts, id, v);
+				unconsumed_dev_option = true;
+			} else if (opt->flags & OPT_FS) {
+				bch2_opt_set_by_id(&fs_opts, id, v);
+			} else {
+				die("got bch_opt of wrong type %s", opt->attr.name);
+			}
+
+			continue;
+		}
+
+		int optid = getopt_long(argc, argv,
+					"-L:l:U:g:fqhv",
+					format_opts,
+					NULL);
+		if (optid == -1)
+			break;
+
+		switch (optid) {
 		case O_replicas:
 			if (kstrtouint(optarg, 10, &v) ||
 			    !v ||
@@ -183,8 +207,9 @@ int cmd_format(int argc, char *argv[])
 			force = true;
 			break;
 		case O_fs_size:
-			if (bch2_strtoull_h(optarg, &dev_opts.size))
+			if (bch2_strtoull_h(optarg, &dev_opts.opts.fs_size))
 				die("invalid filesystem size");
+			dev_opts.opts.fs_size_defined = true;
 			unconsumed_dev_option = true;
 			break;
 		case O_superblock_size:
@@ -193,32 +218,11 @@ int cmd_format(int argc, char *argv[])
 
 			opts.superblock_size >>= 9;
 			break;
-		case O_bucket_size:
-			if (bch2_strtoull_h(optarg, &dev_opts.bucket_size))
-				die("bad bucket_size %s", optarg);
-			unconsumed_dev_option = true;
-			break;
 		case O_label:
 		case 'l':
 			dev_opts.label = optarg;
 			unconsumed_dev_option = true;
 			break;
-		case O_discard:
-			dev_opts.discard = true;
-			unconsumed_dev_option = true;
-			break;
-		case O_data_allowed:
-			dev_opts.data_allowed =
-				read_flag_list_or_die(optarg,
-					__bch2_data_types, "data type");
-			unconsumed_dev_option = true;
-			break;
-		case O_durability:
-			if (kstrtouint(optarg, 10, &dev_opts.durability) ||
-			    dev_opts.durability > BCH_REPLICAS_MAX)
-				die("invalid durability");
-			unconsumed_dev_option = true;
-			break;
 		case O_version:
 			opts.version = version_parse(optarg);
 			break;
@@ -229,7 +233,8 @@ int cmd_format(int argc, char *argv[])
 			darray_push(&device_paths, optarg);
 			dev_opts.path = optarg;
 			darray_push(&devices, dev_opts);
-			dev_opts.size = 0;
+			dev_opts.opts.fs_size = 0;
+			dev_opts.opts.fs_size_defined = 0;
 			unconsumed_dev_option = false;
 			break;
 		case O_quiet:
@@ -241,13 +246,16 @@ int cmd_format(int argc, char *argv[])
 			break;
 		case O_help:
 		case 'h':
-			usage();
+			format_usage();
 			exit(EXIT_SUCCESS);
 			break;
 		case '?':
 			exit(EXIT_FAILURE);
 			break;
+		default:
+			die("getopt ret %i %c", optid, optid);
 		}
+	}
 
 	if (unconsumed_dev_option)
 		die("Options for devices apply to subsequent devices; got a device option with no device");
diff --git a/c_src/cmd_migrate.c b/c_src/cmd_migrate.c
index bce115b7..698cfd55 100644
--- a/c_src/cmd_migrate.c
+++ b/c_src/cmd_migrate.c
@@ -159,9 +159,9 @@ static void find_superblock_space(ranges extents,
 {
 	darray_for_each(extents, i) {
 		u64 start = round_up(max(256ULL << 10, i->start),
-				     dev->bucket_size << 9);
+				     dev->opts.bucket_size << 9);
 		u64 end = round_down(i->end,
-				     dev->bucket_size << 9);
+				     dev->opts.bucket_size << 9);
 
 		/* Need space for two superblocks: */
 		if (start + (opts.superblock_size << 9) * 2 <= end) {
@@ -225,9 +225,9 @@ static int migrate_fs(const char		*fs_path,
 	printf("Creating new filesystem on %s in space reserved at %s\n",
 	       dev.path, file_path);
 
-	dev.size	= get_size(dev.bdev->bd_fd);
-	dev.bucket_size = bch2_pick_bucket_size(fs_opts, &dev);
-	dev.nbuckets	= dev.size / dev.bucket_size;
+	dev.opts.fs_size	= get_size(dev.bdev->bd_fd);
+	dev.opts.bucket_size	= bch2_pick_bucket_size(fs_opts, &dev);
+	dev.nbuckets		= dev.opts.fs_size / dev.opts.bucket_size;
 
 	bch2_check_bucket_size(fs_opts, &dev);
 
diff --git a/c_src/cmd_option.c b/c_src/cmd_option.c
index 99e397b9..02faca07 100644
--- a/c_src/cmd_option.c
+++ b/c_src/cmd_option.c
@@ -30,7 +30,7 @@ static void set_option_usage(void)
 	     "Usage: bcachefs set-fs-option [OPTION].. device\n"
 	     "\n"
 	     "Options:\n");
-	bch2_opts_usage(OPT_MOUNT);
+	bch2_opts_usage(OPT_MOUNT|OPT_RUNTIME);
 	puts("  -d, --dev-idx               index for device specific options\n"
 	     "  -h, --help                  display this help and exit\n"
 	     "Report bugs to <linux-bcachefs@vger.kernel.org>");
diff --git a/c_src/libbcachefs.c b/c_src/libbcachefs.c
index c3af991b..d212b6ef 100644
--- a/c_src/libbcachefs.c
+++ b/c_src/libbcachefs.c
@@ -68,26 +68,23 @@ static u64 min_size(unsigned bucket_size)
 
 u64 bch2_pick_bucket_size(struct bch_opts opts, struct dev_opts *dev)
 {
-	u64 bucket_size;
-
-	if (dev->size < min_size(opts.block_size))
+	if (dev->opts.fs_size < min_size(opts.block_size))
 		die("cannot format %s, too small (%llu bytes, min %llu)",
-		    dev->path, dev->size, min_size(opts.block_size));
+		    dev->path, dev->opts.fs_size, min_size(opts.block_size));
 
 	/* Bucket size must be >= block size: */
-	bucket_size = opts.block_size;
+	u64 bucket_size = opts.block_size;
 
 	/* Bucket size must be >= btree node size: */
 	if (opt_defined(opts, btree_node_size))
-		bucket_size = max_t(unsigned, bucket_size,
-					 opts.btree_node_size);
+		bucket_size = max_t(unsigned, bucket_size, opts.btree_node_size);
 
 	/* Want a bucket size of at least 128k, if possible: */
 	bucket_size = max(bucket_size, 128ULL << 10);
 
-	if (dev->size >= min_size(bucket_size)) {
+	if (dev->opts.fs_size >= min_size(bucket_size)) {
 		unsigned scale = max(1,
-			ilog2(dev->size / min_size(bucket_size)) / 4);
+			ilog2(dev->opts.fs_size / min_size(bucket_size)) / 4);
 
 		scale = rounddown_pow_of_two(scale);
 
@@ -96,7 +93,7 @@ u64 bch2_pick_bucket_size(struct bch_opts opts, struct dev_opts *dev)
 	} else {
 		do {
 			bucket_size /= 2;
-		} while (dev->size < min_size(bucket_size));
+		} while (dev->opts.fs_size < min_size(bucket_size));
 	}
 
 	return bucket_size;
@@ -104,22 +101,18 @@ u64 bch2_pick_bucket_size(struct bch_opts opts, struct dev_opts *dev)
 
 void bch2_check_bucket_size(struct bch_opts opts, struct dev_opts *dev)
 {
-	if (dev->bucket_size < opts.block_size)
-		die("Bucket size (%llu) cannot be smaller than block size (%u)",
-		    dev->bucket_size, opts.block_size);
+	if (dev->opts.bucket_size < opts.block_size)
+		die("Bucket size (%u) cannot be smaller than block size (%u)",
+		    dev->opts.bucket_size, opts.block_size);
 
 	if (opt_defined(opts, btree_node_size) &&
-	    dev->bucket_size < opts.btree_node_size)
-		die("Bucket size (%llu) cannot be smaller than btree node size (%u)",
-		    dev->bucket_size, opts.btree_node_size);
+	    dev->opts.bucket_size < opts.btree_node_size)
+		die("Bucket size (%u) cannot be smaller than btree node size (%u)",
+		    dev->opts.bucket_size, opts.btree_node_size);
 
 	if (dev->nbuckets < BCH_MIN_NR_NBUCKETS)
-		die("Not enough buckets: %llu, need %u (bucket size %llu)",
-		    dev->nbuckets, BCH_MIN_NR_NBUCKETS, dev->bucket_size);
-
-	if (dev->bucket_size > (u32) U16_MAX << 9)
-		die("Bucket size (%llu) too big (max %u)",
-		    dev->bucket_size, (u32) U16_MAX << 9);
+		die("Not enough buckets: %llu, need %u (bucket size %u)",
+		    dev->nbuckets, BCH_MIN_NR_NBUCKETS, dev->opts.bucket_size);
 }
 
 static unsigned parse_target(struct bch_sb_handle *sb,
@@ -144,6 +137,17 @@ static unsigned parse_target(struct bch_sb_handle *sb,
 	return 0;
 }
 
+static void bch2_opt_set_sb_all(struct bch_sb *sb, int dev_idx, struct bch_opts *opts)
+{
+	for (unsigned id = 0; id < bch2_opts_nr; id++) {
+		u64 v = bch2_opt_defined_by_id(opts, id)
+			? bch2_opt_get_by_id(opts, id)
+			: bch2_opt_get_by_id(&bch2_opts_default, id);
+
+		__bch2_opt_set_sb(sb, dev_idx, &bch2_opt_table[id], v);
+	}
+}
+
 struct bch_sb *bch2_format(struct bch_opt_strs	fs_opt_strs,
 			   struct bch_opts	fs_opts,
 			   struct format_opts	opts,
@@ -153,47 +157,45 @@ struct bch_sb *bch2_format(struct bch_opt_strs	fs_opt_strs,
 	struct bch_sb_handle sb = { NULL };
 	struct dev_opts *i;
 	unsigned max_dev_block_size = 0;
-	unsigned opt_id;
 	u64 min_bucket_size = U64_MAX;
 
 	for (i = devs; i < devs + nr_devs; i++)
 		max_dev_block_size = max(max_dev_block_size, get_blocksize(i->bdev->bd_fd));
 
 	/* calculate block size: */
-	if (!opt_defined(fs_opts, block_size)) {
+	if (!opt_defined(fs_opts, block_size))
 		opt_set(fs_opts, block_size, max_dev_block_size);
-	} else if (fs_opts.block_size < max_dev_block_size)
+
+	if (fs_opts.block_size < max_dev_block_size)
 		die("blocksize too small: %u, must be greater than device blocksize %u",
 		    fs_opts.block_size, max_dev_block_size);
 
 	/* get device size, if it wasn't specified: */
 	for (i = devs; i < devs + nr_devs; i++)
-		if (!i->size)
-			i->size = get_size(i->bdev->bd_fd);
+		if (!opt_defined(i->opts, fs_size))
+			opt_set(i->opts, fs_size, get_size(i->bdev->bd_fd));
 
 	/* calculate bucket sizes: */
 	for (i = devs; i < devs + nr_devs; i++)
 		min_bucket_size = min(min_bucket_size,
-			i->bucket_size ?: bch2_pick_bucket_size(fs_opts, i));
+			i->opts.bucket_size ?: bch2_pick_bucket_size(fs_opts, i));
 
 	for (i = devs; i < devs + nr_devs; i++)
-		if (!i->bucket_size)
-			i->bucket_size = min_bucket_size;
+		if (!opt_defined(i->opts, bucket_size))
+			opt_set(i->opts, bucket_size, min_bucket_size);
 
 	for (i = devs; i < devs + nr_devs; i++) {
-		i->nbuckets = i->size / i->bucket_size;
+		i->nbuckets = i->opts.fs_size / i->opts.bucket_size;
 		bch2_check_bucket_size(fs_opts, i);
 	}
 
 	/* calculate btree node size: */
 	if (!opt_defined(fs_opts, btree_node_size)) {
-		/* 256k default btree node size */
-		opt_set(fs_opts, btree_node_size, 256 << 10);
+		unsigned s = bch2_opts_default.btree_node_size;
 
 		for (i = devs; i < devs + nr_devs; i++)
-			fs_opts.btree_node_size =
-				min_t(unsigned, fs_opts.btree_node_size,
-				      i->bucket_size);
+			s = min(s, i->opts.bucket_size);
+		opt_set(fs_opts, btree_node_size, s);
 	}
 
 	if (uuid_is_null(opts.uuid.b))
@@ -219,17 +221,7 @@ struct bch_sb *bch2_format(struct bch_opt_strs	fs_opt_strs,
 		       opts.label,
 		       min(strlen(opts.label), sizeof(sb.sb->label)));
 
-	for (opt_id = 0;
-	     opt_id < bch2_opts_nr;
-	     opt_id++) {
-		u64 v;
-
-		v = bch2_opt_defined_by_id(&fs_opts, opt_id)
-			? bch2_opt_get_by_id(&fs_opts, opt_id)
-			: bch2_opt_get_by_id(&bch2_opts_default, opt_id);
-
-		__bch2_opt_set_sb(sb.sb, -1, &bch2_opt_table[opt_id], v);
-	}
+	bch2_opt_set_sb_all(sb.sb, -1, &fs_opts);
 
 	struct timespec now;
 	if (clock_gettime(CLOCK_REALTIME, &now))
@@ -245,16 +237,13 @@ struct bch_sb *bch2_format(struct bch_opt_strs	fs_opt_strs,
 			nr_devs) / sizeof(u64));
 	mi->member_bytes = cpu_to_le16(sizeof(struct bch_member));
 	for (i = devs; i < devs + nr_devs; i++) {
-		struct bch_member *m = bch2_members_v2_get_mut(sb.sb, (i - devs));
+		unsigned idx = i - devs;
+		struct bch_member *m = bch2_members_v2_get_mut(sb.sb, idx);
 
 		uuid_generate(m->uuid.b);
 		m->nbuckets	= cpu_to_le64(i->nbuckets);
 		m->first_bucket	= 0;
-		m->bucket_size	= cpu_to_le16(i->bucket_size >> 9);
-
-		SET_BCH_MEMBER_DISCARD(m,	i->discard);
-		SET_BCH_MEMBER_DATA_ALLOWED(m,	i->data_allowed);
-		SET_BCH_MEMBER_DURABILITY(m,	i->durability + 1);
+		bch2_opt_set_sb_all(sb.sb, idx, &i->opts);
 	}
 
 	/* Disk labels*/
@@ -298,7 +287,7 @@ struct bch_sb *bch2_format(struct bch_opt_strs	fs_opt_strs,
 	bch2_sb_members_cpy_v2_v1(&sb);
 
 	for (i = devs; i < devs + nr_devs; i++) {
-		u64 size_sectors = i->size >> 9;
+		u64 size_sectors = i->opts.fs_size >> 9;
 
 		sb.sb->dev_idx = i - devs;
 
@@ -322,7 +311,7 @@ struct bch_sb *bch2_format(struct bch_opt_strs	fs_opt_strs,
 			struct bch_sb_layout *l = &sb.sb->layout;
 			u64 backup_sb = size_sectors - (1 << l->sb_max_size_bits);
 
-			backup_sb = rounddown(backup_sb, i->bucket_size >> 9);
+			backup_sb = rounddown(backup_sb, i->opts.bucket_size >> 9);
 			l->sb_offset[l->nr_superblocks++] = cpu_to_le64(backup_sb);
 		}
 
@@ -619,6 +608,8 @@ int bchu_data(struct bchfs_handle fs, struct bch_ioctl_data cmd)
 
 /* option parsing */
 
+#include <getopt.h>
+
 void bch2_opt_strs_free(struct bch_opt_strs *opts)
 {
 	unsigned i;
@@ -629,6 +620,64 @@ void bch2_opt_strs_free(struct bch_opt_strs *opts)
 	}
 }
 
+static bool opt_type_filter(const struct bch_option *opt, unsigned opt_types)
+{
+	if (!(opt->flags & opt_types))
+		return false;
+
+	if ((opt_types & OPT_FORMAT) &&
+	    !opt->set_sb && !opt->set_member)
+		return false;
+
+	return true;
+}
+
+const struct bch_option *bch2_cmdline_opt_parse(int argc, char *argv[],
+						unsigned opt_types)
+{
+	if (optind >= argc)
+		return NULL;
+
+	if (argv[optind][0] != '-' ||
+	    argv[optind][1] != '-')
+		return NULL;
+
+	char *optstr = strdup(argv[optind] + 2);
+	optarg = argv[optind + 1];
+
+	char *eq = strchr(optstr, '=');
+	if (eq) {
+		*eq = '\0';
+		optarg = eq + 1;
+	}
+
+	if (!optarg)
+		optarg = "1";
+
+
+	int optid = bch2_opt_lookup(optstr);
+	if (optid < 0)
+		goto noopt;
+
+	const struct bch_option *opt = bch2_opt_table + optid;
+	if (!opt_type_filter(opt, opt_types))
+		goto noopt;
+
+	optind++;
+
+	if (opt->type != BCH_OPT_BOOL) {
+		if (optarg == argv[optind])
+			optind++;
+	} else {
+		optarg = NULL;
+	}
+
+	return opt;
+noopt:
+	free(optstr);
+	return NULL;
+}
+
 struct bch_opt_strs bch2_cmdline_opts_get(int *argc, char *argv[],
 					  unsigned opt_types)
 {
@@ -716,19 +765,17 @@ struct bch_opts bch2_parse_opts(struct bch_opt_strs strs)
 #define newline(c)		\
 	do {			\
 		printf("\n");	\
-		c = 0;	 	\
+		c = 0;		\
 	} while(0)
 void bch2_opts_usage(unsigned opt_types)
 {
 	const struct bch_option *opt;
 	unsigned i, c = 0, helpcol = 30;
 
-
-
 	for (opt = bch2_opt_table;
 	     opt < bch2_opt_table + bch2_opts_nr;
 	     opt++) {
-		if (!(opt->flags & opt_types))
+		if (!opt_type_filter(opt, opt_types))
 			continue;
 
 		c += printf("      --%s", opt->attr.name);
diff --git a/c_src/libbcachefs.h b/c_src/libbcachefs.h
index 026a3def..05ba1069 100644
--- a/c_src/libbcachefs.h
+++ b/c_src/libbcachefs.h
@@ -28,6 +28,9 @@ struct {
 };
 
 void bch2_opt_strs_free(struct bch_opt_strs *);
+
+const struct bch_option *bch2_cmdline_opt_parse(int argc, char *argv[],
+						unsigned opt_types);
 struct bch_opt_strs bch2_cmdline_opts_get(int *, char *[], unsigned);
 struct bch_opts bch2_parse_opts(struct bch_opt_strs);
 void bch2_opts_usage(unsigned);
@@ -58,25 +61,20 @@ struct dev_opts {
 	struct file	*file;
 	struct block_device *bdev;
 	char		*path;
-	u64		size;		/* bytes*/
-	u64		bucket_size;	/* bytes */
-	const char	*label;
-	unsigned	data_allowed;
-	unsigned	durability;
-	bool		discard;
-
-	u64		nbuckets;
 
 	u64		sb_offset;
 	u64		sb_end;
+
+	u64		nbuckets;
+
+	const char	*label; /* make this a bch_opt */
+
+	struct bch_opts	opts;
 };
 
 static inline struct dev_opts dev_opts_default()
 {
-	return (struct dev_opts) {
-		.data_allowed		= ~0U << 2,
-		.durability		= 1,
-	};
+	return (struct dev_opts) { .opts = bch2_opts_empty() };
 }
 
 void bch2_sb_layout_init(struct bch_sb_layout *,
diff --git a/libbcachefs/opts.c b/libbcachefs/opts.c
index a980b38b..24a46103 100644
--- a/libbcachefs/opts.c
+++ b/libbcachefs/opts.c
@@ -684,12 +684,10 @@ void __bch2_opt_set_sb(struct bch_sb *sb, int dev_idx,
 	if (opt->flags & OPT_SB_FIELD_ONE_BIAS)
 		v++;
 
-	if ((opt->flags & OPT_FS) && opt->set_sb)
+	if ((opt->flags & OPT_FS) && opt->set_sb && dev_idx < 0)
 		opt->set_sb(sb, v);
 
-	if ((opt->flags & OPT_DEVICE) &&
-	    opt->set_member &&
-	    dev_idx >= 0) {
+	if ((opt->flags & OPT_DEVICE) && opt->set_member && dev_idx >= 0) {
 		if (WARN(!bch2_member_exists(sb, dev_idx),
 			 "tried to set device option %s on nonexistent device %i",
 			 opt->attr.name, dev_idx))
diff --git a/libbcachefs/opts.h b/libbcachefs/opts.h
index 0bf39e4b..8d1dc881 100644
--- a/libbcachefs/opts.h
+++ b/libbcachefs/opts.h
@@ -128,13 +128,13 @@ enum fsck_err_opts {
 	  OPT_FS|OPT_FORMAT|						\
 	  OPT_HUMAN_READABLE|OPT_MUST_BE_POW_2|OPT_SB_FIELD_SECTORS,	\
 	  OPT_UINT(512, 1U << 16),					\
-	  BCH_SB_BLOCK_SIZE,		8,				\
+	  BCH_SB_BLOCK_SIZE,		4 << 10,			\
 	  "size",	NULL)						\
 	x(btree_node_size,		u32,				\
 	  OPT_FS|OPT_FORMAT|						\
 	  OPT_HUMAN_READABLE|OPT_MUST_BE_POW_2|OPT_SB_FIELD_SECTORS,	\
 	  OPT_UINT(512, 1U << 20),					\
-	  BCH_SB_BTREE_NODE_SIZE,	512,				\
+	  BCH_SB_BTREE_NODE_SIZE,	256 << 10,			\
 	  "size",	"Btree node size, default 256k")		\
 	x(errors,			u8,				\
 	  OPT_FS|OPT_FORMAT|OPT_MOUNT|OPT_RUNTIME,			\