summaryrefslogtreecommitdiffstats
path: root/libblkid/src/superblocks/zfs.c
diff options
context:
space:
mode:
Diffstat (limited to 'libblkid/src/superblocks/zfs.c')
-rw-r--r--libblkid/src/superblocks/zfs.c328
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
+};