diff mbox series

[2/3] btrfs: introduce "abort=super" mount option

Message ID 65c603a8c60f997a8b23b86a275d46ee39636284.1695350405.git.wqu@suse.com (mailing list archive)
State New, archived
Headers show
Series btrfs: introduce "abort=" groups for more strict error handling | expand

Commit Message

Qu Wenruo Sept. 22, 2023, 2:55 a.m. UTC
Currently btrfs would only report error when the primary super block
writeback failed.
If the backup ones failed to be written, we still continue and just
output a warning.

This is fine but sometimes such warning is an early indication of bigger
problems.
For developers and other critical missions, we may want the filesystem
to be more noisy by abort the current transaction for those situations.

This patch would introduce the mount option group "abort=", and
introduce the first sub option: "super".
This new option would make btrfs to abort and mark the fs read-only if
any super block failed to be written back.

This is different from the existing code by:

- We do not ignore backup super blocks write failure

- We do not accept any super writeback failure for any device
  Currently we allow "num_devices - 1" devices to have super block
  writeback failure.

Signed-off-by: Qu Wenruo <wqu@suse.com>
---
 fs/btrfs/disk-io.c | 12 +++++++
 fs/btrfs/fs.h      |  1 +
 fs/btrfs/super.c   | 84 +++++++++++++++++++++++++++++++++++++++++++---
 3 files changed, 92 insertions(+), 5 deletions(-)
diff mbox series

Patch

diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c
index dc577b3c53f6..5a85b517a031 100644
--- a/fs/btrfs/disk-io.c
+++ b/fs/btrfs/disk-io.c
@@ -3758,6 +3758,8 @@  static int write_dev_supers(struct btrfs_device *device,
 
 	if (max_mirrors == 0)
 		max_mirrors = BTRFS_SUPER_MIRROR_MAX;
+	if (btrfs_test_opt(fs_info, ABORT_SUPER))
+		max_mirrors = 1;
 
 	shash->tfm = fs_info->csum_shash;
 
@@ -3849,6 +3851,8 @@  static int wait_dev_supers(struct btrfs_device *device, int max_mirrors)
 
 	if (max_mirrors == 0)
 		max_mirrors = BTRFS_SUPER_MIRROR_MAX;
+	if (btrfs_test_opt(device->fs_info, ABORT_SUPER))
+		max_mirrors = 1;
 
 	for (i = 0; i < max_mirrors; i++) {
 		struct page *page;
@@ -3895,6 +3899,12 @@  static int wait_dev_supers(struct btrfs_device *device, int max_mirrors)
 			  device->devid);
 		return -1;
 	}
+	if (errors >= i) {
+		btrfs_err(device->fs_info,
+			  "error writing super blocks to device %llu",
+			  device->devid);
+		return -1;
+	}
 
 	return errors < i ? 0 : -1;
 }
@@ -4058,6 +4068,8 @@  int write_all_supers(struct btrfs_fs_info *fs_info, int max_mirrors)
 	mutex_lock(&fs_info->fs_devices->device_list_mutex);
 	head = &fs_info->fs_devices->devices;
 	max_errors = btrfs_super_num_devices(fs_info->super_copy) - 1;
