/src/util-linux/libblkid/src/superblocks/btrfs.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright (C) 2009 Karel Zak <kzak@redhat.com> |
3 | | * |
4 | | * This file may be redistributed under the terms of the |
5 | | * GNU Lesser General Public License. |
6 | | */ |
7 | | #include <stdio.h> |
8 | | #include <stdlib.h> |
9 | | #include <unistd.h> |
10 | | #include <string.h> |
11 | | #include <stdint.h> |
12 | | #include <stdbool.h> |
13 | | #include <assert.h> |
14 | | #include <inttypes.h> |
15 | | |
16 | | #ifdef HAVE_LINUX_BLKZONED_H |
17 | | #include <linux/blkzoned.h> |
18 | | #endif |
19 | | |
20 | | #include "superblocks.h" |
21 | | #include "crc32c.h" |
22 | | #include "sha256.h" |
23 | | #include "xxhash.h" |
24 | | |
25 | | enum btrfs_super_block_csum_type { |
26 | | BTRFS_SUPER_BLOCK_CSUM_TYPE_CRC32C = 0, |
27 | | BTRFS_SUPER_BLOCK_CSUM_TYPE_XXHASH = 1, |
28 | | BTRFS_SUPER_BLOCK_CSUM_TYPE_SHA256 = 2, |
29 | | }; |
30 | | |
31 | | union btrfs_super_block_csum { |
32 | | uint8_t bytes[32]; |
33 | | uint32_t crc32c; |
34 | | XXH64_hash_t xxh64; |
35 | | uint8_t sha256[UL_SHA256LENGTH]; |
36 | | }; |
37 | | |
38 | | struct btrfs_super_block { |
39 | | union btrfs_super_block_csum csum; |
40 | | uint8_t fsid[16]; |
41 | | uint64_t bytenr; |
42 | | uint64_t flags; |
43 | | uint8_t magic[8]; |
44 | | uint64_t generation; |
45 | | uint64_t root; |
46 | | uint64_t chunk_root; |
47 | | uint64_t log_root; |
48 | | uint64_t log_root_transid; |
49 | | uint64_t total_bytes; |
50 | | uint64_t bytes_used; |
51 | | uint64_t root_dir_objectid; |
52 | | uint64_t num_devices; |
53 | | uint32_t sectorsize; |
54 | | uint32_t nodesize; |
55 | | uint32_t leafsize; |
56 | | uint32_t stripesize; |
57 | | uint32_t sys_chunk_array_size; |
58 | | uint64_t chunk_root_generation; |
59 | | uint64_t compat_flags; |
60 | | uint64_t compat_ro_flags; |
61 | | uint64_t incompat_flags; |
62 | | uint16_t csum_type; |
63 | | uint8_t root_level; |
64 | | uint8_t chunk_root_level; |
65 | | uint8_t log_root_level; |
66 | | struct btrfs_dev_item { |
67 | | uint64_t devid; |
68 | | uint64_t total_bytes; |
69 | | uint64_t bytes_used; |
70 | | uint32_t io_align; |
71 | | uint32_t io_width; |
72 | | uint32_t sector_size; |
73 | | uint64_t type; |
74 | | uint64_t generation; |
75 | | uint64_t start_offset; |
76 | | uint32_t dev_group; |
77 | | uint8_t seek_speed; |
78 | | uint8_t bandwidth; |
79 | | uint8_t uuid[16]; |
80 | | uint8_t fsid[16]; |
81 | | } __attribute__ ((__packed__)) dev_item; |
82 | | uint8_t label[256]; |
83 | | uint8_t padding[3541]; /* pad to BTRFS_SUPER_INFO_SIZE for csum calculation */ |
84 | | } __attribute__ ((__packed__)); |
85 | | |
86 | 0 | #define BTRFS_SUPER_INFO_SIZE 4096 |
87 | | |
88 | | /* Number of superblock log zones */ |
89 | 0 | #define BTRFS_NR_SB_LOG_ZONES 2 |
90 | | |
91 | | /* Introduce some macros and types to unify the code with kernel side */ |
92 | 0 | #define SECTOR_SHIFT 9 |
93 | | |
94 | | typedef uint64_t sector_t; |
95 | | |
96 | | #ifdef HAVE_LINUX_BLKZONED_H |
97 | | static int sb_write_pointer(blkid_probe pr, struct blk_zone *zones, uint64_t *wp_ret) |
98 | 0 | { |
99 | 0 | bool empty[BTRFS_NR_SB_LOG_ZONES]; |
100 | 0 | bool full[BTRFS_NR_SB_LOG_ZONES]; |
101 | 0 | sector_t sector; |
102 | |
|
103 | 0 | assert(zones[0].type != BLK_ZONE_TYPE_CONVENTIONAL && |
104 | 0 | zones[1].type != BLK_ZONE_TYPE_CONVENTIONAL); |
105 | |
|
106 | 0 | empty[0] = zones[0].cond == BLK_ZONE_COND_EMPTY; |
107 | 0 | empty[1] = zones[1].cond == BLK_ZONE_COND_EMPTY; |
108 | 0 | full[0] = zones[0].cond == BLK_ZONE_COND_FULL; |
109 | 0 | full[1] = zones[1].cond == BLK_ZONE_COND_FULL; |
110 | | |
111 | | /* |
112 | | * Possible states of log buffer zones |
113 | | * |
114 | | * Empty[0] In use[0] Full[0] |
115 | | * Empty[1] * x 0 |
116 | | * In use[1] 0 x 0 |
117 | | * Full[1] 1 1 C |
118 | | * |
119 | | * Log position: |
120 | | * *: Special case, no superblock is written |
121 | | * 0: Use write pointer of zones[0] |
122 | | * 1: Use write pointer of zones[1] |
123 | | * C: Compare super blocks from zones[0] and zones[1], use the latest |
124 | | * one determined by generation |
125 | | * x: Invalid state |
126 | | */ |
127 | |
|
128 | 0 | if (empty[0] && empty[1]) { |
129 | | /* Special case to distinguish no superblock to read */ |
130 | 0 | *wp_ret = zones[0].start << SECTOR_SHIFT; |
131 | 0 | return -ENOENT; |
132 | 0 | } else if (full[0] && full[1]) { |
133 | | /* Compare two super blocks */ |
134 | 0 | struct btrfs_super_block *super[BTRFS_NR_SB_LOG_ZONES]; |
135 | 0 | int i; |
136 | |
|
137 | 0 | for (i = 0; i < BTRFS_NR_SB_LOG_ZONES; i++) { |
138 | 0 | uint64_t bytenr; |
139 | |
|
140 | 0 | bytenr = ((zones[i].start + zones[i].len) |
141 | 0 | << SECTOR_SHIFT) - BTRFS_SUPER_INFO_SIZE; |
142 | |
|
143 | 0 | super[i] = (struct btrfs_super_block *) |
144 | 0 | blkid_probe_get_buffer(pr, bytenr, BTRFS_SUPER_INFO_SIZE); |
145 | 0 | if (!super[i]) |
146 | 0 | return -EIO; |
147 | 0 | DBG(LOWPROBE, ul_debug("(btrfs) checking #%d zone " |
148 | 0 | "[start=%" PRIu64", len=%" PRIu64", sb-offset=%" PRIu64"]", |
149 | 0 | i, (uint64_t) zones[i].start, |
150 | 0 | (uint64_t) zones[i].len, bytenr)); |
151 | 0 | } |
152 | | |
153 | 0 | if (super[0]->generation > super[1]->generation) |
154 | 0 | sector = zones[1].start; |
155 | 0 | else |
156 | 0 | sector = zones[0].start; |
157 | 0 | } else if (!full[0] && (empty[1] || full[1])) { |
158 | 0 | sector = zones[0].wp; |
159 | 0 | } else if (full[0]) { |
160 | 0 | sector = zones[1].wp; |
161 | 0 | } else { |
162 | 0 | return -EUCLEAN; |
163 | 0 | } |
164 | 0 | *wp_ret = sector << SECTOR_SHIFT; |
165 | |
|
166 | 0 | DBG(LOWPROBE, ul_debug("(btrfs) write pointer: %" PRIu64" sector", sector)); |
167 | 0 | return 0; |
168 | 0 | } |
169 | | |
170 | | static int sb_log_offset(blkid_probe pr, uint64_t *bytenr_ret) |
171 | 0 | { |
172 | 0 | uint32_t zone_num = 0; |
173 | 0 | uint32_t zone_size_sector; |
174 | 0 | struct blk_zone_report *rep; |
175 | 0 | struct blk_zone *zones; |
176 | 0 | int ret; |
177 | 0 | int i; |
178 | 0 | uint64_t wp; |
179 | | |
180 | |
|
181 | 0 | zone_size_sector = pr->zone_size >> SECTOR_SHIFT; |
182 | 0 | rep = blkdev_get_zonereport(pr->fd, zone_num * zone_size_sector, 2); |
183 | 0 | if (!rep) { |
184 | 0 | ret = -errno; |
185 | 0 | goto out; |
186 | 0 | } |
187 | 0 | zones = (struct blk_zone *)(rep + 1); |
188 | | |
189 | | /* |
190 | | * Use the head of the first conventional zone, if the zones |
191 | | * contain one. |
192 | | */ |
193 | 0 | for (i = 0; i < BTRFS_NR_SB_LOG_ZONES; i++) { |
194 | 0 | if (zones[i].type == BLK_ZONE_TYPE_CONVENTIONAL) { |
195 | 0 | DBG(LOWPROBE, ul_debug("(btrfs) checking conventional zone")); |
196 | 0 | *bytenr_ret = zones[i].start << SECTOR_SHIFT; |
197 | 0 | ret = 0; |
198 | 0 | goto out; |
199 | 0 | } |
200 | 0 | } |
201 | | |
202 | 0 | ret = sb_write_pointer(pr, zones, &wp); |
203 | 0 | if (ret != -ENOENT && ret) { |
204 | 0 | ret = 1; |
205 | 0 | goto out; |
206 | 0 | } |
207 | 0 | if (ret != -ENOENT) { |
208 | 0 | if (wp == zones[0].start << SECTOR_SHIFT) |
209 | 0 | wp = (zones[1].start + zones[1].len) << SECTOR_SHIFT; |
210 | 0 | wp -= BTRFS_SUPER_INFO_SIZE; |
211 | 0 | } |
212 | 0 | *bytenr_ret = wp; |
213 | |
|
214 | 0 | ret = 0; |
215 | 0 | out: |
216 | 0 | free(rep); |
217 | |
|
218 | 0 | return ret; |
219 | 0 | } |
220 | | #endif |
221 | | |
222 | | static int btrfs_verify_csum(blkid_probe pr, const struct btrfs_super_block *bfs) |
223 | 0 | { |
224 | 0 | uint16_t csum_type = le16_to_cpu(bfs->csum_type); |
225 | 0 | const void *csum_data = (char *) bfs + sizeof(bfs->csum); |
226 | 0 | size_t csum_data_size = sizeof(*bfs) - sizeof(bfs->csum); |
227 | 0 | switch (csum_type) { |
228 | 0 | case BTRFS_SUPER_BLOCK_CSUM_TYPE_CRC32C: { |
229 | 0 | uint32_t crc = ~crc32c(~0L, csum_data, csum_data_size); |
230 | 0 | return blkid_probe_verify_csum(pr, crc, |
231 | 0 | le32_to_cpu(bfs->csum.crc32c)); |
232 | 0 | } |
233 | 0 | case BTRFS_SUPER_BLOCK_CSUM_TYPE_XXHASH: { |
234 | 0 | XXH64_hash_t xxh64 = XXH64(csum_data, csum_data_size, 0); |
235 | 0 | return blkid_probe_verify_csum(pr, xxh64, |
236 | 0 | le64_to_cpu(bfs->csum.xxh64)); |
237 | 0 | } |
238 | 0 | case BTRFS_SUPER_BLOCK_CSUM_TYPE_SHA256: { |
239 | 0 | uint8_t sha256[UL_SHA256LENGTH]; |
240 | 0 | ul_SHA256(sha256, csum_data, csum_data_size); |
241 | 0 | return blkid_probe_verify_csum_buf(pr, UL_SHA256LENGTH, |
242 | 0 | sha256, bfs->csum.sha256); |
243 | 0 | } |
244 | 0 | default: |
245 | 0 | DBG(LOWPROBE, ul_debug("(btrfs) unknown checksum type %d, skipping validation", |
246 | 0 | csum_type)); |
247 | 0 | return 1; |
248 | 0 | } |
249 | 0 | } |
250 | | |
251 | | static int probe_btrfs(blkid_probe pr, const struct blkid_idmag *mag) |
252 | 0 | { |
253 | 0 | const struct btrfs_super_block *bfs; |
254 | |
|
255 | 0 | if (pr->zone_size) { |
256 | 0 | #ifdef HAVE_LINUX_BLKZONED_H |
257 | 0 | uint64_t offset = 0; |
258 | 0 | int ret; |
259 | |
|
260 | 0 | ret = sb_log_offset(pr, &offset); |
261 | 0 | if (ret) |
262 | 0 | return ret; |
263 | 0 | bfs = (struct btrfs_super_block *) |
264 | 0 | blkid_probe_get_buffer(pr, offset, |
265 | 0 | sizeof(struct btrfs_super_block)); |
266 | | #else |
267 | | /* Nothing can be done */ |
268 | | return 1; |
269 | | #endif |
270 | 0 | } else { |
271 | 0 | bfs = blkid_probe_get_sb(pr, mag, struct btrfs_super_block); |
272 | 0 | } |
273 | 0 | if (!bfs) |
274 | 0 | return errno ? -errno : 1; |
275 | | |
276 | 0 | if (!btrfs_verify_csum(pr, bfs)) |
277 | 0 | return 1; |
278 | | |
279 | | /* Invalid sector size; total_bytes would be bogus. */ |
280 | 0 | if (!le32_to_cpu(bfs->sectorsize)) |
281 | 0 | return 1; |
282 | | |
283 | 0 | if (*bfs->label) |
284 | 0 | blkid_probe_set_label(pr, |
285 | 0 | (unsigned char *) bfs->label, |
286 | 0 | sizeof(bfs->label)); |
287 | |
|
288 | 0 | blkid_probe_set_uuid(pr, bfs->fsid); |
289 | 0 | blkid_probe_set_uuid_as(pr, bfs->dev_item.uuid, "UUID_SUB"); |
290 | 0 | blkid_probe_set_fsblocksize(pr, le32_to_cpu(bfs->sectorsize)); |
291 | 0 | blkid_probe_set_block_size(pr, le32_to_cpu(bfs->sectorsize)); |
292 | |
|
293 | 0 | uint32_t sectorsize_log = 31 - |
294 | 0 | __builtin_clz(le32_to_cpu(bfs->sectorsize)); |
295 | 0 | blkid_probe_set_fslastblock(pr, |
296 | 0 | le64_to_cpu(bfs->total_bytes) >> sectorsize_log); |
297 | | |
298 | | /* The size is calculated without the RAID factor. It could not be |
299 | | * obtained from the superblock as it is property of device tree. |
300 | | * Without the factor we would show fs size with the redundant data. The |
301 | | * acquisition of the factor will require additional parsing of btrfs |
302 | | * tree. |
303 | | */ |
304 | 0 | blkid_probe_set_fssize(pr, le64_to_cpu(bfs->total_bytes)); |
305 | |
|
306 | 0 | return 0; |
307 | 0 | } |
308 | | |
309 | | const struct blkid_idinfo btrfs_idinfo = |
310 | | { |
311 | | .name = "btrfs", |
312 | | .usage = BLKID_USAGE_FILESYSTEM, |
313 | | .probefunc = probe_btrfs, |
314 | | .minsz = 1024 * 1024, |
315 | | .magics = |
316 | | { |
317 | | { .magic = "_BHRfS_M", .len = 8, .sboff = 0x40, .kboff = 64 }, |
318 | | /* For zoned btrfs */ |
319 | | { .magic = "_BHRfS_M", .len = 8, .sboff = 0x40, |
320 | | .is_zoned = 1, .zonenum = 0, .kboff_inzone = 0 }, |
321 | | { .magic = "_BHRfS_M", .len = 8, .sboff = 0x40, |
322 | | .is_zoned = 1, .zonenum = 1, .kboff_inzone = 0 }, |
323 | | { NULL } |
324 | | } |
325 | | }; |
326 | | |