]> git.ipfire.org Git - thirdparty/util-linux.git/blobdiff - libblkid/src/superblocks/zfs.c
libblkid: (zfs) don't probe whole-disk areas covered by partitions
[thirdparty/util-linux.git] / libblkid / src / superblocks / zfs.c
index 60e6c7aa874dd2dc1354a02929163d6d9a08f3de..774a199e1b51e57034af74579391a9c8d03cdd31 100644 (file)
@@ -37,6 +37,7 @@ struct zfs_uberblock {
 
 #define DATA_TYPE_UINT64 8
 #define DATA_TYPE_STRING 9
+#define DATA_TYPE_DIRECTORY 19
 
 struct nvpair {
        uint32_t        nvp_size;
@@ -58,6 +59,11 @@ 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 {
@@ -65,16 +71,85 @@ struct nvlist {
        struct nvpair   nvl_nvpair;
 };
 
-#define nvdebug(fmt, ...)      do { } while(0)
-/*#define nvdebug(fmt, a...)   fprintf(stderr, fmt, ##a)*/
+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, buff[4096];
+       unsigned char *p;
        struct nvlist *nvl;
        struct nvpair *nvp;
-       size_t left = sizeof(buff);
-       int found = 0;
+       size_t left = 4096;
+       unsigned directory_level = 0;
 
        offset = (offset & ~(VDEV_LABEL_SIZE - 1)) + VDEV_LABEL_NVPAIR;
 
@@ -86,110 +161,86 @@ static void zfs_extract_guid_name(blkid_probe pr, loff_t offset)
        if (!p)
                return;
 
-       /* libblkid buffers are strictly readonly, but the code below modifies nvpair etc. */
-       memcpy(buff, p, sizeof(buff));
-       nvl = (struct nvlist *) buff;
-
-       nvdebug("zfs_extract: nvlist offset %llu\n", offset);
+       DBG(LOWPROBE, ul_debug("zfs_extract: nvlist offset %jd\n",
+                              (intmax_t)offset));
 
+       nvl = (struct nvlist *) p;
        nvp = &nvl->nvl_nvpair;
-       while (left > sizeof(*nvp) && nvp->nvp_size != 0 && found < 3) {
-               int avail;   /* tracks that name/value data fits in nvp_size */
-               int namesize;
+       left -= (unsigned char *)nvp - p; /* Already used up 12 bytes */
 
-               nvp->nvp_size = be32_to_cpu(nvp->nvp_size);
-               nvp->nvp_namelen = be32_to_cpu(nvp->nvp_namelen);
-               avail = nvp->nvp_size - nvp->nvp_namelen - sizeof(*nvp);
+       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;
 
-               nvdebug("left %zd nvp_size %u\n", left, nvp->nvp_size);
-               if (left < nvp->nvp_size || avail < 0)
-                       break;
+               if (!nvp->nvp_size) {
+                       if (!directory_level)
+                               break;
+                       directory_level--;
+                       nvp_size = 8;
+                       goto cont;
+               }
 
-               namesize = (nvp->nvp_namelen + 3) & ~3;
+               DBG(LOWPROBE, ul_debug("left %zd nvp_size %u\n",
+                                      left, nvp_size));
 
-               nvdebug("nvlist: size %u, namelen %u, name %*s\n",
-                       nvp->nvp_size, nvp->nvp_namelen, nvp->nvp_namelen,
-                       nvp->nvp_name);
-               if (strncmp(nvp->nvp_name, "name", nvp->nvp_namelen) == 0) {
-                       struct nvstring *nvs = (void *)(nvp->nvp_name+namesize);
+               /* nvpair fits in buffer and name fits in nvpair? */
+               if (nvp_size > left || namesize + sizeof(*nvp) > nvp_size)
+                       break;
 
-                       nvs->nvs_type = be32_to_cpu(nvs->nvs_type);
-                       nvs->nvs_strlen = be32_to_cpu(nvs->nvs_strlen);
-                       if (nvs->nvs_strlen > UINT_MAX - sizeof(*nvs))
-                               break;
-                       avail -= nvs->nvs_strlen + sizeof(*nvs);
-                       nvdebug("nvstring: type %u string %*s\n", nvs->nvs_type,
-                               nvs->nvs_strlen, nvs->nvs_string);
-                       if (nvs->nvs_type == DATA_TYPE_STRING && avail >= 0)
-                               blkid_probe_set_label(pr, nvs->nvs_string,
-                                                     nvs->nvs_strlen);
-                       found++;
-               } else if (strncmp(nvp->nvp_name, "guid",
-                                  nvp->nvp_namelen) == 0) {
-                       struct nvuint64 *nvu = (void *)(nvp->nvp_name+namesize);
-                       uint64_t nvu_value;
-
-                       memcpy(&nvu_value, &nvu->nvu_value, sizeof(nvu_value));
-                       nvu->nvu_type = be32_to_cpu(nvu->nvu_type);
-                       nvu_value = be64_to_cpu(nvu_value);
-                       avail -= sizeof(*nvu);
-                       nvdebug("nvuint64: type %u value %"PRIu64"\n",
-                               nvu->nvu_type, nvu_value);
-                       if (nvu->nvu_type == DATA_TYPE_UINT64 && avail >= 0)
-                               blkid_probe_sprintf_value(pr, "UUID_SUB",
-                                                         "%"PRIu64, nvu_value);
-                       found++;
-               } else if (strncmp(nvp->nvp_name, "pool_guid",
-                                  nvp->nvp_namelen) == 0) {
-                       struct nvuint64 *nvu = (void *)(nvp->nvp_name+namesize);
-                       uint64_t nvu_value;
-
-                       memcpy(&nvu_value, &nvu->nvu_value, sizeof(nvu_value));
-                       nvu->nvu_type = be32_to_cpu(nvu->nvu_type);
-                       nvu_value = be64_to_cpu(nvu_value);
-                       avail -= sizeof(*nvu);
-                       nvdebug("nvuint64: type %u value %"PRIu64"\n",
-                               nvu->nvu_type, nvu_value);
-                       if (nvu->nvu_type == DATA_TYPE_UINT64 && avail >= 0)
-                               blkid_probe_sprintf_uuid(pr, (unsigned char *)
-                                                        &nvu_value,
-                                                        sizeof(nvu_value),
-                                                        "%"PRIu64, nvu_value);
-                       found++;
+               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;
+                       }
                }
-               if (left > nvp->nvp_size)
-                       left -= nvp->nvp_size;
-               else
-                       left = 0;
-               nvp = (struct nvpair *)((char *)nvp + nvp->nvp_size);
+
+               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);
        }
 }
 
