diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 14:30:35 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 14:30:35 +0000 |
commit | 378c18e5f024ac5a8aef4cb40d7c9aa9633d144c (patch) | |
tree | 44dfb6ca500d32cabd450649b322a42e70a30683 /libblkid/src/superblocks/zfs.c | |
parent | Initial commit. (diff) | |
download | util-linux-378c18e5f024ac5a8aef4cb40d7c9aa9633d144c.tar.xz util-linux-378c18e5f024ac5a8aef4cb40d7c9aa9633d144c.zip |
Adding upstream version 2.38.1.upstream/2.38.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | libblkid/src/superblocks/zfs.c | 328 |
1 files changed, 328 insertions, 0 deletions
diff --git a/libblkid/src/superblocks/zfs.c b/libblkid/src/superblocks/zfs.c new file mode 100644 index 0000000..774a199 --- /dev/null +++ b/libblkid/src/superblocks/zfs.c @@ -0,0 +1,328 @@ +/* + * Copyright (C) 2009-2010 by Andreas Dilger <adilger@sun.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 <errno.h> +#include <ctype.h> +#include <inttypes.h> +#include <limits.h> + +#include "superblocks.h" + +#define VDEV_LABEL_UBERBLOCK (128 * 1024ULL) +#define VDEV_LABEL_NVPAIR ( 16 * 1024ULL) +#define VDEV_LABEL_SIZE (256 * 1024ULL) +#define UBERBLOCK_SIZE 1024ULL +#define UBERBLOCKS_COUNT 128 + +/* #include <sys/uberblock_impl.h> */ +#define UBERBLOCK_MAGIC 0x00bab10c /* oo-ba-bloc! */ +struct zfs_uberblock { + uint64_t ub_magic; /* UBERBLOCK_MAGIC */ + uint64_t ub_version; /* SPA_VERSION */ + uint64_t ub_txg; /* txg of last sync */ + uint64_t ub_guid_sum; /* sum of all vdev guids */ + uint64_t ub_timestamp; /* UTC time of last sync */ + char ub_rootbp; /* MOS objset_phys_t */ +} __attribute__((packed)); + +#define ZFS_WANT 4 + +#define DATA_TYPE_UINT64 8 +#define DATA_TYPE_STRING 9 +#define DATA_TYPE_DIRECTORY 19 + +struct nvpair { + uint32_t nvp_size; + uint32_t nvp_unkown; + uint32_t nvp_namelen; + char nvp_name[0]; /* aligned to 4 bytes */ + /* aligned ptr array for string arrays */ + /* aligned array of data for value */ +}; + +struct nvstring { + uint32_t nvs_type; + uint32_t nvs_elem; + uint32_t nvs_strlen; + unsigned char nvs_string[0]; +}; + +struct nvuint64 { + uint32_t nvu_type; + uint32_t nvu_elem; + uint64_t nvu_value; +} __attribute__((packed)); + +struct nvdirectory { + uint32_t nvd_type; + uint32_t nvd_unknown[3]; +}; + +struct nvlist { + uint32_t nvl_unknown[3]; + struct nvpair nvl_nvpair; +}; + +static void zfs_process_value(blkid_probe pr, char *name, size_t namelen, + void *value, size_t max_value_size, unsigned directory_level) +{ + if (strncmp(name, "name", namelen) == 0 && + sizeof(struct nvstring) <= max_value_size && + !directory_level) { + struct nvstring *nvs = value; + uint32_t nvs_type = be32_to_cpu(nvs->nvs_type); + uint32_t nvs_strlen = be32_to_cpu(nvs->nvs_strlen); + + if (nvs_type != DATA_TYPE_STRING || + (uint64_t)nvs_strlen + sizeof(*nvs) > max_value_size) + return; + + DBG(LOWPROBE, ul_debug("nvstring: type %u string %*s\n", + nvs_type, nvs_strlen, nvs->nvs_string)); + + blkid_probe_set_label(pr, nvs->nvs_string, nvs_strlen); + } else if (strncmp(name, "guid", namelen) == 0 && + sizeof(struct nvuint64) <= max_value_size && + !directory_level) { + struct nvuint64 *nvu = value; + uint32_t nvu_type = be32_to_cpu(nvu->nvu_type); + uint64_t nvu_value; + + memcpy(&nvu_value, &nvu->nvu_value, sizeof(nvu_value)); + nvu_value = be64_to_cpu(nvu_value); + + if (nvu_type != DATA_TYPE_UINT64) + return; + + DBG(LOWPROBE, ul_debug("nvuint64: type %u value %"PRIu64"\n", + nvu_type, nvu_value)); + + blkid_probe_sprintf_value(pr, "UUID_SUB", + "%"PRIu64, nvu_value); + } else if (strncmp(name, "pool_guid", namelen) == 0 && + sizeof(struct nvuint64) <= max_value_size && + !directory_level) { + struct nvuint64 *nvu = value; + uint32_t nvu_type = be32_to_cpu(nvu->nvu_type); + uint64_t nvu_value; + + memcpy(&nvu_value, &nvu->nvu_value, sizeof(nvu_value)); + nvu_value = be64_to_cpu(nvu_value); + + if (nvu_type != DATA_TYPE_UINT64) + return; + + DBG(LOWPROBE, ul_debug("nvuint64: type %u value %"PRIu64"\n", + nvu_type, nvu_value)); + + blkid_probe_sprintf_uuid(pr, (unsigned char *) &nvu_value, + sizeof(nvu_value), + "%"PRIu64, nvu_value); + } else if (strncmp(name, "ashift", namelen) == 0 && + sizeof(struct nvuint64) <= max_value_size) { + struct nvuint64 *nvu = value; + uint32_t nvu_type = be32_to_cpu(nvu->nvu_type); + uint64_t nvu_value; + + memcpy(&nvu_value, &nvu->nvu_value, sizeof(nvu_value)); + nvu_value = be64_to_cpu(nvu_value); + + if (nvu_type != DATA_TYPE_UINT64) + return; + + if (nvu_value < 32) + blkid_probe_set_block_size(pr, 1U << nvu_value); + } +} + +static void zfs_extract_guid_name(blkid_probe pr, loff_t offset) +{ + unsigned char *p; + struct nvlist *nvl; + struct nvpair *nvp; + size_t left = 4096; + unsigned directory_level = 0; + + offset = (offset & ~(VDEV_LABEL_SIZE - 1)) + VDEV_LABEL_NVPAIR; + + /* Note that we currently assume that the desired fields are within + * the first 4k (left) of the nvlist. This is true for all pools + * I've seen, and simplifies this code somewhat, because we don't + * have to handle an nvpair crossing a buffer boundary. */ + p = blkid_probe_get_buffer(pr, offset, left); + if (!p) + return; + + DBG(LOWPROBE, ul_debug("zfs_extract: nvlist offset %jd\n", + (intmax_t)offset)); + + nvl = (struct nvlist *) p; + nvp = &nvl->nvl_nvpair; + left -= (unsigned char *)nvp - p; /* Already used up 12 bytes */ + + while (left > sizeof(*nvp)) { + uint32_t nvp_size = be32_to_cpu(nvp->nvp_size); + uint32_t nvp_namelen = be32_to_cpu(nvp->nvp_namelen); + uint64_t namesize = ((uint64_t)nvp_namelen + 3) & ~3; + size_t max_value_size; + void *value; + + if (!nvp->nvp_size) { + if (!directory_level) + break; + directory_level--; + nvp_size = 8; + goto cont; + } + + DBG(LOWPROBE, ul_debug("left %zd nvp_size %u\n", + left, nvp_size)); + + /* nvpair fits in buffer and name fits in nvpair? */ + if (nvp_size > left || namesize + sizeof(*nvp) > nvp_size) + break; + + DBG(LOWPROBE, + ul_debug("nvlist: size %u, namelen %u, name %*s\n", + nvp_size, nvp_namelen, nvp_namelen, + nvp->nvp_name)); + + max_value_size = nvp_size - (namesize + sizeof(*nvp)); + value = nvp->nvp_name + namesize; + + if (sizeof(struct nvdirectory) <= max_value_size) { + struct nvdirectory *nvu = value; + if (be32_to_cpu(nvu->nvd_type) == DATA_TYPE_DIRECTORY) { + nvp_size = sizeof(*nvp) + namesize + sizeof(*nvu); + directory_level++; + goto cont; + } + } + + zfs_process_value(pr, nvp->nvp_name, nvp_namelen, + value, max_value_size, directory_level); + +cont: + if (nvp_size > left) + break; + left -= nvp_size; + + nvp = (struct nvpair *)((char *)nvp + nvp_size); + } +} + +static int find_uberblocks(const void *label, loff_t *ub_offset, int *swap_endian) +{ + uint64_t swab_magic = swab64((uint64_t)UBERBLOCK_MAGIC); + const struct zfs_uberblock *ub; + int i, found = 0; + loff_t offset = VDEV_LABEL_UBERBLOCK; + + for (i = 0; i < UBERBLOCKS_COUNT; i++, offset += UBERBLOCK_SIZE) { + ub = (const struct zfs_uberblock *)((const char *) label + offset); + + if (ub->ub_magic == UBERBLOCK_MAGIC) { + *ub_offset = offset; + *swap_endian = 0; + found++; + DBG(LOWPROBE, ul_debug("probe_zfs: found little-endian uberblock at %jd\n", (intmax_t)offset >> 10)); + } + + if (ub->ub_magic == swab_magic) { + *ub_offset = offset; + *swap_endian = 1; + found++; + DBG(LOWPROBE, ul_debug("probe_zfs: found big-endian uberblock at %jd\n", (intmax_t)offset >> 10)); + } + } + + return found; +} + +/* ZFS has 128x1kB host-endian root blocks, stored in 2 areas at the start + * of the disk, and 2 areas at the end of the disk. Check only some of them... + * #4 (@ 132kB) is the first one written on a new filesystem. */ +static int probe_zfs(blkid_probe pr, + const struct blkid_idmag *mag __attribute__((__unused__))) +{ + int swab_endian = 0; + struct zfs_uberblock *ub = NULL; + loff_t offset = 0, ub_offset = 0; + int label_no, found = 0, found_in_label; + void *label; + loff_t blk_align = (pr->size % (256 * 1024ULL)); + + DBG(PROBE, ul_debug("probe_zfs\n")); + /* Look for at least 4 uberblocks to ensure a positive match */ + for (label_no = 0; label_no < 4; label_no++) { + switch(label_no) { + case 0: // jump to L0 + offset = 0; + break; + case 1: // jump to L1 + offset = VDEV_LABEL_SIZE; + break; + case 2: // jump to L2 + offset = pr->size - 2 * VDEV_LABEL_SIZE - blk_align; + break; + case 3: // jump to L3 + offset = pr->size - VDEV_LABEL_SIZE - blk_align; + break; + } + + if ((S_ISREG(pr->mode) || blkid_probe_is_wholedisk(pr)) && + blkid_probe_is_covered_by_pt(pr, offset, VDEV_LABEL_SIZE)) + /* ignore this area, it's within any partition and + * we are working with whole-disk now */ + continue; + + label = blkid_probe_get_buffer(pr, offset, VDEV_LABEL_SIZE); + if (label == NULL) + return errno ? -errno : 1; + + found_in_label = find_uberblocks(label, &ub_offset, &swab_endian); + + if (found_in_label > 0) { + found+= found_in_label; + ub = (struct zfs_uberblock *)((char *) label + ub_offset); + ub_offset += offset; + + if (found >= ZFS_WANT) + break; + } + } + + if (found < ZFS_WANT) + return 1; + + /* If we found the 4th uberblock, then we will have exited from the + * scanning loop immediately, and ub will be a valid uberblock. */ + blkid_probe_sprintf_version(pr, "%" PRIu64, swab_endian ? + swab64(ub->ub_version) : ub->ub_version); + + zfs_extract_guid_name(pr, offset); + + if (blkid_probe_set_magic(pr, ub_offset, + sizeof(ub->ub_magic), + (unsigned char *) &ub->ub_magic)) + return 1; + + return 0; +} + +const struct blkid_idinfo zfs_idinfo = +{ + .name = "zfs_member", + .usage = BLKID_USAGE_FILESYSTEM, + .probefunc = probe_zfs, + .minsz = 64 * 1024 * 1024, + .magics = BLKID_NONE_MAGIC +}; |