]> git.ipfire.org Git - thirdparty/util-linux.git/blob - libblkid/src/superblocks/zfs.c
Merge branch 'hardlink-import' into hardlink
[thirdparty/util-linux.git] / libblkid / src / superblocks / zfs.c
1 /*
2 * Copyright (C) 2009-2010 by Andreas Dilger <adilger@sun.com>
3 *
4 * This file may be redistributed under the terms of the
5 * GNU Lesser General Public License.
6 */
7
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <unistd.h>
11 #include <string.h>
12 #include <errno.h>
13 #include <ctype.h>
14 #include <inttypes.h>
15 #include <limits.h>
16
17 #include "superblocks.h"
18
19 #define VDEV_LABEL_UBERBLOCK (128 * 1024ULL)
20 #define VDEV_LABEL_NVPAIR ( 16 * 1024ULL)
21 #define VDEV_LABEL_SIZE (256 * 1024ULL)
22 #define UBERBLOCK_SIZE 1024ULL
23 #define UBERBLOCKS_COUNT 128
24
25 /* #include <sys/uberblock_impl.h> */
26 #define UBERBLOCK_MAGIC 0x00bab10c /* oo-ba-bloc! */
27 struct zfs_uberblock {
28 uint64_t ub_magic; /* UBERBLOCK_MAGIC */
29 uint64_t ub_version; /* SPA_VERSION */
30 uint64_t ub_txg; /* txg of last sync */
31 uint64_t ub_guid_sum; /* sum of all vdev guids */
32 uint64_t ub_timestamp; /* UTC time of last sync */
33 char ub_rootbp; /* MOS objset_phys_t */
34 } __attribute__((packed));
35
36 #define ZFS_WANT 4
37
38 #define DATA_TYPE_UINT64 8
39 #define DATA_TYPE_STRING 9
40
41 struct nvpair {
42 uint32_t nvp_size;
43 uint32_t nvp_unkown;
44 uint32_t nvp_namelen;
45 char nvp_name[0]; /* aligned to 4 bytes */
46 /* aligned ptr array for string arrays */
47 /* aligned array of data for value */
48 };
49
50 struct nvstring {
51 uint32_t nvs_type;
52 uint32_t nvs_elem;
53 uint32_t nvs_strlen;
54 unsigned char nvs_string[0];
55 };
56
57 struct nvuint64 {
58 uint32_t nvu_type;
59 uint32_t nvu_elem;
60 uint64_t nvu_value;
61 };
62
63 struct nvlist {
64 uint32_t nvl_unknown[3];
65 struct nvpair nvl_nvpair;
66 };
67
68 static int zfs_process_value(blkid_probe pr, char *name, size_t namelen,
69 void *value, size_t max_value_size)
70 {
71 if (strncmp(name, "name", namelen) == 0 &&
72 sizeof(struct nvstring) <= max_value_size) {
73 struct nvstring *nvs = value;
74 uint32_t nvs_type = be32_to_cpu(nvs->nvs_type);
75 uint32_t nvs_strlen = be32_to_cpu(nvs->nvs_strlen);
76
77 if (nvs_type != DATA_TYPE_STRING ||
78 (uint64_t)nvs_strlen + sizeof(*nvs) > max_value_size)
79 return 0;
80
81 DBG(LOWPROBE, ul_debug("nvstring: type %u string %*s\n",
82 nvs_type, nvs_strlen, nvs->nvs_string));
83
84 blkid_probe_set_label(pr, nvs->nvs_string, nvs_strlen);
85
86 return 1;
87 } else if (strncmp(name, "guid", namelen) == 0 &&
88 sizeof(struct nvuint64) <= max_value_size) {
89 struct nvuint64 *nvu = value;
90 uint32_t nvu_type = be32_to_cpu(nvu->nvu_type);
91 uint64_t nvu_value;
92
93 memcpy(&nvu_value, &nvu->nvu_value, sizeof(nvu_value));
94 nvu_value = be64_to_cpu(nvu_value);
95
96 if (nvu_type != DATA_TYPE_UINT64)
97 return 0;
98
99 DBG(LOWPROBE, ul_debug("nvuint64: type %u value %"PRIu64"\n",
100 nvu_type, nvu_value));
101
102 blkid_probe_sprintf_value(pr, "UUID_SUB",
103 "%"PRIu64, nvu_value);
104
105 return 1;
106 } else if (strncmp(name, "pool_guid", namelen) == 0 &&
107 sizeof(struct nvuint64) <= max_value_size) {
108 struct nvuint64 *nvu = value;
109 uint32_t nvu_type = be32_to_cpu(nvu->nvu_type);
110 uint64_t nvu_value;
111
112 memcpy(&nvu_value, &nvu->nvu_value, sizeof(nvu_value));
113 nvu_value = be64_to_cpu(nvu_value);
114
115 if (nvu_type != DATA_TYPE_UINT64)
116 return 0;
117
118 DBG(LOWPROBE, ul_debug("nvuint64: type %u value %"PRIu64"\n",
119 nvu_type, nvu_value));
120
121 blkid_probe_sprintf_uuid(pr, (unsigned char *) &nvu_value,
122 sizeof(nvu_value),
123 "%"PRIu64, nvu_value);
124 return 1;
125 }
126
127 return 0;
128 }
129
130 static void zfs_extract_guid_name(blkid_probe pr, loff_t offset)
131 {
132 unsigned char *p;
133 struct nvlist *nvl;
134 struct nvpair *nvp;
135 size_t left = 4096;
136 int found = 0;
137
138 offset = (offset & ~(VDEV_LABEL_SIZE - 1)) + VDEV_LABEL_NVPAIR;
139
140 /* Note that we currently assume that the desired fields are within
141 * the first 4k (left) of the nvlist. This is true for all pools
142 * I've seen, and simplifies this code somewhat, because we don't
143 * have to handle an nvpair crossing a buffer boundary. */
144 p = blkid_probe_get_buffer(pr, offset, left);
145 if (!p)
146 return;
147
148 DBG(LOWPROBE, ul_debug("zfs_extract: nvlist offset %jd\n",
149 (intmax_t)offset));
150
151 nvl = (struct nvlist *) p;
152 nvp = &nvl->nvl_nvpair;
153 left -= (unsigned char *)nvp - p; /* Already used up 12 bytes */
154
155 while (left > sizeof(*nvp) && nvp->nvp_size != 0 && found < 3) {
156 uint32_t nvp_size = be32_to_cpu(nvp->nvp_size);
157 uint32_t nvp_namelen = be32_to_cpu(nvp->nvp_namelen);
158 uint64_t namesize = ((uint64_t)nvp_namelen + 3) & ~3;
159 size_t max_value_size;
160 void *value;
161
162 DBG(LOWPROBE, ul_debug("left %zd nvp_size %u\n",
163 left, nvp_size));
164
165 /* nvpair fits in buffer and name fits in nvpair? */
166 if (nvp_size > left || namesize + sizeof(*nvp) > nvp_size)
167 break;
168
169 DBG(LOWPROBE,
170 ul_debug("nvlist: size %u, namelen %u, name %*s\n",
171 nvp_size, nvp_namelen, nvp_namelen,
172 nvp->nvp_name));
173
174 max_value_size = nvp_size - (namesize + sizeof(*nvp));
175 value = nvp->nvp_name + namesize;
176
177 found += zfs_process_value(pr, nvp->nvp_name, nvp_namelen,
178 value, max_value_size);
179
180 left -= nvp_size;
181
182 nvp = (struct nvpair *)((char *)nvp + nvp_size);
183 }
184 }
185
186 static int find_uberblocks(const void *label, loff_t *ub_offset, int *swap_endian)
187 {
188 uint64_t swab_magic = swab64((uint64_t)UBERBLOCK_MAGIC);
189 struct zfs_uberblock *ub;
190 int i, found = 0;
191 loff_t offset = VDEV_LABEL_UBERBLOCK;
192
193 for (i = 0; i < UBERBLOCKS_COUNT; i++, offset += UBERBLOCK_SIZE) {
194 ub = (struct zfs_uberblock *)((char *) label + offset);
195
196 if (ub->ub_magic == UBERBLOCK_MAGIC) {
197 *ub_offset = offset;
198 *swap_endian = 0;
199 found++;
200 DBG(LOWPROBE, ul_debug("probe_zfs: found little-endian uberblock at %jd\n", (intmax_t)offset >> 10));
201 }
202
203 if (ub->ub_magic == swab_magic) {
204 *ub_offset = offset;
205 *swap_endian = 1;
206 found++;
207 DBG(LOWPROBE, ul_debug("probe_zfs: found big-endian uberblock at %jd\n", (intmax_t)offset >> 10));
208 }
209 }
210
211 return found;
212 }
213
214 /* ZFS has 128x1kB host-endian root blocks, stored in 2 areas at the start
215 * of the disk, and 2 areas at the end of the disk. Check only some of them...
216 * #4 (@ 132kB) is the first one written on a new filesystem. */
217 static int probe_zfs(blkid_probe pr,
218 const struct blkid_idmag *mag __attribute__((__unused__)))
219 {
220 int swab_endian = 0;
221 struct zfs_uberblock *ub;
222 loff_t offset = 0, ub_offset = 0;
223 int label_no, found = 0, found_in_label;
224 void *label;
225 loff_t blk_align = (pr->size % (256 * 1024ULL));
226
227 DBG(PROBE, ul_debug("probe_zfs\n"));
228 /* Look for at least 4 uberblocks to ensure a positive match */
229 for (label_no = 0; label_no < 4; label_no++) {
230 switch(label_no) {
231 case 0: // jump to L0
232 offset = 0;
233 break;
234 case 1: // jump to L1
235 offset = VDEV_LABEL_SIZE;
236 break;
237 case 2: // jump to L2
238 offset = pr->size - 2 * VDEV_LABEL_SIZE - blk_align;
239 break;
240 case 3: // jump to L3
241 offset = pr->size - VDEV_LABEL_SIZE - blk_align;
242 break;
243 }
244
245 label = blkid_probe_get_buffer(pr, offset, VDEV_LABEL_SIZE);
246 if (label == NULL)
247 return errno ? -errno : 1;
248
249 found_in_label = find_uberblocks(label, &ub_offset, &swab_endian);
250
251 if (found_in_label > 0) {
252 found+= found_in_label;
253 ub = (struct zfs_uberblock *)((char *) label + ub_offset);
254 ub_offset += offset;
255
256 if (found >= ZFS_WANT)
257 break;
258 }
259 }
260
261 if (found < ZFS_WANT)
262 return 1;
263
264 /* If we found the 4th uberblock, then we will have exited from the
265 * scanning loop immediately, and ub will be a valid uberblock. */
266 blkid_probe_sprintf_version(pr, "%" PRIu64, swab_endian ?
267 swab64(ub->ub_version) : ub->ub_version);
268
269 zfs_extract_guid_name(pr, offset);
270
271 if (blkid_probe_set_magic(pr, ub_offset,
272 sizeof(ub->ub_magic),
273 (unsigned char *) &ub->ub_magic))
274 return 1;
275
276 return 0;
277 }
278
279 const struct blkid_idinfo zfs_idinfo =
280 {
281 .name = "zfs_member",
282 .usage = BLKID_USAGE_FILESYSTEM,
283 .probefunc = probe_zfs,
284 .minsz = 64 * 1024 * 1024,
285 .magics = BLKID_NONE_MAGIC
286 };