-#define zdebug(fmt, ...)       do {} while(0)
-/*#define zdebug(fmt, a...)    fprintf(stderr, fmt, ##a)*/
-
 static int find_uberblocks(const void *label, loff_t *ub_offset, int *swap_endian)
 {
-       uint64_t swab_magic = swab64(UBERBLOCK_MAGIC);
-       struct zfs_uberblock *ub;
+       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 = (struct zfs_uberblock *)(label + offset);
+               ub = (const struct zfs_uberblock *)((const char *) label + offset);
 
                if (ub->ub_magic == UBERBLOCK_MAGIC) {
                        *ub_offset = offset;
                        *swap_endian = 0;
                        found++;
-                       zdebug("probe_zfs: found little-endian uberblock at %llu\n", offset >> 10);
+                       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++;
-                       zdebug("probe_zfs: found big-endian uberblock at %llu\n", offset >> 10);
+                       DBG(LOWPROBE, ul_debug("probe_zfs: found big-endian uberblock at %jd\n", (intmax_t)offset >> 10));
                }
        }
 
@@ -199,16 +250,17 @@ static int find_uberblocks(const void *label, loff_t *ub_offset, int *swap_endia
 /* 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)
+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, ub_offset = 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));
 
-       zdebug("probe_zfs\n");
+       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) {
@@ -226,6 +278,12 @@ static int probe_zfs(blkid_probe pr, const struct blkid_idmag *mag)
                        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;
@@ -234,7 +292,7 @@ static int probe_zfs(blkid_probe pr, const struct blkid_idmag *mag)
 
                if (found_in_label > 0) {
                        found+= found_in_label;
-                       ub = (struct zfs_uberblock *)(label + ub_offset);
+                       ub = (struct zfs_uberblock *)((char *) label + ub_offset);
                        ub_offset += offset;
 
                        if (found >= ZFS_WANT)
@@ -263,7 +321,7 @@ static int probe_zfs(blkid_probe pr, const struct blkid_idmag *mag)
 const struct blkid_idinfo zfs_idinfo =
 {
        .name           = "zfs_member",
-       .usage          = BLKID_USAGE_RAID,
+       .usage          = BLKID_USAGE_FILESYSTEM,
        .probefunc      = probe_zfs,
        .minsz          = 64 * 1024 * 1024,
        .magics         = BLKID_NONE_MAGIC