/src/util-linux/libblkid/src/superblocks/bcache.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright (C) 2013 Rolf Fokkens <rolf@fokkens.nl> |
3 | | * |
4 | | * This file may be redistributed under the terms of the |
5 | | * GNU Lesser General Public License. |
6 | | * |
7 | | * Based on code fragments from bcache-tools by Kent Overstreet: |
8 | | * http://evilpiepirate.org/git/bcache-tools.git |
9 | | */ |
10 | | |
11 | | #include <stddef.h> |
12 | | #include <stdio.h> |
13 | | #include <inttypes.h> |
14 | | |
15 | | #include "superblocks.h" |
16 | | #include "crc32c.h" |
17 | | #include "crc64.h" |
18 | | #include "xxhash.h" |
19 | | |
20 | | #define SB_LABEL_SIZE 32 |
21 | | #define SB_JOURNAL_BUCKETS 256U |
22 | | |
23 | | /* |
24 | | * The bcache_super_block is adapted from struct cache_sb in kernel. |
25 | | * https://github.com/torvalds/linux/blob/master/drivers/md/bcache/bcache_ondisk.h |
26 | | */ |
27 | | struct bcache_super_block { |
28 | | uint64_t csum; |
29 | | uint64_t offset; /* where this super block was written */ |
30 | | uint64_t version; |
31 | | uint8_t magic[16]; /* bcache file system identifier */ |
32 | | uint8_t uuid[16]; /* device identifier */ |
33 | | uint8_t set_info[16]; /* magic or uuid */ |
34 | | uint8_t label[SB_LABEL_SIZE]; |
35 | | uint64_t flags; |
36 | | uint64_t seq; |
37 | | |
38 | | uint64_t feature_compat; |
39 | | uint64_t feature_incompat; |
40 | | uint64_t feature_ro_compat; |
41 | | |
42 | | uint64_t pad[5]; |
43 | | |
44 | | union { |
45 | | struct { |
46 | | /* Cache devices */ |
47 | | uint64_t nbuckets; /* device size */ |
48 | | |
49 | | uint16_t block_size; /* sectors */ |
50 | | uint16_t bucket_size; /* sectors */ |
51 | | |
52 | | uint16_t nr_in_set; |
53 | | uint16_t nr_this_dev; |
54 | | }; |
55 | | struct { |
56 | | /* Backing devices */ |
57 | | uint64_t data_offset; |
58 | | }; |
59 | | }; |
60 | | |
61 | | uint32_t last_mount; |
62 | | |
63 | | uint16_t first_bucket; |
64 | | union { |
65 | | uint16_t njournal_buckets; |
66 | | uint16_t keys; |
67 | | }; |
68 | | uint64_t d[SB_JOURNAL_BUCKETS]; /* journal buckets */ |
69 | | uint16_t obso_bucket_size_hi; /* obsoleted */ |
70 | | } __attribute__((packed)); |
71 | | |
72 | | struct bcachefs_sb_field { |
73 | | uint32_t u64s; |
74 | | uint32_t type; |
75 | | } __attribute__((packed)); |
76 | | |
77 | | struct bcachefs_sb_member { |
78 | | uint8_t uuid[16]; |
79 | | uint64_t nbuckets; |
80 | | uint16_t first_bucket; |
81 | | uint16_t bucket_size; |
82 | | uint32_t pad; |
83 | | uint64_t last_mount; |
84 | | uint64_t flags[2]; |
85 | | } __attribute__((packed)); |
86 | | |
87 | | struct bcachefs_sb_field_members { |
88 | | struct bcachefs_sb_field field; |
89 | | struct bcachefs_sb_member members[]; |
90 | | } __attribute__((packed)); |
91 | | |
92 | | struct bcachefs_sb_disk_group { |
93 | | uint8_t label[SB_LABEL_SIZE]; |
94 | | uint64_t flags[2]; |
95 | | } __attribute__((packed)); |
96 | | |
97 | | struct bcachefs_sb_field_disk_groups { |
98 | | struct bcachefs_sb_field field; |
99 | | struct bcachefs_sb_disk_group disk_groups[]; |
100 | | } __attribute__((packed)); |
101 | | |
102 | | enum bcachefs_sb_csum_type { |
103 | | BCACHEFS_SB_CSUM_TYPE_NONE = 0, |
104 | | BCACHEFS_SB_CSUM_TYPE_CRC32C = 1, |
105 | | BCACHEFS_SB_CSUM_TYPE_CRC64 = 2, |
106 | | BCACHEFS_SB_CSUM_TYPE_XXHASH = 7, |
107 | | }; |
108 | | |
109 | | union bcachefs_sb_csum { |
110 | | uint32_t crc32c; |
111 | | uint64_t crc64; |
112 | | XXH64_hash_t xxh64; |
113 | | uint8_t raw[16]; |
114 | | } __attribute__((packed)); |
115 | | |
116 | | struct bcachefs_sb_layout { |
117 | | uint8_t magic[16]; |
118 | | uint8_t layout_type; |
119 | | uint8_t sb_max_size_bits; |
120 | | uint8_t nr_superblocks; |
121 | | uint8_t pad[5]; |
122 | | uint64_t sb_offset[61]; |
123 | | } __attribute__((packed)); |
124 | | |
125 | | struct bcachefs_super_block { |
126 | | union bcachefs_sb_csum csum; |
127 | | uint16_t version; |
128 | | uint16_t version_min; |
129 | | uint16_t pad[2]; |
130 | | uint8_t magic[16]; |
131 | | uint8_t uuid[16]; |
132 | | uint8_t user_uuid[16]; |
133 | | uint8_t label[SB_LABEL_SIZE]; |
134 | | uint64_t offset; |
135 | | uint64_t seq; |
136 | | uint16_t block_size; |
137 | | uint8_t dev_idx; |
138 | | uint8_t nr_devices; |
139 | | uint32_t u64s; |
140 | | uint64_t time_base_lo; |
141 | | uint32_t time_base_hi; |
142 | | uint32_t time_precision; |
143 | | uint64_t flags[8]; |
144 | | uint64_t features[2]; |
145 | | uint64_t compat[2]; |
146 | | struct bcachefs_sb_layout layout; |
147 | | struct bcachefs_sb_field _start[]; |
148 | | } __attribute__((packed)); |
149 | | |
150 | | /* magic string */ |
151 | | #define BCACHE_SB_MAGIC "\xc6\x85\x73\xf6\x4e\x1a\x45\xca\x82\x65\xf5\x7f\x48\xba\x6d\x81" |
152 | | #define BCACHEFS_SB_MAGIC "\xc6\x85\x73\xf6\x66\xce\x90\xa9\xd9\x6a\x60\xcf\x80\x3d\xf7\xef" |
153 | | /* magic string len */ |
154 | | #define BCACHE_SB_MAGIC_LEN (sizeof(BCACHE_SB_MAGIC) - 1) |
155 | | /* super block offset */ |
156 | 66 | #define BCACHE_SB_OFF 0x1000U |
157 | | /* supper block offset in kB */ |
158 | | #define BCACHE_SB_KBOFF (BCACHE_SB_OFF >> 10) |
159 | | /* magic string offset within super block */ |
160 | | #define BCACHE_SB_MAGIC_OFF offsetof(struct bcache_super_block, magic) |
161 | | /* start of checksummed data within superblock */ |
162 | 78 | #define BCACHE_SB_CSUMMED_START 8U |
163 | | /* granularity of offset and length fields within superblock */ |
164 | 450 | #define BCACHEFS_SECTOR_SIZE 512U |
165 | | /* maximum superblock size shift */ |
166 | 107 | #define BCACHEFS_SB_MAX_SIZE_SHIFT 0x10U |
167 | | /* fields offset within super block */ |
168 | 171 | #define BCACHEFS_SB_FIELDS_OFF offsetof(struct bcachefs_super_block, _start) |
169 | | /* tag value for members field */ |
170 | 162 | #define BCACHEFS_SB_FIELD_TYPE_MEMBERS 1 |
171 | | /* tag value for disk_groups field */ |
172 | 162 | #define BCACHEFS_SB_FIELD_TYPE_DISK_GROUPS 5 |
173 | | /* version splitting helpers */ |
174 | 64 | #define BCH_VERSION_MAJOR(_v) ((uint16_t) ((_v) >> 10)) |
175 | 64 | #define BCH_VERSION_MINOR(_v) ((uint16_t) ((_v) & ~(~0U << 10))) |
176 | | |
177 | 553 | #define BYTES(f) ((((uint64_t) le32_to_cpu((f)->u64s)) * 8)) |
178 | | |
179 | | static int bcache_verify_checksum(blkid_probe pr, const struct blkid_idmag *mag, |
180 | | const struct bcache_super_block *bcs) |
181 | 131 | { |
182 | 131 | const unsigned char *csummed; |
183 | 131 | size_t csummed_size; |
184 | 131 | uint64_t csum; |
185 | | |
186 | 131 | if (le16_to_cpu(bcs->keys) > ARRAY_SIZE(bcs->d)) |
187 | 92 | return 0; |
188 | | |
189 | | /* up to the end of bcs->d[] */ |
190 | 39 | csummed_size = offsetof(__typeof__(*bcs), d) + |
191 | 39 | sizeof(bcs->d[0]) * le16_to_cpu(bcs->keys); |
192 | 39 | csummed = blkid_probe_get_sb_buffer(pr, mag, csummed_size); |
193 | 39 | csum = ul_crc64_we(csummed + BCACHE_SB_CSUMMED_START, |
194 | 39 | csummed_size - BCACHE_SB_CSUMMED_START); |
195 | 39 | return blkid_probe_verify_csum(pr, csum, le64_to_cpu(bcs->csum)); |
196 | 131 | } |
197 | | |
198 | | static int probe_bcache (blkid_probe pr, const struct blkid_idmag *mag) |
199 | 131 | { |
200 | 131 | const struct bcache_super_block *bcs; |
201 | | |
202 | 131 | bcs = blkid_probe_get_sb(pr, mag, struct bcache_super_block); |
203 | 131 | if (!bcs) |
204 | 0 | return errno ? -errno : BLKID_PROBE_NONE; |
205 | | |
206 | 131 | if (!bcache_verify_checksum(pr, mag, bcs)) |
207 | 129 | return BLKID_PROBE_NONE; |
208 | | |
209 | 2 | if (le64_to_cpu(bcs->offset) != BCACHE_SB_OFF / 512) |
210 | 2 | return BLKID_PROBE_NONE; |
211 | | |
212 | 0 | if (blkid_probe_sprintf_version(pr, "%"PRIu64, le64_to_cpu(bcs->version)) < 0) |
213 | 0 | return BLKID_PROBE_NONE; |
214 | | |
215 | 0 | if (blkid_probe_set_uuid(pr, bcs->uuid) < 0) |
216 | 0 | return BLKID_PROBE_NONE; |
217 | | |
218 | 0 | if (blkid_probe_set_label(pr, bcs->label, sizeof(bcs->label)) < 0) |
219 | 0 | return BLKID_PROBE_NONE; |
220 | | |
221 | 0 | if (blkid_probe_set_block_size(pr, le16_to_cpu(bcs->block_size) * 512)) |
222 | 0 | return BLKID_PROBE_NONE; |
223 | | |
224 | 0 | blkid_probe_set_wiper(pr, 0, BCACHE_SB_OFF); |
225 | |
|
226 | 0 | return BLKID_PROBE_OK; |
227 | 0 | } |
228 | | |
229 | | static void probe_bcachefs_sb_members(blkid_probe pr, |
230 | | const struct bcachefs_super_block *bcs, |
231 | | const struct bcachefs_sb_field *field, |
232 | | uint8_t dev_idx) |
233 | 50 | { |
234 | 50 | struct bcachefs_sb_field_members *members = |
235 | 50 | (struct bcachefs_sb_field_members *) field; |
236 | 50 | uint64_t sectors = 0; |
237 | 50 | uint8_t i; |
238 | | |
239 | 50 | if (BYTES(field) != offsetof(__typeof__(*members), members[bcs->nr_devices])) |
240 | 50 | return; |
241 | | |
242 | 0 | blkid_probe_set_uuid_as(pr, members->members[dev_idx].uuid, "UUID_SUB"); |
243 | |
|
244 | 0 | for (i = 0; i < bcs->nr_devices; i++) { |
245 | 0 | struct bcachefs_sb_member *member = &members->members[i]; |
246 | 0 | sectors += le64_to_cpu(member->nbuckets) * le16_to_cpu(member->bucket_size); |
247 | 0 | } |
248 | 0 | blkid_probe_set_fssize(pr, sectors * BCACHEFS_SECTOR_SIZE); |
249 | 0 | } |
250 | | |
251 | | static void probe_bcachefs_sb_disk_groups(blkid_probe pr, |
252 | | const struct bcachefs_super_block *bcs, |
253 | | const struct bcachefs_sb_field *field, |
254 | | uint8_t dev_idx) |
255 | 8 | { |
256 | 8 | struct bcachefs_sb_field_disk_groups *disk_groups = |
257 | 8 | (struct bcachefs_sb_field_disk_groups *) field; |
258 | | |
259 | 8 | if (BYTES(field) != offsetof(__typeof__(*disk_groups), disk_groups[bcs->nr_devices])) |
260 | 8 | return; |
261 | | |
262 | 0 | blkid_probe_set_id_label(pr, "LABEL_SUB", |
263 | 0 | disk_groups->disk_groups[dev_idx].label, |
264 | 0 | sizeof(disk_groups->disk_groups[dev_idx].label)); |
265 | 0 | } |
266 | | |
267 | | static int is_within_range(const void *start, uint64_t size, const void *end) |
268 | 444 | { |
269 | 444 | ptrdiff_t diff; |
270 | | |
271 | 444 | if (start >= end) |
272 | 0 | return 0; // should not happen |
273 | | |
274 | 444 | diff = (unsigned char *) end - (unsigned char *) start; |
275 | 444 | return size <= (uint64_t) diff; |
276 | 444 | } |
277 | | |
278 | | static void probe_bcachefs_sb_fields(blkid_probe pr, const struct bcachefs_super_block *bcs, |
279 | | const unsigned char *sb_start, const unsigned char *sb_end) |
280 | 64 | { |
281 | 64 | const unsigned char *field_addr = sb_start + BCACHEFS_SB_FIELDS_OFF; |
282 | | |
283 | 226 | while (1) { |
284 | 226 | struct bcachefs_sb_field *field = (struct bcachefs_sb_field *) field_addr; |
285 | 226 | uint64_t field_size; |
286 | 226 | uint32_t type; |
287 | | |
288 | 226 | if (!is_within_range(field, sizeof(*field), sb_end)) |
289 | 0 | break; |
290 | | |
291 | 226 | field_size = BYTES(field); |
292 | | |
293 | 226 | if (field_size < sizeof(*field)) |
294 | 8 | break; |
295 | | |
296 | 218 | if (!is_within_range(field, field_size, sb_end)) |
297 | 53 | break; |
298 | | |
299 | 165 | type = le32_to_cpu(field->type); |
300 | 165 | if (!type) |
301 | 3 | break; |
302 | | |
303 | 162 | if (type == BCACHEFS_SB_FIELD_TYPE_MEMBERS) |
304 | 50 | probe_bcachefs_sb_members(pr, bcs, field, bcs->dev_idx); |
305 | | |
306 | 162 | if (type == BCACHEFS_SB_FIELD_TYPE_DISK_GROUPS) |
307 | 8 | probe_bcachefs_sb_disk_groups(pr, bcs, field, bcs->dev_idx); |
308 | | |
309 | 162 | field_addr += BYTES(field); |
310 | 162 | } |
311 | 64 | } |
312 | | |
313 | | static int bcachefs_validate_checksum(blkid_probe pr, const struct bcachefs_super_block *bcs, |
314 | | const unsigned char *sb, const unsigned char *sb_end) |
315 | 87 | { |
316 | 87 | uint8_t checksum_type = be64_to_cpu(bcs->flags[0]) >> 58; |
317 | 87 | const unsigned char *checksummed_data_start = sb + sizeof(bcs->csum); |
318 | 87 | size_t checksummed_data_size = sb_end - checksummed_data_start; |
319 | | |
320 | 87 | switch (checksum_type) { |
321 | 2 | case BCACHEFS_SB_CSUM_TYPE_NONE: |
322 | 2 | return 1; |
323 | 3 | case BCACHEFS_SB_CSUM_TYPE_CRC32C: { |
324 | 3 | uint32_t crc = crc32c(~0LL, checksummed_data_start, checksummed_data_size) ^ ~0LL; |
325 | 3 | return blkid_probe_verify_csum(pr, crc, le32_to_cpu(bcs->csum.crc32c)); |
326 | 0 | } |
327 | 2 | case BCACHEFS_SB_CSUM_TYPE_CRC64: { |
328 | 2 | uint64_t crc = ul_crc64_we(checksummed_data_start, checksummed_data_size); |
329 | 2 | return blkid_probe_verify_csum(pr, crc, le64_to_cpu(bcs->csum.crc64)); |
330 | 0 | } |
331 | 18 | case BCACHEFS_SB_CSUM_TYPE_XXHASH: { |
332 | 18 | XXH64_hash_t xxh64 = XXH64(checksummed_data_start, checksummed_data_size, 0); |
333 | 18 | return blkid_probe_verify_csum(pr, xxh64, le64_to_cpu(bcs->csum.xxh64)); |
334 | 0 | } |
335 | 62 | default: |
336 | 62 | DBG(LOWPROBE, ul_debug("bcachefs: unknown checksum type %d, ignoring.", checksum_type)); |
337 | 62 | return 1; |
338 | 87 | } |
339 | 87 | } |
340 | | |
341 | | static int probe_bcachefs(blkid_probe pr, const struct blkid_idmag *mag) |
342 | 220 | { |
343 | 220 | const struct bcachefs_super_block *bcs; |
344 | 220 | const unsigned char *sb, *sb_end; |
345 | 220 | uint64_t sb_size, blocksize, offset_sectors; |
346 | 220 | uint16_t version; |
347 | | |
348 | 220 | bcs = blkid_probe_get_sb(pr, mag, struct bcachefs_super_block); |
349 | 220 | if (!bcs) |
350 | 0 | return errno ? -errno : BLKID_PROBE_NONE; |
351 | | |
352 | 220 | offset_sectors = blkid_probe_get_idmag_off(pr, mag) / BCACHEFS_SECTOR_SIZE; |
353 | 220 | if (le64_to_cpu(bcs->offset) != offset_sectors) |
354 | 103 | return BLKID_PROBE_NONE; |
355 | | |
356 | 117 | if (bcs->nr_devices == 0 || bcs->dev_idx >= bcs->nr_devices) |
357 | 10 | return BLKID_PROBE_NONE; |
358 | | |
359 | 107 | sb_size = BCACHEFS_SB_FIELDS_OFF + BYTES(bcs); |
360 | | |
361 | 107 | if (bcs->layout.sb_max_size_bits > BCACHEFS_SB_MAX_SIZE_SHIFT) |
362 | 5 | return BLKID_PROBE_NONE; |
363 | | |
364 | 102 | if (sb_size > (BCACHEFS_SECTOR_SIZE << bcs->layout.sb_max_size_bits)) |
365 | 13 | return BLKID_PROBE_NONE; |
366 | | |
367 | 89 | sb = blkid_probe_get_sb_buffer(pr, mag, sb_size); |
368 | 89 | if (!sb) |
369 | 2 | return BLKID_PROBE_NONE; |
370 | 87 | sb_end = sb + sb_size; |
371 | | |
372 | 87 | if (!bcachefs_validate_checksum(pr, bcs, sb, sb_end)) |
373 | 23 | return BLKID_PROBE_NONE; |
374 | | |
375 | 64 | blkid_probe_set_uuid(pr, bcs->user_uuid); |
376 | 64 | blkid_probe_set_label(pr, bcs->label, sizeof(bcs->label)); |
377 | 64 | version = le16_to_cpu(bcs->version); |
378 | 64 | blkid_probe_sprintf_version(pr, "%"PRIu16".%"PRIu16, |
379 | 64 | BCH_VERSION_MAJOR(version), |
380 | 64 | BCH_VERSION_MINOR(version)); |
381 | 64 | blocksize = le16_to_cpu(bcs->block_size); |
382 | 64 | blkid_probe_set_block_size(pr, blocksize * BCACHEFS_SECTOR_SIZE); |
383 | 64 | blkid_probe_set_fsblocksize(pr, blocksize * BCACHEFS_SECTOR_SIZE); |
384 | 64 | blkid_probe_set_wiper(pr, 0, BCACHE_SB_OFF); |
385 | | |
386 | 64 | probe_bcachefs_sb_fields(pr, bcs, sb, sb_end); |
387 | | |
388 | 64 | return BLKID_PROBE_OK; |
389 | 87 | } |
390 | | |
391 | | const struct blkid_idinfo bcache_idinfo = |
392 | | { |
393 | | .name = "bcache", |
394 | | .usage = BLKID_USAGE_OTHER, |
395 | | .probefunc = probe_bcache, |
396 | | .minsz = 8192, |
397 | | .magics = |
398 | | { |
399 | | { |
400 | | .magic = BCACHE_SB_MAGIC, |
401 | | .len = BCACHE_SB_MAGIC_LEN, |
402 | | .kboff = BCACHE_SB_KBOFF, |
403 | | .sboff = BCACHE_SB_MAGIC_OFF |
404 | | }, |
405 | | { NULL } |
406 | | } |
407 | | }; |
408 | | |
409 | | const struct blkid_idinfo bcachefs_idinfo = |
410 | | { |
411 | | .name = "bcachefs", |
412 | | .usage = BLKID_USAGE_FILESYSTEM, |
413 | | .probefunc = probe_bcachefs, |
414 | | .minsz = 256 * BCACHEFS_SECTOR_SIZE, |
415 | | .magics = { |
416 | | { |
417 | | .magic = BCACHE_SB_MAGIC, |
418 | | .len = BCACHE_SB_MAGIC_LEN, |
419 | | .kboff = BCACHE_SB_KBOFF, |
420 | | .sboff = BCACHE_SB_MAGIC_OFF, |
421 | | }, |
422 | | { |
423 | | .magic = BCACHEFS_SB_MAGIC, |
424 | | .len = BCACHE_SB_MAGIC_LEN, |
425 | | .kboff = BCACHE_SB_KBOFF, |
426 | | .sboff = BCACHE_SB_MAGIC_OFF, |
427 | | }, |
428 | | { |
429 | | .magic = BCACHEFS_SB_MAGIC, |
430 | | .len = BCACHE_SB_MAGIC_LEN, |
431 | | .kboff = 1 << 11, |
432 | | .sboff = BCACHE_SB_MAGIC_OFF, |
433 | | }, |
434 | | { |
435 | | .magic = BCACHEFS_SB_MAGIC, |
436 | | .len = BCACHE_SB_MAGIC_LEN, |
437 | | .kboff = -(1 << 10), |
438 | | .sboff = BCACHE_SB_MAGIC_OFF, |
439 | | }, |
440 | | { NULL } |
441 | | } |
442 | | }; |