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
14 #include "superblocks.h"
15 #include "crc32c.h"
16 #include "crc64.h"
17 #include "xxhash.h"
18
19 #define SB_LABEL_SIZE 32
20
21 /*
22 * The bcache_super_block is heavily simplified version of struct cache_sb in kernel.
23 * https://github.com/torvalds/linux/blob/master/include/uapi/linux/bcache.h
24 */
25 struct bcache_super_block {
26 uint64_t csum;
27 uint64_t offset; /* where this super block was written */
28 uint64_t version;
29 uint8_t magic[16]; /* bcache file system identifier */
30 uint8_t uuid[16]; /* device identifier */
31 } __attribute__((packed));
32
33 struct bcachefs_sb_field {
34 uint32_t u64s;
35 uint32_t type;
36 } __attribute__((packed));
37
38 struct bcachefs_sb_member {
39 uint8_t uuid[16];
40 uint64_t nbuckets;
41 uint16_t first_bucket;
42 uint16_t bucket_size;
43 uint32_t pad;
44 uint64_t last_mount;
45 uint64_t flags[2];
46 } __attribute__((packed));
47
48 struct bcachefs_sb_field_members {
49 struct bcachefs_sb_field field;
50 struct bcachefs_sb_member members[];
51 } __attribute__((packed));
52
53 enum bcachefs_sb_csum_type {
54 BCACHEFS_SB_CSUM_TYPE_NONE = 0,
55 BCACHEFS_SB_CSUM_TYPE_CRC32C = 1,
56 BCACHEFS_SB_CSUM_TYPE_CRC64 = 2,
57 BCACHEFS_SB_CSUM_TYPE_XXHASH = 7,
58 };
59
60 union bcachefs_sb_csum {
61 uint32_t crc32c;
62 uint64_t crc64;
63 XXH64_hash_t xxh64;
64 uint8_t raw[16];
65 } __attribute__((packed));
66
67 struct bcachefs_super_block {
68 union bcachefs_sb_csum csum;
69 uint16_t version;
70 uint16_t version_min;
71 uint16_t pad[2];
72 uint8_t magic[16];
73 uint8_t uuid[16];
74 uint8_t user_uuid[16];
75 uint8_t label[SB_LABEL_SIZE];
76 uint64_t offset;
77 uint64_t seq;
78 uint16_t block_size;
79 uint8_t dev_idx;
80 uint8_t nr_devices;
81 uint32_t u64s;
82 uint64_t time_base_lo;
83 uint32_t time_base_hi;
84 uint32_t time_precision;
85 uint64_t flags[8];
86 uint64_t features[2];
87 uint64_t compat[2];
88 uint8_t layout[512];
89 struct bcachefs_sb_field _start[];
90 } __attribute__((packed));
91
92 /* magic string */
93 #define BCACHE_SB_MAGIC "\xc6\x85\x73\xf6\x4e\x1a\x45\xca\x82\x65\xf5\x7f\x48\xba\x6d\x81"
94 #define BCACHEFS_SB_MAGIC "\xc6\x85\x73\xf6\x66\xce\x90\xa9\xd9\x6a\x60\xcf\x80\x3d\xf7\xef"
95 /* magic string len */
96 #define BCACHE_SB_MAGIC_LEN (sizeof(BCACHE_SB_MAGIC) - 1)
97 /* super block offset */
98 #define BCACHE_SB_OFF 0x1000
99 /* supper block offset in kB */
100 #define BCACHE_SB_KBOFF (BCACHE_SB_OFF >> 10)
101 /* magic string offset within super block */
102 #define BCACHE_SB_MAGIC_OFF offsetof(struct bcache_super_block, magic)
103 /* start of checksummed data within superblock */
104 #define BCACHE_SB_CSUMMED_START 8
105 /* end of checksummed data within superblock */
106 #define BCACHE_SB_CSUMMED_END 208
107 /* granularity of offset and length fields within superblock */
108 #define BCACHEFS_SECTOR_SIZE 512
109 /* maximum superblock size */
110 #define BCACHEFS_SB_MAX_SIZE 4096
111 /* fields offset within super block */
112 #define BCACHEFS_SB_FIELDS_OFF offsetof(struct bcachefs_super_block, _start)
113 /* tag value for members field */
114 #define BCACHEFS_SB_FIELD_TYPE_MEMBERS 1
115
116 #define BYTES(f) ((((uint64_t) le32_to_cpu((f)->u64s)) * 8))
117
118 static int bcache_verify_checksum(blkid_probe pr, const struct blkid_idmag *mag,
119 const struct bcache_super_block *bcs)
120 {
121 unsigned char *csummed = blkid_probe_get_sb_buffer(pr, mag, BCACHE_SB_CSUMMED_END);
122 uint64_t csum = ul_crc64_we(csummed + BCACHE_SB_CSUMMED_START,
123 BCACHE_SB_CSUMMED_END - BCACHE_SB_CSUMMED_START);
124 return blkid_probe_verify_csum(pr, csum, le64_to_cpu(bcs->csum));
125 }
126
127 static int probe_bcache (blkid_probe pr, const struct blkid_idmag *mag)
128 {
129 struct bcache_super_block *bcs;
130
131 bcs = blkid_probe_get_sb(pr, mag, struct bcache_super_block);
132 if (!bcs)
133 return errno ? -errno : BLKID_PROBE_NONE;
134
135 if (!bcache_verify_checksum(pr, mag, bcs))
136 return BLKID_PROBE_NONE;
137
138 if (le64_to_cpu(bcs->offset) != BCACHE_SB_OFF / 512)
139 return BLKID_PROBE_NONE;
140
141 if (blkid_probe_set_uuid(pr, bcs->uuid) < 0)
142 return BLKID_PROBE_NONE;
143
144 blkid_probe_set_wiper(pr, 0, BCACHE_SB_OFF);
145
146 return BLKID_PROBE_OK;
147 }
148
149 static void probe_bcachefs_sb_members(blkid_probe pr,
150 const struct bcachefs_super_block *bcs,
151 const struct bcachefs_sb_field *field,
152 uint8_t dev_idx)
153 {
154 struct bcachefs_sb_field_members *members =
155 (struct bcachefs_sb_field_members *) field;
156 uint64_t sectors = 0;
157 uint8_t i;
158
159 if (BYTES(field) != offsetof(typeof(*members), members[bcs->nr_devices]))
160 return;
161
162 blkid_probe_set_uuid_as(pr, members->members[dev_idx].uuid, "UUID_SUB");
163
164 for (i = 0; i < bcs->nr_devices; i++) {
165 struct bcachefs_sb_member *member = &members->members[i];
166 sectors += le64_to_cpu(member->nbuckets) * le16_to_cpu(member->bucket_size);
167 }
168 blkid_probe_set_fssize(pr, sectors * BCACHEFS_SECTOR_SIZE);
169 }
170
171 static int is_within_range(void *start, uint64_t size, void *end)
172 {
173 ptrdiff_t diff;
174
175 if (start >= end)
176 return 0; // should not happen
177
178 diff = (unsigned char *) end - (unsigned char *) start;
179 return size <= (uint64_t) diff;
180 }
181
182 static void probe_bcachefs_sb_fields(blkid_probe pr, const struct bcachefs_super_block *bcs,
183 unsigned char *sb_start, unsigned char *sb_end)
184 {
185 unsigned char *field_addr = sb_start + BCACHEFS_SB_FIELDS_OFF;
186
187 while (1) {
188 struct bcachefs_sb_field *field = (struct bcachefs_sb_field *) field_addr;
189 uint64_t field_size;
190 uint32_t type;
191
192 if (!is_within_range(field, sizeof(*field), sb_end))
193 break;
194
195 field_size = BYTES(field);
196
197 if (field_size < sizeof(*field))
198 break;
199
200 if (!is_within_range(field, field_size, sb_end))
201 break;
202
203 type = le32_to_cpu(field->type);
204 if (!type)
205 break;
206
207 if (type == BCACHEFS_SB_FIELD_TYPE_MEMBERS)
208 probe_bcachefs_sb_members(pr, bcs, field, bcs->dev_idx);
209
210 field_addr += BYTES(field);
211 }
212 }
213
214 static int bcachefs_validate_checksum(blkid_probe pr, const struct bcachefs_super_block *bcs,
215 unsigned char *sb, unsigned char *sb_end)
216 {
217 uint8_t checksum_type = be64_to_cpu(bcs->flags[0]) >> 58;
218 unsigned char *checksummed_data_start = sb + sizeof(bcs->csum);
219 size_t checksummed_data_size = sb_end - checksummed_data_start;
220
221 switch (checksum_type) {
222 case BCACHEFS_SB_CSUM_TYPE_NONE:
223 return 1;
224 case BCACHEFS_SB_CSUM_TYPE_CRC32C: {
225 uint32_t crc = crc32c(~0LL, checksummed_data_start, checksummed_data_size) ^ ~0LL;
226 return blkid_probe_verify_csum(pr, crc, le32_to_cpu(bcs->csum.crc32c));
227 }
228 case BCACHEFS_SB_CSUM_TYPE_CRC64: {
229 uint64_t crc = ul_crc64_we(checksummed_data_start, checksummed_data_size);
230 return blkid_probe_verify_csum(pr, crc, le64_to_cpu(bcs->csum.crc64));
231 }
232 case BCACHEFS_SB_CSUM_TYPE_XXHASH: {
233 XXH64_hash_t xxh64 = XXH64(checksummed_data_start, checksummed_data_size, 0);
234 return blkid_probe_verify_csum(pr, xxh64, le64_to_cpu(bcs->csum.xxh64));
235 }
236 default:
237 DBG(LOWPROBE, ul_debug("bcachefs: unknown checksum type %d, ignoring.", checksum_type));
238 return 1;
239 }
240 }
241
242 static int probe_bcachefs(blkid_probe pr, const struct blkid_idmag *mag)
243 {
244 struct bcachefs_super_block *bcs;
245 unsigned char *sb, *sb_end;
246 uint64_t sb_size, blocksize;
247
248 bcs = blkid_probe_get_sb(pr, mag, struct bcachefs_super_block);
249 if (!bcs)
250 return errno ? -errno : BLKID_PROBE_NONE;
251
252 if (le64_to_cpu(bcs->offset) != BCACHE_SB_OFF / BCACHEFS_SECTOR_SIZE)
253 return BLKID_PROBE_NONE;
254
255 if (bcs->nr_devices == 0 || bcs->dev_idx >= bcs->nr_devices)
256 return BLKID_PROBE_NONE;
257
258 sb_size = BCACHEFS_SB_FIELDS_OFF + BYTES(bcs);
259 if (sb_size > BCACHEFS_SB_MAX_SIZE)
260 return BLKID_PROBE_NONE;
261
262 sb = blkid_probe_get_sb_buffer(pr, mag, sb_size);
263 if (!sb)
264 return BLKID_PROBE_NONE;
265 sb_end = sb + sb_size;
266
267 if (!bcachefs_validate_checksum(pr, bcs, sb, sb_end))
268 return BLKID_PROBE_NONE;
269
270 blkid_probe_set_uuid(pr, bcs->user_uuid);
271 blkid_probe_set_label(pr, bcs->label, sizeof(bcs->label));
272 blkid_probe_sprintf_version(pr, "%d", le16_to_cpu(bcs->version));
273 blocksize = le16_to_cpu(bcs->block_size);
274 blkid_probe_set_block_size(pr, blocksize * BCACHEFS_SECTOR_SIZE);
275 blkid_probe_set_fsblocksize(pr, blocksize * BCACHEFS_SECTOR_SIZE);
276 blkid_probe_set_wiper(pr, 0, BCACHE_SB_OFF);
277
278 probe_bcachefs_sb_fields(pr, bcs, sb, sb_end);
279
280 return BLKID_PROBE_OK;
281 }
282
283 const struct blkid_idinfo bcache_idinfo =
284 {
285 .name = "bcache",
286 .usage = BLKID_USAGE_OTHER,
287 .probefunc = probe_bcache,
288 .minsz = 8192,
289 .magics =
290 {
291 {
292 .magic = BCACHE_SB_MAGIC,
293 .len = BCACHE_SB_MAGIC_LEN,
294 .kboff = BCACHE_SB_KBOFF,
295 .sboff = BCACHE_SB_MAGIC_OFF
296 },
297 { NULL }
298 }
299 };
300
301 const struct blkid_idinfo bcachefs_idinfo =
302 {
303 .name = "bcachefs",
304 .usage = BLKID_USAGE_FILESYSTEM,
305 .probefunc = probe_bcachefs,
306 .minsz = 256 * BCACHEFS_SECTOR_SIZE,
307 .magics = {
308 {
309 .magic = BCACHE_SB_MAGIC,
310 .len = BCACHE_SB_MAGIC_LEN,
311 .kboff = BCACHE_SB_KBOFF,
312 .sboff = BCACHE_SB_MAGIC_OFF,
313 },
314 {
315 .magic = BCACHEFS_SB_MAGIC,
316 .len = BCACHE_SB_MAGIC_LEN,
317 .kboff = BCACHE_SB_KBOFF,
318 .sboff = BCACHE_SB_MAGIC_OFF,
319 },
320 { NULL }
321 }
322 };