blob: 1292b303be8520cbba4341c70cd0ce7e79a1c729 [file] [log] [blame]
/*
* 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
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;
};
struct nvlist {
uint32_t nvl_unknown[3];
struct nvpair nvl_nvpair;
};
static int zfs_process_value(blkid_probe pr, char *name, size_t namelen,
void *value, size_t max_value_size)
{
if (strncmp(name, "name", namelen) == 0 &&
sizeof(struct nvstring) <= max_value_size) {
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 0;
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);
return 1;
} else if (strncmp(name, "guid", 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 0;
DBG(LOWPROBE, ul_debug("nvuint64: type %u value %"PRIu64"\n",
nvu_type, nvu_value));
blkid_probe_sprintf_value(pr, "UUID_SUB",
"%"PRIu64, nvu_value);
return 1;
} else if (strncmp(name, "pool_guid", 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 0;
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);
return 1;
}
return 0;
}
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;
int found = 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) && nvp->nvp_size != 0 && found < 3) {
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;
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;
found += zfs_process_value(pr, nvp->nvp_name, nvp_namelen,
value, max_value_size);
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;
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;
}
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
};