]>
Commit | Line | Data |
---|---|---|
1f4da8d2 | 1 | /* |
a1fbeb3d | 2 | * Copyright (C) 2009-2010 by Andreas Dilger <adilger@sun.com> |
1f4da8d2 AD |
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> | |
109df14f | 15 | #include <limits.h> |
1f4da8d2 | 16 | |
1e3cf6a2 | 17 | #include "superblocks.h" |
1f4da8d2 | 18 | |
a1fbeb3d AD |
19 | #define VDEV_LABEL_UBERBLOCK (128 * 1024ULL) |
20 | #define VDEV_LABEL_NVPAIR ( 16 * 1024ULL) | |
21 | #define VDEV_LABEL_SIZE (256 * 1024ULL) | |
5dd705ba | 22 | #define UBERBLOCK_SIZE 1024ULL |
e44a4c7a | 23 | #define UBERBLOCKS_COUNT 128 |
a1fbeb3d | 24 | |
1f4da8d2 AD |
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 */ | |
e54a76ca | 33 | char ub_rootbp; /* MOS objset_phys_t */ |
11854e2e | 34 | } __attribute__((packed)); |
1f4da8d2 | 35 | |
a1fbeb3d AD |
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 | #define nvdebug(fmt, ...) do { } while(0) | |
0540ea54 | 69 | /*#define nvdebug(fmt, a...) fprintf(stderr, fmt, ##a)*/ |
a1fbeb3d AD |
70 | |
71 | static void zfs_extract_guid_name(blkid_probe pr, loff_t offset) | |
72 | { | |
73 | struct nvlist *nvl; | |
74 | struct nvpair *nvp; | |
538a2fe9 | 75 | size_t left = 4096; |
a1fbeb3d AD |
76 | int found = 0; |
77 | ||
78 | offset = (offset & ~(VDEV_LABEL_SIZE - 1)) + VDEV_LABEL_NVPAIR; | |
79 | ||
80 | /* Note that we currently assume that the desired fields are within | |
81 | * the first 4k (left) of the nvlist. This is true for all pools | |
82 | * I've seen, and simplifies this code somewhat, because we don't | |
83 | * have to handle an nvpair crossing a buffer boundary. */ | |
84 | nvl = (struct nvlist *)blkid_probe_get_buffer(pr, offset, left); | |
85 | if (nvl == NULL) | |
86 | return; | |
87 | ||
88 | nvdebug("zfs_extract: nvlist offset %llu\n", offset); | |
89 | ||
90 | nvp = &nvl->nvl_nvpair; | |
91 | while (left > sizeof(*nvp) && nvp->nvp_size != 0 && found < 3) { | |
92 | int avail; /* tracks that name/value data fits in nvp_size */ | |
93 | int namesize; | |
94 | ||
95 | nvp->nvp_size = be32_to_cpu(nvp->nvp_size); | |
96 | nvp->nvp_namelen = be32_to_cpu(nvp->nvp_namelen); | |
97 | avail = nvp->nvp_size - nvp->nvp_namelen - sizeof(*nvp); | |
98 | ||
538a2fe9 | 99 | nvdebug("left %zd nvp_size %u\n", left, nvp->nvp_size); |
a1fbeb3d AD |
100 | if (left < nvp->nvp_size || avail < 0) |
101 | break; | |
102 | ||
103 | namesize = (nvp->nvp_namelen + 3) & ~3; | |
104 | ||
105 | nvdebug("nvlist: size %u, namelen %u, name %*s\n", | |
106 | nvp->nvp_size, nvp->nvp_namelen, nvp->nvp_namelen, | |
107 | nvp->nvp_name); | |
108 | if (strncmp(nvp->nvp_name, "name", nvp->nvp_namelen) == 0) { | |
109 | struct nvstring *nvs = (void *)(nvp->nvp_name+namesize); | |
110 | ||
111 | nvs->nvs_type = be32_to_cpu(nvs->nvs_type); | |
112 | nvs->nvs_strlen = be32_to_cpu(nvs->nvs_strlen); | |
109df14f SK |
113 | if (nvs->nvs_strlen > UINT_MAX - sizeof(*nvs)) |
114 | break; | |
a1fbeb3d AD |
115 | avail -= nvs->nvs_strlen + sizeof(*nvs); |
116 | nvdebug("nvstring: type %u string %*s\n", nvs->nvs_type, | |
117 | nvs->nvs_strlen, nvs->nvs_string); | |
118 | if (nvs->nvs_type == DATA_TYPE_STRING && avail >= 0) | |
119 | blkid_probe_set_label(pr, nvs->nvs_string, | |
120 | nvs->nvs_strlen); | |
121 | found++; | |
122 | } else if (strncmp(nvp->nvp_name, "guid", | |
123 | nvp->nvp_namelen) == 0) { | |
124 | struct nvuint64 *nvu = (void *)(nvp->nvp_name+namesize); | |
125 | uint64_t nvu_value; | |
126 | ||
127 | memcpy(&nvu_value, &nvu->nvu_value, sizeof(nvu_value)); | |
128 | nvu->nvu_type = be32_to_cpu(nvu->nvu_type); | |
129 | nvu_value = be64_to_cpu(nvu_value); | |
130 | avail -= sizeof(*nvu); | |
131 | nvdebug("nvuint64: type %u value %"PRIu64"\n", | |
132 | nvu->nvu_type, nvu_value); | |
133 | if (nvu->nvu_type == DATA_TYPE_UINT64 && avail >= 0) | |
134 | blkid_probe_sprintf_value(pr, "UUID_SUB", | |
135 | "%"PRIu64, nvu_value); | |
136 | found++; | |
137 | } else if (strncmp(nvp->nvp_name, "pool_guid", | |
138 | nvp->nvp_namelen) == 0) { | |
139 | struct nvuint64 *nvu = (void *)(nvp->nvp_name+namesize); | |
140 | uint64_t nvu_value; | |
141 | ||
142 | memcpy(&nvu_value, &nvu->nvu_value, sizeof(nvu_value)); | |
143 | nvu->nvu_type = be32_to_cpu(nvu->nvu_type); | |
144 | nvu_value = be64_to_cpu(nvu_value); | |
145 | avail -= sizeof(*nvu); | |
146 | nvdebug("nvuint64: type %u value %"PRIu64"\n", | |
147 | nvu->nvu_type, nvu_value); | |
148 | if (nvu->nvu_type == DATA_TYPE_UINT64 && avail >= 0) | |
149 | blkid_probe_sprintf_uuid(pr, (unsigned char *) | |
150 | &nvu_value, | |
151 | sizeof(nvu_value), | |
152 | "%"PRIu64, nvu_value); | |
153 | found++; | |
154 | } | |
538a2fe9 KZ |
155 | if (left > nvp->nvp_size) |
156 | left -= nvp->nvp_size; | |
157 | else | |
158 | left = 0; | |
a1fbeb3d AD |
159 | nvp = (struct nvpair *)((char *)nvp + nvp->nvp_size); |
160 | } | |
161 | } | |
162 | ||
163 | #define zdebug(fmt, ...) do {} while(0) | |
0540ea54 | 164 | /*#define zdebug(fmt, a...) fprintf(stderr, fmt, ##a)*/ |
a1fbeb3d | 165 | |
e44a4c7a MH |
166 | static int find_uberblocks(const void *label, loff_t *ub_offset, int *swap_endian) |
167 | { | |
168 | uint64_t swab_magic = swab64(UBERBLOCK_MAGIC); | |
169 | struct zfs_uberblock *ub; | |
170 | int i, found = 0; | |
171 | loff_t offset = VDEV_LABEL_UBERBLOCK; | |
172 | ||
173 | for (i = 0; i < UBERBLOCKS_COUNT; i++, offset += UBERBLOCK_SIZE) { | |
174 | ub = (struct zfs_uberblock *)(label + offset); | |
175 | ||
176 | if (ub->ub_magic == UBERBLOCK_MAGIC) { | |
177 | *ub_offset = offset; | |
178 | *swap_endian = 0; | |
179 | found++; | |
180 | zdebug("probe_zfs: found little-endian uberblock at %llu\n", offset >> 10); | |
181 | } | |
182 | ||
183 | if (ub->ub_magic == swab_magic) { | |
184 | *ub_offset = offset; | |
185 | *swap_endian = 1; | |
186 | found++; | |
187 | zdebug("probe_zfs: found big-endian uberblock at %llu\n", offset >> 10); | |
188 | } | |
189 | } | |
190 | ||
191 | return found; | |
192 | } | |
193 | ||
194 | /* ZFS has 128x1kB host-endian root blocks, stored in 2 areas at the start | |
195 | * of the disk, and 2 areas at the end of the disk. Check only some of them... | |
196 | * #4 (@ 132kB) is the first one written on a new filesystem. */ | |
5dd705ba | 197 | static int probe_zfs(blkid_probe pr, const struct blkid_idmag *mag) |
1f4da8d2 | 198 | { |
e44a4c7a | 199 | int swab_endian = 0; |
1f4da8d2 | 200 | struct zfs_uberblock *ub; |
59cbbd71 | 201 | loff_t offset, ub_offset = 0; |
e44a4c7a MH |
202 | int label_no, found = 0, found_in_label; |
203 | void *label; | |
5dd705ba | 204 | loff_t blk_align = (pr->size % (256 * 1024ULL)); |
1f4da8d2 | 205 | |
a1fbeb3d | 206 | zdebug("probe_zfs\n"); |
e44a4c7a MH |
207 | /* Look for at least 4 uberblocks to ensure a positive match */ |
208 | for (label_no = 0; label_no < 4; label_no++) { | |
209 | switch(label_no) { | |
210 | case 0: // jump to L0 | |
211 | offset = 0; | |
5dd705ba | 212 | break; |
e44a4c7a MH |
213 | case 1: // jump to L1 |
214 | offset = VDEV_LABEL_SIZE; | |
5dd705ba | 215 | break; |
e44a4c7a MH |
216 | case 2: // jump to L2 |
217 | offset = pr->size - 2 * VDEV_LABEL_SIZE - blk_align; | |
218 | break; | |
219 | case 3: // jump to L3 | |
220 | offset = pr->size - VDEV_LABEL_SIZE - blk_align; | |
5dd705ba MH |
221 | break; |
222 | } | |
a1fbeb3d | 223 | |
e44a4c7a MH |
224 | label = blkid_probe_get_buffer(pr, offset, VDEV_LABEL_SIZE); |
225 | if (label == NULL) | |
37f40602 | 226 | return errno ? -errno : 1; |
a1fbeb3d | 227 | |
e44a4c7a | 228 | found_in_label = find_uberblocks(label, &ub_offset, &swab_endian); |
a1fbeb3d | 229 | |
e44a4c7a MH |
230 | if (found_in_label > 0) { |
231 | found+= found_in_label; | |
232 | ub = (struct zfs_uberblock *)(label + ub_offset); | |
233 | ub_offset += offset; | |
234 | ||
235 | if (found >= ZFS_WANT) | |
236 | break; | |
983a43e0 | 237 | } |
a1fbeb3d AD |
238 | } |
239 | ||
e44a4c7a | 240 | if (found < ZFS_WANT) |
37f40602 | 241 | return 1; |
1f4da8d2 | 242 | |
a1fbeb3d AD |
243 | /* If we found the 4th uberblock, then we will have exited from the |
244 | * scanning loop immediately, and ub will be a valid uberblock. */ | |
245 | blkid_probe_sprintf_version(pr, "%" PRIu64, swab_endian ? | |
246 | swab64(ub->ub_version) : ub->ub_version); | |
247 | ||
248 | zfs_extract_guid_name(pr, offset); | |
249 | ||
983a43e0 | 250 | if (blkid_probe_set_magic(pr, ub_offset, |
abc67e06 KZ |
251 | sizeof(ub->ub_magic), |
252 | (unsigned char *) &ub->ub_magic)) | |
37f40602 | 253 | return 1; |
abc67e06 | 254 | |
1f4da8d2 AD |
255 | return 0; |
256 | } | |
257 | ||
258 | const struct blkid_idinfo zfs_idinfo = | |
259 | { | |
a1fbeb3d AD |
260 | .name = "zfs_member", |
261 | .usage = BLKID_USAGE_RAID, | |
1f4da8d2 | 262 | .probefunc = probe_zfs, |
8c2b156e | 263 | .minsz = 64 * 1024 * 1024, |
a1fbeb3d | 264 | .magics = BLKID_NONE_MAGIC |
1f4da8d2 | 265 | }; |