[PATCH 2/2] btrfs-progs: check: detect leaked active zones on zoned devices

Johannes Thumshirn <[email protected]>
Newsgroups org.kernel.vger.linux-btrfs
Message-ID <[email protected]>
On a zoned filesystem a sequential zone can end up open (and written) on
the device while it is not referenced by any dev extent and is not in a
sb-log zone. Such a zone is unreferenced - its space is free according to
the device tree - yet it stays active on the device, permanently consuming
one of the device's limited active zones. On a device with few active
zones this can lead to premature ENOSPC and/or allocation hangs.

One way to end up in this state is a mkfs that failed to reset a
temporary chunk's zone before finishing (fixed in a preceding commit).
Existing filesystems created by such a mkfs need a way to detect and
repair the condition.

Add a zoned device specific check that cross-checks the on-disk metadata
against the physical zone state of the device. It flags any written,
sequential zone that is not backed by a dev extent, and in
--repair mode resets it. The check is a no-op on non-zoned filesystems and
runs as part of the extent/device checking rather than as a separate
numbered check phase.

Introduce check_zoned() as a entry point for future zoned device checks.

Signed-off-by: Johannes Thumshirn <[email protected]>
---
 check/main.c | 154 ++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 153 insertions(+), 1 deletion(-)

diff --git a/check/main.c b/check/main.c
index a60b9b0b..37de9a36 100644
--- a/check/main.c
+++ b/check/main.c
@@ -47,6 +47,7 @@
 #include "kernel-shared/ulist.h"
 #include "kernel-shared/file-item.h"
 #include "kernel-shared/tree-checker.h"
+#include "kernel-shared/zoned.h"
 #include "common/defs.h"
 #include "common/extent-cache.h"
 #include "common/internal.h"
@@ -9124,6 +9125,150 @@ out:
 	return ret;
 }
 
+/*
+ * On a zoned filesystem, find sequential zones that are open/written on the
+ * device but are not backed by any dev extent and are not superblock log
+ * zones. Such a zone is unreferenced yet it stays 'active' on the device,
+ * permanently consuming one of the device's limited active zones. This can
+ * cause premature ENOSPC or, on filesystems with a small number of active
+ * zones, allocation hangs.
+ */
+static int check_zoned_active_zones(void)
+{
+	struct btrfs_root *dev_root = gfs_info->dev_root;
+	struct btrfs_device *dev;
+	int ret = 0;
+
+	list_for_each_entry(dev, &gfs_info->fs_devices->devices, dev_list) {
+		struct btrfs_zoned_device_info *zinfo = dev->zone_info;
+		struct btrfs_path path = { 0 };
+		struct btrfs_key key;
+		unsigned long *covered;
+		int shift;
+		int i;
+
+		if (!zinfo || zinfo->model == ZONED_NONE)
+			continue;
+
+		shift = ilog2(zinfo->zone_size);
+		covered = calloc(BITS_TO_LONGS(zinfo->nr_zones),
+				 sizeof(unsigned long));
+		if (!covered)
+			return -ENOMEM;
+
+		/* Superblock log zones are written but not backed by a chunk. */
+		for (int mirror = 0; mirror < BTRFS_SUPER_MIRROR_MAX; mirror++) {
+			u32 sb_zno = sb_zone_number(shift, mirror);
+
+			if (sb_zno + BTRFS_NR_SB_LOG_ZONES > zinfo->nr_zones)
+				continue;
+			for (i = 0; i < BTRFS_NR_SB_LOG_ZONES; i++)
+				set_bit(sb_zno + i, covered);
+		}
+
+		key.objectid = dev->devid;
+		key.type = BTRFS_DEV_EXTENT_KEY;
+		key.offset = 0;
+		ret = btrfs_search_slot(NULL, dev_root, &key, &path, 0, 0);
+		if (ret < 0) {
+			btrfs_release_path(&path);
+			free(covered);
+			return ret;
+		}
+
+		while (1) {
+			struct btrfs_dev_extent *devext;
+			u64 pstart, plen, zno;
+
+			if (path.slots[0] >= btrfs_header_nritems(path.nodes[0])) {
+				ret = btrfs_next_leaf(dev_root, &path);
+				if (ret < 0) {
+					btrfs_release_path(&path);
+					free(covered);
+					return ret;
+				}
+				if (ret > 0)
+					break;
+			}
+
+			btrfs_item_key_to_cpu(path.nodes[0], &key, path.slots[0]);
+			if (key.objectid != dev->devid ||
+			    key.type != BTRFS_DEV_EXTENT_KEY)
+				break;
+
+			devext = btrfs_item_ptr(path.nodes[0], path.slots[0],
+					    struct btrfs_dev_extent);
+			pstart = key.offset;
+			plen = btrfs_dev_extent_length(path.nodes[0], devext);
+			for (zno = pstart >> shift;
+			     zno <= (pstart + plen - 1) >> shift; zno++) {
+				if (zno < zinfo->nr_zones)
+					set_bit(zno, covered);
+			}
+			path.slots[0]++;
+		}
+		btrfs_release_path(&path);
+
+		for (i = 0; i < zinfo->nr_zones; i++) {
+			struct blk_zone *zone = &zinfo->zones[i];
+
+			if (zone->type == BLK_ZONE_TYPE_CONVENTIONAL)
+				continue;
+			if (zone->cond == BLK_ZONE_COND_EMPTY)
+				continue;
+			if (test_bit(i, covered))
+				continue;
+
+			ret = 1;
+			error(
+"zoned: devid %llu zone %u is open/written but not referenced by any block group",
+			      dev->devid, i);
+			if (opt_check_repair) {
+				ret = btrfs_reset_dev_zone(dev->fd, zone);
+				if (ret) {
+					error(
+"zoned: failed to reset leaked zone %u on devid %llu: %m",
+					      i, dev->devid);
+					free(covered);
+					return ret;
+				}
+				printf("Reset leaked active zone %u on devid %llu\n",
+				       i, dev->devid);
+			}
+		}
+		free(covered);
+	}
+
+	return ret;
+}
+
+/*
+ * Zoned device consistency checks.
+ *
+ * These cross-check the on-disk metadata against the physical zone state of
+ * the underlying zoned device(s), as opposed to the tree-internal consistency
+ * checks done elsewhere. They are a no-op on non-zoned filesystems and are run
+ * as part of the device checking, not as a separate numbered check phase.
+ *
+ * Return 0 if clean, 1 if a problem was found (and not repaired), or a
+ * negative errno on a fatal error.
+ */
+static int check_zoned(void)
+{
+	int err = 0;
+	int ret;
+
+	if (!btrfs_is_zoned(gfs_info))
+		return 0;
+
+	ret = check_zoned_active_zones();
+	if (ret < 0)
+		return ret;
+	err |= ret;
+
+	return err;
+}
+
 /*
  * Check if all dev extents are valid (not overlapping nor beyond device
  * boundary).
@@ -9409,7 +9554,14 @@ static int do_check_chunks_and_extents(void)
 		return ret;
 
 	ret = check_and_repair_super_num_devs(gfs_info);
-	return ret;
+	if (ret)
+		return ret;
+
+	/*
+	 * Zoned device consistency checks. Not a separate numbered phase, run
+	 * as part of the extent/device checking. No-op on non-zoned.
+	 */
+	return check_zoned();
 }
 
 static struct extent_buffer *btrfs_fsck_clear_root(
-- 
2.55.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.