+	if (btrfs_test_opt(fs_info, ABORT_SUPER))
+		max_errors = 0;
 
 	if (do_barriers) {
 		ret = barrier_all_devices(fs_info);
diff --git a/fs/btrfs/fs.h b/fs/btrfs/fs.h
index cec28d6b20bc..4fc0afcf1ef2 100644
--- a/fs/btrfs/fs.h
+++ b/fs/btrfs/fs.h
@@ -188,6 +188,7 @@  enum {
 	BTRFS_MOUNT_IGNOREBADROOTS		= (1ULL << 27),
 	BTRFS_MOUNT_IGNOREDATACSUMS		= (1ULL << 28),
 	BTRFS_MOUNT_NODISCARD			= (1ULL << 29),
+	BTRFS_MOUNT_ABORT_SUPER			= (1ULL << 30),
 };
 
 /*
diff --git a/fs/btrfs/super.c b/fs/btrfs/super.c
index 5c6054f0552b..41ab8c6e3fab 100644
--- a/fs/btrfs/super.c
+++ b/fs/btrfs/super.c
@@ -125,6 +125,11 @@  enum {
 	Opt_ignoredatacsums,
 	Opt_rescue_all,
 
+	/* Extra abort options. */
+	Opt_abort,
+	Opt_abort_super,
+	Opt_abort_all,
+
 	/* Deprecated options */
 	Opt_recovery,
 	Opt_inode_cache, Opt_noinode_cache,
@@ -187,8 +192,12 @@  static const match_table_t tokens = {
 	{Opt_notreelog, "notreelog"},
 	{Opt_user_subvol_rm_allowed, "user_subvol_rm_allowed"},
 
+	/* Abort options */
+	{Opt_abort, "abort=%s"},
+
 	/* Rescue options */
 	{Opt_rescue, "rescue=%s"},
+
 	/* Deprecated, with alias rescue=nologreplay */
 	{Opt_nologreplay, "nologreplay"},
 	/* Deprecated, with alias rescue=usebackuproot */
@@ -222,6 +231,12 @@  static const match_table_t rescue_tokens = {
 	{Opt_err, NULL},
 };
 
+static const match_table_t abort_tokens = {
+	{Opt_abort_super, "super"},
+	{Opt_abort_all, "all"},
+	{Opt_err, NULL},
+};
+
 static bool check_ro_option(struct btrfs_fs_info *fs_info, unsigned long opt,
 			    const char *opt_name)
 {
@@ -233,6 +248,48 @@  static bool check_ro_option(struct btrfs_fs_info *fs_info, unsigned long opt,
 	return false;
 }
 
+static int parse_abort_options(struct btrfs_fs_info *info, const char *options)
+{
+	char *opts;
+	char *orig;
+	char *p;
+	substring_t args[MAX_OPT_ARGS];
+	int ret = 0;
+
+	opts = kstrdup(options, GFP_KERNEL);
+	if (!opts)
+		return -ENOMEM;
+	orig = opts;
+
+	while ((p = strsep(&opts, ":")) != NULL) {
+		int token;
+
+		if (!*p)
+			continue;
+		token = match_token(p, abort_tokens, args);
+		switch (token) {
+		case Opt_abort_super:
+			btrfs_set_and_info(info, ABORT_SUPER,
+				"will abort if any super block write back failed");
+			break;
+		case Opt_abort_all:
+			btrfs_info(info, "enabling all abort options");
+			btrfs_set_and_info(info, ABORT_SUPER,
+				"will abort if any super block write back failed");
+			break;
+		case Opt_err:
+			btrfs_info(info, "unrecognized abort option '%s'", p);
+			ret = -EINVAL;
+			goto out;
+		default:
+			break;
+		}
+	}
+out:
+	kfree(orig);
+	return ret;
+}
+
 static int parse_rescue_options(struct btrfs_fs_info *info, const char *options)
 {
 	char *opts;
@@ -736,6 +793,14 @@  int btrfs_parse_options(struct btrfs_fs_info *info, char *options,
 			}
 			info->commit_interval = intarg;
 			break;
+		case Opt_abort:
+			ret = parse_abort_options(info, args[0].from);
+			if (ret < 0) {
+				btrfs_err(info, "unrecognized abort value %s",
+					  args[0].from);
+				goto out;
+			}
+			break;
 		case Opt_rescue:
 			ret = parse_rescue_options(info, args[0].from);
 			if (ret < 0) {
@@ -1191,12 +1256,19 @@  static void print_rescue_option(struct seq_file *seq, const char *s, bool *print
 	*printed = true;
 }
 
+static void print_abort_option(struct seq_file *seq, const char *s, bool *printed)
+{
+	seq_printf(seq, "%s%s", (*printed) ? ":" : ",abort=", s);
+	*printed = true;
+}
+
 static int btrfs_show_options(struct seq_file *seq, struct dentry *dentry)
 {
 	struct btrfs_fs_info *info = btrfs_sb(dentry->d_sb);
 	const char *compress_type;
 	const char *subvol_name;
-	bool printed = false;
+	bool rescue_printed = false;
+	bool abort_printed = false;
 
 	if (btrfs_test_opt(info, DEGRADED))
 		seq_puts(seq, ",degraded");
@@ -1229,13 +1301,15 @@  static int btrfs_show_options(struct seq_file *seq, struct dentry *dentry)
 	if (btrfs_test_opt(info, NOTREELOG))
 		seq_puts(seq, ",notreelog");
 	if (btrfs_test_opt(info, NOLOGREPLAY))
-		print_rescue_option(seq, "nologreplay", &printed);
+		print_rescue_option(seq, "nologreplay", &rescue_printed);
 	if (btrfs_test_opt(info, USEBACKUPROOT))
-		print_rescue_option(seq, "usebackuproot", &printed);
+		print_rescue_option(seq, "usebackuproot", &rescue_printed);
 	if (btrfs_test_opt(info, IGNOREBADROOTS))
-		print_rescue_option(seq, "ignorebadroots", &printed);
+		print_rescue_option(seq, "ignorebadroots", &rescue_printed);
 	if (btrfs_test_opt(info, IGNOREDATACSUMS))
-		print_rescue_option(seq, "ignoredatacsums", &printed);
+		print_rescue_option(seq, "ignoredatacsums", &rescue_printed);
+	if (btrfs_test_opt(info, ABORT_SUPER))
+		print_abort_option(seq, "super", &abort_printed);
 	if (btrfs_test_opt(info, FLUSHONCOMMIT))
 		seq_puts(seq, ",flushoncommit");
 	if (btrfs_test_opt(info, DISCARD_SYNC))