summaryrefslogtreecommitdiffstats
path: root/libblkid/src/superblocks/btrfs.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-14 19:10:49 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-14 19:10:49 +0000
commitcfe5e3905201349e9cf3f95d52ff4bd100bde37d (patch)
treed0baf160cbee3195249d095f85e52d20c21acf02 /libblkid/src/superblocks/btrfs.c
parentInitial commit. (diff)
downloadutil-linux-cfe5e3905201349e9cf3f95d52ff4bd100bde37d.tar.xz
util-linux-cfe5e3905201349e9cf3f95d52ff4bd100bde37d.zip
Adding upstream version 2.39.3.upstream/2.39.3
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'libblkid/src/superblocks/btrfs.c')
-rw-r--r--libblkid/src/superblocks/btrfs.c326
1 files changed, 326 insertions, 0 deletions
diff --git a/libblkid/src/superblocks/btrfs.c b/libblkid/src/superblocks/btrfs.c
new file mode 100644
index 0000000..b9cf4bd
--- /dev/null
+++ b/libblkid/src/superblocks/btrfs.c
@@ -0,0 +1,326 @@
+/*
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <assert.h>
+#include <inttypes.h>
+
+#ifdef HAVE_LINUX_BLKZONED_H
+#include <linux/blkzoned.h>
+#endif
+
+#include "superblocks.h"
+#include "crc32c.h"
+#include "sha256.h"
+#include "xxhash.h"
+
+enum btrfs_super_block_csum_type {
+ BTRFS_SUPER_BLOCK_CSUM_TYPE_CRC32C = 0,
+ BTRFS_SUPER_BLOCK_CSUM_TYPE_XXHASH = 1,
+ BTRFS_SUPER_BLOCK_CSUM_TYPE_SHA256 = 2,
+};
+
+union btrfs_super_block_csum {
+ uint8_t bytes[32];
+ uint32_t crc32c;
+ XXH64_hash_t xxh64;
+ uint8_t sha256[UL_SHA256LENGTH];
+};
+
+struct btrfs_super_block {
+ union btrfs_super_block_csum csum;
+ uint8_t fsid[16];
+ uint64_t bytenr;
+ uint64_t flags;
+ uint8_t magic[8];
+ uint64_t generation;
+ uint64_t root;
+ uint64_t chunk_root;
+ uint64_t log_root;
+ uint64_t log_root_transid;
+ uint64_t total_bytes;
+ uint64_t bytes_used;
+ uint64_t root_dir_objectid;
+ uint64_t num_devices;
+ uint32_t sectorsize;
+ uint32_t nodesize;
+ uint32_t leafsize;
+ uint32_t stripesize;
+ uint32_t sys_chunk_array_size;
+ uint64_t chunk_root_generation;
+ uint64_t compat_flags;
+ uint64_t compat_ro_flags;
+ uint64_t incompat_flags;
+ uint16_t csum_type;
+ uint8_t root_level;
+ uint8_t chunk_root_level;
+ uint8_t log_root_level;
+ struct btrfs_dev_item {
+ uint64_t devid;
+ uint64_t total_bytes;
+ uint64_t bytes_used;
+ uint32_t io_align;
+ uint32_t io_width;
+ uint32_t sector_size;
+ uint64_t type;
+ uint64_t generation;
+ uint64_t start_offset;
+ uint32_t dev_group;
+ uint8_t seek_speed;
+ uint8_t bandwidth;
+ uint8_t uuid[16];
+ uint8_t fsid[16];
+ } __attribute__ ((__packed__)) dev_item;
+ uint8_t label[256];
+ uint8_t padding[3541]; /* pad to BTRFS_SUPER_INFO_SIZE for csum calculation */
+} __attribute__ ((__packed__));
+
+#define BTRFS_SUPER_INFO_SIZE 4096
+
+/* Number of superblock log zones */
+#define BTRFS_NR_SB_LOG_ZONES 2
+
+/* Introduce some macros and types to unify the code with kernel side */
+#define SECTOR_SHIFT 9
+
+typedef uint64_t sector_t;
+
+#ifdef HAVE_LINUX_BLKZONED_H
+static int sb_write_pointer(blkid_probe pr, struct blk_zone *zones, uint64_t *wp_ret)
+{
+ bool empty[BTRFS_NR_SB_LOG_ZONES];
+ bool full[BTRFS_NR_SB_LOG_ZONES];
+ sector_t sector;
+
+ assert(zones[0].type != BLK_ZONE_TYPE_CONVENTIONAL &&
+ zones[1].type != BLK_ZONE_TYPE_CONVENTIONAL);
+
+ empty[0] = zones[0].cond == BLK_ZONE_COND_EMPTY;
+ empty[1] = zones[1].cond == BLK_ZONE_COND_EMPTY;
+ full[0] = zones[0].cond == BLK_ZONE_COND_FULL;
+ full[1] = zones[1].cond == BLK_ZONE_COND_FULL;
+
+ /*
+ * Possible states of log buffer zones
+ *
+ * Empty[0] In use[0] Full[0]
+ * Empty[1] * x 0
+ * In use[1] 0 x 0
+ * Full[1] 1 1 C
+ *
+ * Log position:
+ * *: Special case, no superblock is written
+ * 0: Use write pointer of zones[0]
+ * 1: Use write pointer of zones[1]
+ * C: Compare super blocks from zones[0] and zones[1], use the latest
+ * one determined by generation
+ * x: Invalid state
+ */
+
+ if (empty[0] && empty[1]) {
+ /* Special case to distinguish no superblock to read */
+ *wp_ret = zones[0].start << SECTOR_SHIFT;
+ return -ENOENT;
+ } else if (full[0] && full[1]) {
+ /* Compare two super blocks */
+ struct btrfs_super_block *super[BTRFS_NR_SB_LOG_ZONES];
+ int i;
+
+ for (i = 0; i < BTRFS_NR_SB_LOG_ZONES; i++) {
+ uint64_t bytenr;
+
+ bytenr = ((zones[i].start + zones[i].len)
+ << SECTOR_SHIFT) - BTRFS_SUPER_INFO_SIZE;
+
+ super[i] = (struct btrfs_super_block *)
+ blkid_probe_get_buffer(pr, bytenr, BTRFS_SUPER_INFO_SIZE);
+ if (!super[i])
+ return -EIO;
+ DBG(LOWPROBE, ul_debug("(btrfs) checking #%d zone "
+ "[start=%" PRIu64", len=%" PRIu64", sb-offset=%" PRIu64"]",
+ i, (uint64_t) zones[i].start,
+ (uint64_t) zones[i].len, bytenr));
+ }
+
+ if (super[0]->generation > super[1]->generation)
+ sector = zones[1].start;
+ else
+ sector = zones[0].start;
+ } else if (!full[0] && (empty[1] || full[1])) {
+ sector = zones[0].wp;
+ } else if (full[0]) {
+ sector = zones[1].wp;
+ } else {
+ return -EUCLEAN;
+ }
+ *wp_ret = sector << SECTOR_SHIFT;
+
+ DBG(LOWPROBE, ul_debug("(btrfs) write pointer: %" PRIu64" sector", sector));
+ return 0;
+}
+
+static int sb_log_offset(blkid_probe pr, uint64_t *bytenr_ret)
+{
+ uint32_t zone_num = 0;
+ uint32_t zone_size_sector;
+ struct blk_zone_report *rep;
+ struct blk_zone *zones;
+ int ret;
+ int i;
+ uint64_t wp;
+
+
+ zone_size_sector = pr->zone_size >> SECTOR_SHIFT;
+ rep = blkdev_get_zonereport(pr->fd, zone_num * zone_size_sector, 2);
+ if (!rep) {
+ ret = -errno;
+ goto out;
+ }
+ zones = (struct blk_zone *)(rep + 1);
+
+ /*
+ * Use the head of the first conventional zone, if the zones
+ * contain one.
+ */
+ for (i = 0; i < BTRFS_NR_SB_LOG_ZONES; i++) {
+ if (zones[i].type == BLK_ZONE_TYPE_CONVENTIONAL) {
+ DBG(LOWPROBE, ul_debug("(btrfs) checking conventional zone"));
+ *bytenr_ret = zones[i].start << SECTOR_SHIFT;
+ ret = 0;
+ goto out;
+ }
+ }
+
+ ret = sb_write_pointer(pr, zones, &wp);
+ if (ret != -ENOENT && ret) {
+ ret = 1;
+ goto out;
+ }
+ if (ret != -ENOENT) {
+ if (wp == zones[0].start << SECTOR_SHIFT)
+ wp = (zones[1].start + zones[1].len) << SECTOR_SHIFT;
+ wp -= BTRFS_SUPER_INFO_SIZE;
+ }
+ *bytenr_ret = wp;
+
+ ret = 0;
+out:
+ free(rep);
+
+ return ret;
+}
+#endif
+
+static int btrfs_verify_csum(blkid_probe pr, const struct btrfs_super_block *bfs)
+{
+ uint16_t csum_type = le16_to_cpu(bfs->csum_type);
+ const void *csum_data = (char *) bfs + sizeof(bfs->csum);
+ size_t csum_data_size = sizeof(*bfs) - sizeof(bfs->csum);
+ switch (csum_type) {
+ case BTRFS_SUPER_BLOCK_CSUM_TYPE_CRC32C: {
+ uint32_t crc = ~crc32c(~0L, csum_data, csum_data_size);
+ return blkid_probe_verify_csum(pr, crc,
+ le32_to_cpu(bfs->csum.crc32c));
+ }
+ case BTRFS_SUPER_BLOCK_CSUM_TYPE_XXHASH: {
+ XXH64_hash_t xxh64 = XXH64(csum_data, csum_data_size, 0);
+ return blkid_probe_verify_csum(pr, xxh64,
+ le64_to_cpu(bfs->csum.xxh64));
+ }
+ case BTRFS_SUPER_BLOCK_CSUM_TYPE_SHA256: {
+ uint8_t sha256[UL_SHA256LENGTH];
+ ul_SHA256(sha256, csum_data, csum_data_size);
+ return blkid_probe_verify_csum_buf(pr, UL_SHA256LENGTH,
+ sha256, bfs->csum.sha256);
+ }
+ default:
+ DBG(LOWPROBE, ul_debug("(btrfs) unknown checksum type %d, skipping validation",
+ csum_type));
+ return 1;
+ }
+}
+
+static int probe_btrfs(blkid_probe pr, const struct blkid_idmag *mag)
+{
+ struct btrfs_super_block *bfs;
+
+ if (pr->zone_size) {
+#ifdef HAVE_LINUX_BLKZONED_H
+ uint64_t offset = 0;
+ int ret;
+
+ ret = sb_log_offset(pr, &offset);
+ if (ret)
+ return ret;
+ bfs = (struct btrfs_super_block *)
+ blkid_probe_get_buffer(pr, offset,
+ sizeof(struct btrfs_super_block));
+#else
+ /* Nothing can be done */
+ return 1;
+#endif
+ } else {
+ bfs = blkid_probe_get_sb(pr, mag, struct btrfs_super_block);
+ }
+ if (!bfs)
+ return errno ? -errno : 1;
+
+ if (!btrfs_verify_csum(pr, bfs))
+ return 1;
+
+ /* Invalid sector size; total_bytes would be bogus. */
+ if (!le32_to_cpu(bfs->sectorsize))
+ return 1;
+
+ if (*bfs->label)
+ blkid_probe_set_label(pr,
+ (unsigned char *) bfs->label,
+ sizeof(bfs->label));
+
+ blkid_probe_set_uuid(pr, bfs->fsid);
+ blkid_probe_set_uuid_as(pr, bfs->dev_item.uuid, "UUID_SUB");
+ blkid_probe_set_fsblocksize(pr, le32_to_cpu(bfs->sectorsize));
+ blkid_probe_set_block_size(pr, le32_to_cpu(bfs->sectorsize));
+
+ uint32_t sectorsize_log = 31 -
+ __builtin_clz(le32_to_cpu(bfs->sectorsize));
+ blkid_probe_set_fslastblock(pr,
+ le64_to_cpu(bfs->total_bytes) >> sectorsize_log);
+
+ /* The size is calculated without the RAID factor. It could not be
+ * obtained from the superblock as it is property of device tree.
+ * Without the factor we would show fs size with the redundant data. The
+ * acquisition of the factor will require additional parsing of btrfs
+ * tree.
+ */
+ blkid_probe_set_fssize(pr, le64_to_cpu(bfs->total_bytes));
+
+ return 0;
+}
+
+const struct blkid_idinfo btrfs_idinfo =
+{
+ .name = "btrfs",
+ .usage = BLKID_USAGE_FILESYSTEM,
+ .probefunc = probe_btrfs,
+ .minsz = 1024 * 1024,
+ .magics =
+ {
+ { .magic = "_BHRfS_M", .len = 8, .sboff = 0x40, .kboff = 64 },
+ /* For zoned btrfs */
+ { .magic = "_BHRfS_M", .len = 8, .sboff = 0x40,
+ .is_zoned = 1, .zonenum = 0, .kboff_inzone = 0 },
+ { .magic = "_BHRfS_M", .len = 8, .sboff = 0x40,
+ .is_zoned = 1, .zonenum = 1, .kboff_inzone = 0 },
+ { NULL }
+ }
+};
+