]>
Commit | Line | Data |
---|---|---|
4dfbc574 RM |
1 | /* btrfs.c - B-tree file system. */ |
2 | /* | |
3 | * GRUB -- GRand Unified Bootloader | |
4 | * Copyright (C) 2010 Free Software Foundation, Inc. | |
5 | * | |
6 | * GRUB is free software: you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License as published by | |
8 | * the Free Software Foundation, either version 3 of the License, or | |
9 | * (at your option) any later version. | |
10 | * | |
11 | * GRUB is distributed in the hope that it will be useful, | |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | * GNU General Public License for more details. | |
15 | * | |
16 | * You should have received a copy of the GNU General Public License | |
17 | * along with GRUB. If not, see <http://www.gnu.org/licenses/>. | |
18 | */ | |
19 | ||
20 | #include <grub/err.h> | |
21 | #include <grub/file.h> | |
22 | #include <grub/mm.h> | |
23 | #include <grub/misc.h> | |
24 | #include <grub/disk.h> | |
25 | #include <grub/dl.h> | |
26 | #include <grub/types.h> | |
b18610fe | 27 | #include <grub/lib/crc.h> |
3be8e5ea | 28 | #include <grub/deflate.h> |
4dfbc574 | 29 | |
0e761d3d | 30 | #define GRUB_BTRFS_SIGNATURE "_BHRfS_M" |
4dfbc574 | 31 | |
0e761d3d VS |
32 | typedef grub_uint8_t grub_btrfs_checksum_t[0x20]; |
33 | typedef grub_uint16_t grub_btrfs_uuid_t[8]; | |
b18610fe VS |
34 | |
35 | struct grub_btrfs_device | |
36 | { | |
37 | grub_uint64_t device_id; | |
38 | grub_uint8_t dummy[0x62 - 8]; | |
39 | } __attribute__ ((packed)); | |
40 | ||
0e761d3d | 41 | struct grub_btrfs_superblock |
4dfbc574 | 42 | { |
0e761d3d VS |
43 | grub_btrfs_checksum_t checksum; |
44 | grub_btrfs_uuid_t uuid; | |
b18610fe | 45 | grub_uint8_t dummy[0x10]; |
0e761d3d | 46 | grub_uint8_t signature[sizeof (GRUB_BTRFS_SIGNATURE) - 1]; |
b18610fe VS |
47 | grub_uint64_t generation; |
48 | grub_uint64_t root_tree; | |
49 | grub_uint64_t chunk_tree; | |
50 | grub_uint8_t dummy2[0x20]; | |
51 | grub_uint64_t root_dir_objectid; | |
52 | grub_uint8_t dummy3[0x41]; | |
db51e201 | 53 | struct grub_btrfs_device this_device; |
b18610fe VS |
54 | char label[0x100]; |
55 | grub_uint8_t dummy4[0x100]; | |
56 | grub_uint8_t bootstrap_mapping[0x800]; | |
57 | } __attribute__ ((packed)); | |
58 | ||
59 | struct btrfs_header | |
60 | { | |
0e761d3d VS |
61 | grub_btrfs_checksum_t checksum; |
62 | grub_btrfs_uuid_t uuid; | |
b18610fe VS |
63 | grub_uint8_t dummy[0x30]; |
64 | grub_uint32_t nitems; | |
65 | grub_uint8_t level; | |
4dfbc574 RM |
66 | } __attribute__ ((packed)); |
67 | ||
db51e201 VS |
68 | struct grub_btrfs_device_desc |
69 | { | |
70 | grub_device_t dev; | |
71 | grub_uint64_t id; | |
72 | }; | |
73 | ||
4dfbc574 RM |
74 | struct grub_btrfs_data |
75 | { | |
0e761d3d | 76 | struct grub_btrfs_superblock sblock; |
b18610fe VS |
77 | grub_uint64_t tree; |
78 | grub_uint64_t inode; | |
34018a7d | 79 | |
db51e201 VS |
80 | struct grub_btrfs_device_desc *devices_attached; |
81 | unsigned n_devices_attached; | |
82 | unsigned n_devices_allocated; | |
83 | ||
34018a7d | 84 | /* Cached extent data. */ |
a43c4bc5 | 85 | grub_uint64_t extstart; |
6a01f54a | 86 | grub_uint64_t extend; |
34018a7d VS |
87 | grub_uint64_t extino; |
88 | grub_uint64_t exttree; | |
3be8e5ea | 89 | grub_size_t extsize; |
a43c4bc5 | 90 | struct grub_btrfs_extent_data *extent; |
4dfbc574 RM |
91 | }; |
92 | ||
b18610fe VS |
93 | struct grub_btrfs_key |
94 | { | |
95 | grub_uint64_t object_id; | |
96 | #define GRUB_BTRFS_ITEM_TYPE_INODE_ITEM 0x01 | |
97 | #define GRUB_BTRFS_ITEM_TYPE_DIR_ITEM 0x54 | |
98 | #define GRUB_BTRFS_ITEM_TYPE_EXTENT_ITEM 0x6c | |
99 | #define GRUB_BTRFS_ITEM_TYPE_ROOT_ITEM 0x84 | |
100 | #define GRUB_BTRFS_ITEM_TYPE_DEVICE 0xd8 | |
101 | #define GRUB_BTRFS_ITEM_TYPE_CHUNK 0xe4 | |
102 | grub_uint8_t type; | |
103 | grub_uint64_t offset; | |
104 | } __attribute__ ((packed)); | |
105 | ||
106 | struct grub_btrfs_chunk_item | |
107 | { | |
108 | grub_uint64_t size; | |
109 | grub_uint64_t dummy; | |
110 | grub_uint64_t stripe_length; | |
db51e201 VS |
111 | grub_uint64_t type; |
112 | #define GRUB_BTRFS_CHUNK_TYPE_BITS_DONTCARE 0x07 | |
113 | #define GRUB_BTRFS_CHUNK_TYPE_SINGLE 0x00 | |
114 | #define GRUB_BTRFS_CHUNK_TYPE_RAID0 0x08 | |
115 | #define GRUB_BTRFS_CHUNK_TYPE_RAID1 0x10 | |
116 | #define GRUB_BTRFS_CHUNK_TYPE_DUPLICATED 0x20 | |
6333f1e9 | 117 | #define GRUB_BTRFS_CHUNK_TYPE_RAID10 0x40 |
db51e201 | 118 | grub_uint8_t dummy2[0xc]; |
b18610fe | 119 | grub_uint16_t nstripes; |
db51e201 | 120 | grub_uint16_t nsubstripes; |
b18610fe VS |
121 | } __attribute__ ((packed)); |
122 | ||
123 | struct grub_btrfs_chunk_stripe | |
124 | { | |
125 | grub_uint64_t device_id; | |
126 | grub_uint64_t offset; | |
0e761d3d | 127 | grub_btrfs_uuid_t device_uuid; |
b18610fe VS |
128 | } __attribute__ ((packed)); |
129 | ||
130 | struct grub_btrfs_leaf_node | |
131 | { | |
132 | struct grub_btrfs_key key; | |
133 | grub_uint32_t offset; | |
134 | grub_uint32_t size; | |
135 | } __attribute__ ((packed)); | |
136 | ||
355b3eed VS |
137 | struct grub_btrfs_internal_node |
138 | { | |
139 | struct grub_btrfs_key key; | |
0e761d3d | 140 | grub_uint64_t addr; |
355b3eed VS |
141 | grub_uint64_t dummy; |
142 | } __attribute__ ((packed)); | |
143 | ||
b18610fe VS |
144 | struct grub_btrfs_dir_item |
145 | { | |
146 | struct grub_btrfs_key key; | |
147 | grub_uint8_t dummy[8]; | |
148 | grub_uint16_t m; | |
149 | grub_uint16_t n; | |
df80cd06 VS |
150 | #define GRUB_BTRFS_DIR_ITEM_TYPE_REGULAR 1 |
151 | #define GRUB_BTRFS_DIR_ITEM_TYPE_DIRECTORY 2 | |
152 | #define GRUB_BTRFS_DIR_ITEM_TYPE_SYMLINK 7 | |
b18610fe VS |
153 | grub_uint8_t type; |
154 | char name[0]; | |
155 | } __attribute__ ((packed)); | |
156 | ||
157 | struct grub_btrfs_leaf_descriptor | |
158 | { | |
159 | unsigned depth; | |
160 | unsigned allocated; | |
161 | struct { | |
162 | grub_disk_addr_t addr; | |
163 | unsigned iter; | |
164 | unsigned maxiter; | |
165 | int leaf; | |
166 | } *data; | |
167 | }; | |
168 | ||
169 | struct grub_btrfs_root_item | |
170 | { | |
171 | grub_uint8_t dummy[0xb0]; | |
172 | grub_uint64_t tree; | |
173 | grub_uint64_t inode; | |
174 | }; | |
175 | ||
d6f07b29 VS |
176 | struct grub_btrfs_time |
177 | { | |
178 | grub_int64_t sec; | |
179 | grub_uint32_t nanosec; | |
180 | } __attribute__ ((aligned(4))); | |
181 | ||
b18610fe VS |
182 | struct grub_btrfs_inode |
183 | { | |
d6f07b29 | 184 | grub_uint8_t dummy1[0x10]; |
b18610fe | 185 | grub_uint64_t size; |
d6f07b29 VS |
186 | grub_uint8_t dummy2[0x70]; |
187 | struct grub_btrfs_time mtime; | |
b18610fe VS |
188 | } __attribute__ ((packed)); |
189 | ||
190 | struct grub_btrfs_extent_data | |
191 | { | |
192 | grub_uint64_t dummy; | |
193 | grub_uint64_t size; | |
194 | grub_uint8_t compression; | |
195 | grub_uint8_t encryption; | |
196 | grub_uint16_t encoding; | |
197 | grub_uint8_t type; | |
198 | union | |
199 | { | |
200 | char inl[0]; | |
3be8e5ea VS |
201 | struct |
202 | { | |
203 | grub_uint64_t laddr; | |
204 | grub_uint64_t compressed_size; | |
205 | grub_uint64_t offset; | |
6a01f54a | 206 | grub_uint64_t filled; |
3be8e5ea | 207 | }; |
b18610fe VS |
208 | }; |
209 | } __attribute__ ((packed)); | |
210 | ||
211 | #define GRUB_BTRFS_EXTENT_INLINE 0 | |
212 | #define GRUB_BTRFS_EXTENT_REGULAR 1 | |
213 | ||
3be8e5ea VS |
214 | #define GRUB_BTRFS_COMPRESSION_NONE 0 |
215 | #define GRUB_BTRFS_COMPRESSION_ZLIB 1 | |
b18610fe VS |
216 | |
217 | #define GRUB_BTRFS_OBJECT_ID_CHUNK 0x100 | |
218 | ||
219 | static grub_disk_addr_t superblock_sectors[] = { 64 * 2, 64 * 1024 * 2, | |
220 | 256 * 1048576 * 2, | |
221 | 1048576ULL * 1048576ULL * 2 }; | |
222 | ||
223 | static grub_err_t | |
224 | grub_btrfs_read_logical (struct grub_btrfs_data *data, | |
db51e201 VS |
225 | grub_disk_addr_t addr, void *buf, grub_size_t size); |
226 | ||
227 | static grub_err_t | |
228 | read_sblock (grub_disk_t disk, struct grub_btrfs_superblock *sb) | |
229 | { | |
230 | unsigned i; | |
231 | grub_err_t err = GRUB_ERR_NONE; | |
232 | for (i = 0; i < ARRAY_SIZE (superblock_sectors); i++) | |
233 | { | |
234 | struct grub_btrfs_superblock sblock; | |
235 | err = grub_disk_read (disk, superblock_sectors[i], 0, | |
236 | sizeof (sblock), &sblock); | |
237 | if (err == GRUB_ERR_OUT_OF_RANGE) | |
238 | break; | |
239 | ||
240 | if (grub_memcmp ((char *) sblock.signature, GRUB_BTRFS_SIGNATURE, | |
241 | sizeof (GRUB_BTRFS_SIGNATURE) - 1) != 0) | |
242 | break; | |
243 | if (i == 0 || grub_le_to_cpu64 (sblock.generation) | |
244 | > grub_le_to_cpu64 (sb->generation)) | |
245 | grub_memcpy (sb, &sblock, sizeof (sblock)); | |
246 | } | |
247 | ||
248 | if ((err == GRUB_ERR_OUT_OF_RANGE || !err) && i == 0) | |
249 | return grub_error (GRUB_ERR_BAD_FS, "not a Btrfs filesystem"); | |
250 | ||
251 | if (err == GRUB_ERR_OUT_OF_RANGE) | |
252 | grub_errno = err = GRUB_ERR_NONE; | |
253 | ||
254 | return err; | |
255 | } | |
b18610fe VS |
256 | |
257 | static int | |
258 | key_cmp (const struct grub_btrfs_key *a, const struct grub_btrfs_key *b) | |
259 | { | |
260 | if (grub_cpu_to_le64 (a->object_id) < grub_cpu_to_le64 (b->object_id)) | |
261 | return -1; | |
262 | if (grub_cpu_to_le64 (a->object_id) > grub_cpu_to_le64 (b->object_id)) | |
263 | return +1; | |
264 | ||
265 | if (a->type < b->type) | |
266 | return -1; | |
267 | if (a->type > b->type) | |
268 | return +1; | |
269 | ||
270 | if (grub_cpu_to_le64 (a->offset) < grub_cpu_to_le64 (b->offset)) | |
271 | return -1; | |
272 | if (grub_cpu_to_le64 (a->offset) > grub_cpu_to_le64 (b->offset)) | |
273 | return +1; | |
274 | return 0; | |
275 | } | |
276 | ||
277 | static void | |
278 | free_iterator (struct grub_btrfs_leaf_descriptor *desc) | |
279 | { | |
280 | grub_free (desc->data); | |
281 | } | |
282 | ||
355b3eed VS |
283 | static grub_err_t |
284 | save_ref (struct grub_btrfs_leaf_descriptor *desc, | |
285 | grub_disk_addr_t addr, unsigned i, unsigned m, int l) | |
286 | { | |
287 | desc->depth++; | |
98042add | 288 | if (desc->allocated < desc->depth) |
355b3eed VS |
289 | { |
290 | void *newdata; | |
291 | desc->allocated *= 2; | |
292 | newdata = grub_realloc (desc->data, sizeof (desc->data[0]) | |
293 | * desc->allocated); | |
294 | if (!newdata) | |
295 | return grub_errno; | |
296 | desc->data = newdata; | |
297 | } | |
298 | desc->data[desc->depth - 1].addr = addr; | |
299 | desc->data[desc->depth - 1].iter = i; | |
300 | desc->data[desc->depth - 1].maxiter = m; | |
301 | desc->data[desc->depth - 1].leaf = l; | |
302 | return GRUB_ERR_NONE; | |
303 | } | |
304 | ||
b18610fe | 305 | static int |
db51e201 | 306 | next (struct grub_btrfs_data *data, |
b18610fe VS |
307 | struct grub_btrfs_leaf_descriptor *desc, |
308 | grub_disk_addr_t *outaddr, grub_size_t *outsize, | |
309 | struct grub_btrfs_key *key_out) | |
310 | { | |
b18610fe VS |
311 | grub_err_t err; |
312 | struct grub_btrfs_leaf_node leaf; | |
313 | ||
98042add | 314 | for (; desc->depth > 0; desc->depth--) |
b18610fe | 315 | { |
98042add VS |
316 | desc->data[desc->depth - 1].iter++; |
317 | if (desc->data[desc->depth - 1].iter | |
b18610fe VS |
318 | < desc->data[desc->depth - 1].maxiter) |
319 | break; | |
b18610fe | 320 | } |
98042add | 321 | if (desc->depth == 0) |
b18610fe VS |
322 | return 0; |
323 | while (!desc->data[desc->depth - 1].leaf) | |
324 | { | |
355b3eed VS |
325 | struct grub_btrfs_internal_node node; |
326 | struct btrfs_header head; | |
327 | ||
db51e201 | 328 | err = grub_btrfs_read_logical (data, desc->data[desc->depth - 1].iter |
355b3eed VS |
329 | * sizeof (node) |
330 | + sizeof (struct btrfs_header) | |
331 | + desc->data[desc->depth - 1].addr, &node, | |
332 | sizeof (node)); | |
333 | if (err) | |
334 | return -err; | |
335 | ||
db51e201 | 336 | err = grub_btrfs_read_logical (data, grub_le_to_cpu64 (node.addr), &head, |
355b3eed VS |
337 | sizeof (head)); |
338 | if (err) | |
339 | return -err; | |
340 | ||
0e761d3d | 341 | save_ref (desc, grub_le_to_cpu64 (node.addr), 0, |
355b3eed | 342 | grub_le_to_cpu32 (head.nitems), !head.level); |
b18610fe | 343 | } |
db51e201 | 344 | err = grub_btrfs_read_logical (data, desc->data[desc->depth - 1].iter |
b18610fe VS |
345 | * sizeof (leaf) |
346 | + sizeof (struct btrfs_header) | |
347 | + desc->data[desc->depth - 1].addr, &leaf, | |
348 | sizeof (leaf)); | |
349 | if (err) | |
350 | return -err; | |
351 | *outsize = grub_le_to_cpu32 (leaf.size); | |
352 | *outaddr = desc->data[desc->depth - 1].addr + sizeof (struct btrfs_header) | |
353 | + grub_le_to_cpu32 (leaf.offset); | |
354 | *key_out = leaf.key; | |
355 | return 1; | |
356 | } | |
357 | ||
b18610fe | 358 | static grub_err_t |
db51e201 | 359 | lower_bound (struct grub_btrfs_data *data, |
b18610fe VS |
360 | const struct grub_btrfs_key *key_in, |
361 | struct grub_btrfs_key *key_out, | |
362 | grub_disk_addr_t root, | |
363 | grub_disk_addr_t *outaddr, grub_size_t *outsize, | |
364 | struct grub_btrfs_leaf_descriptor *desc) | |
365 | { | |
366 | grub_disk_addr_t addr = root; | |
355b3eed | 367 | int depth = -1; |
b18610fe VS |
368 | |
369 | if (desc) | |
370 | { | |
371 | desc->allocated = 16; | |
372 | desc->depth = 0; | |
373 | desc->data = grub_malloc (sizeof (desc->data[0]) * desc->allocated); | |
374 | if (!desc->data) | |
375 | return grub_errno; | |
376 | } | |
377 | ||
355b3eed VS |
378 | grub_dprintf ("btrfs", |
379 | "retrieving %" PRIxGRUB_UINT64_T | |
380 | " %x %" PRIxGRUB_UINT64_T "\n", | |
381 | key_in->object_id, key_in->type, key_in->offset); | |
382 | ||
b18610fe VS |
383 | while (1) |
384 | { | |
355b3eed VS |
385 | grub_err_t err; |
386 | struct btrfs_header head; | |
387 | ||
388 | reiter: | |
389 | depth++; | |
390 | /* FIXME: preread few nodes into buffer. */ | |
db51e201 | 391 | err = grub_btrfs_read_logical (data, addr, &head, sizeof (head)); |
b18610fe VS |
392 | if (err) |
393 | return err; | |
b18610fe | 394 | addr += sizeof (head); |
355b3eed | 395 | if (head.level) |
b18610fe | 396 | { |
355b3eed VS |
397 | unsigned i; |
398 | struct grub_btrfs_internal_node node, node_last; | |
399 | int have_last = 0; | |
ac5dcabe | 400 | grub_memset (&node_last, 0, sizeof (node_last)); |
355b3eed | 401 | for (i = 0; i < grub_le_to_cpu32 (head.nitems); i++) |
b18610fe | 402 | { |
db51e201 | 403 | err = grub_btrfs_read_logical (data, addr + i * sizeof (node), |
355b3eed VS |
404 | &node, sizeof (node)); |
405 | if (err) | |
406 | return err; | |
407 | ||
408 | grub_dprintf ("btrfs", | |
409 | "internal node (depth %d) %" PRIxGRUB_UINT64_T | |
410 | " %x %" PRIxGRUB_UINT64_T "\n", depth, | |
411 | node.key.object_id, node.key.type, node.key.offset); | |
412 | ||
413 | if (key_cmp (&node.key, key_in) == 0) | |
414 | { | |
415 | err = GRUB_ERR_NONE; | |
416 | if (desc) | |
417 | err = save_ref (desc, addr - sizeof (head), i, | |
418 | grub_le_to_cpu32 (head.nitems), 0); | |
419 | if (err) | |
420 | return err; | |
0e761d3d | 421 | addr = grub_le_to_cpu64 (node.addr); |
355b3eed VS |
422 | goto reiter; |
423 | } | |
424 | if (key_cmp (&node.key, key_in) > 0) | |
425 | break; | |
426 | node_last = node; | |
427 | have_last = 1; | |
428 | } | |
429 | if (have_last) | |
430 | { | |
355b3eed | 431 | err = GRUB_ERR_NONE; |
b18610fe | 432 | if (desc) |
355b3eed VS |
433 | err = save_ref (desc, addr - sizeof (head), i - 1, |
434 | grub_le_to_cpu32 (head.nitems), 0); | |
435 | if (err) | |
436 | return err; | |
0e761d3d | 437 | addr = grub_le_to_cpu64 (node_last.addr); |
355b3eed | 438 | goto reiter; |
b18610fe | 439 | } |
355b3eed VS |
440 | *outsize = 0; |
441 | *outaddr = 0; | |
442 | grub_memset (key_out, 0, sizeof (*key_out)); | |
b18610fe | 443 | if (desc) |
355b3eed VS |
444 | return save_ref (desc, addr - sizeof (head), -1, |
445 | grub_le_to_cpu32 (head.nitems), 0); | |
446 | return GRUB_ERR_NONE; | |
b18610fe | 447 | } |
355b3eed VS |
448 | { |
449 | unsigned i; | |
450 | struct grub_btrfs_leaf_node leaf, leaf_last; | |
451 | int have_last = 0; | |
452 | for (i = 0; i < grub_le_to_cpu32 (head.nitems); i++) | |
453 | { | |
db51e201 | 454 | err = grub_btrfs_read_logical (data, addr + i * sizeof (leaf), |
355b3eed VS |
455 | &leaf, sizeof (leaf)); |
456 | if (err) | |
457 | return err; | |
458 | ||
459 | grub_dprintf ("btrfs", | |
460 | "leaf (depth %d) %" PRIxGRUB_UINT64_T | |
461 | " %x %" PRIxGRUB_UINT64_T "\n", depth, | |
462 | leaf.key.object_id, leaf.key.type, leaf.key.offset); | |
463 | ||
464 | if (key_cmp (&leaf.key, key_in) == 0) | |
465 | { | |
466 | grub_memcpy (key_out, &leaf.key, sizeof(*key_out)); | |
467 | *outsize = grub_le_to_cpu32 (leaf.size); | |
468 | *outaddr = addr + grub_le_to_cpu32 (leaf.offset); | |
469 | if (desc) | |
470 | return save_ref (desc, addr - sizeof (head), i, | |
471 | grub_le_to_cpu32 (head.nitems), 1); | |
472 | return GRUB_ERR_NONE; | |
473 | } | |
474 | ||
475 | if (key_cmp (&leaf.key, key_in) > 0) | |
476 | break; | |
477 | ||
478 | have_last = 1; | |
479 | leaf_last = leaf; | |
480 | } | |
481 | ||
482 | if (have_last) | |
483 | { | |
484 | grub_memcpy (key_out, &leaf_last.key, sizeof(*key_out)); | |
485 | *outsize = grub_le_to_cpu32 (leaf_last.size); | |
486 | *outaddr = addr + grub_le_to_cpu32 (leaf_last.offset); | |
487 | if (desc) | |
488 | return save_ref (desc, addr - sizeof (head), i - 1, | |
489 | grub_le_to_cpu32 (head.nitems), 1); | |
490 | return GRUB_ERR_NONE; | |
491 | } | |
492 | *outsize = 0; | |
493 | *outaddr = 0; | |
494 | grub_memset (key_out, 0, sizeof (*key_out)); | |
495 | if (desc) | |
496 | return save_ref (desc, addr - sizeof (head), -1, | |
497 | grub_le_to_cpu32 (head.nitems), 1); | |
498 | return GRUB_ERR_NONE; | |
499 | } | |
b18610fe VS |
500 | } |
501 | } | |
502 | ||
db51e201 | 503 | static grub_device_t |
79282228 VS |
504 | find_device (struct grub_btrfs_data *data, grub_uint64_t id, |
505 | int do_rescan) | |
db51e201 VS |
506 | { |
507 | grub_device_t dev_found = NULL; | |
508 | auto int hook (const char *name); | |
509 | int hook (const char *name) | |
510 | { | |
511 | grub_device_t dev; | |
512 | grub_err_t err; | |
513 | struct grub_btrfs_superblock sb; | |
514 | dev = grub_device_open (name); | |
515 | if (!dev) | |
516 | return 0; | |
517 | if (!dev->disk) | |
518 | { | |
519 | grub_device_close (dev); | |
520 | return 0; | |
521 | } | |
522 | err = read_sblock (dev->disk, &sb); | |
523 | if (err == GRUB_ERR_BAD_FS) | |
524 | { | |
525 | grub_device_close (dev); | |
526 | grub_errno = GRUB_ERR_NONE; | |
527 | return 0; | |
528 | } | |
529 | if (err) | |
530 | { | |
531 | grub_device_close (dev); | |
532 | grub_print_error (); | |
533 | return 0; | |
534 | } | |
535 | if (grub_memcmp (data->sblock.uuid, sb.uuid, sizeof (sb.uuid)) != 0 | |
536 | || sb.this_device.device_id != id) | |
537 | { | |
538 | grub_device_close (dev); | |
539 | return 0; | |
540 | } | |
541 | ||
542 | dev_found = dev; | |
543 | return 1; | |
544 | } | |
545 | ||
546 | unsigned i; | |
547 | ||
548 | for (i = 0; i < data->n_devices_attached; i++) | |
549 | if (id == data->devices_attached[i].id) | |
550 | return data->devices_attached[i].dev; | |
79282228 VS |
551 | if (do_rescan) |
552 | grub_device_iterate (hook); | |
db51e201 VS |
553 | if (!dev_found) |
554 | { | |
555 | grub_error (GRUB_ERR_BAD_FS, "couldn't find a member device"); | |
556 | return NULL; | |
557 | } | |
558 | data->n_devices_attached++; | |
559 | if (data->n_devices_attached > data->n_devices_allocated) | |
560 | { | |
561 | void *tmp; | |
562 | data->n_devices_allocated = 2 * data->n_devices_attached + 1; | |
563 | data->devices_attached | |
564 | = grub_realloc (tmp = data->devices_attached, | |
565 | data->n_devices_allocated | |
566 | * sizeof (data->devices_attached[0])); | |
567 | if (!data->devices_attached) | |
568 | { | |
569 | grub_device_close (dev_found); | |
570 | data->devices_attached = tmp; | |
571 | return NULL; | |
572 | } | |
573 | } | |
574 | data->devices_attached[data->n_devices_attached - 1].id = id; | |
575 | data->devices_attached[data->n_devices_attached - 1].dev = dev_found; | |
576 | return dev_found; | |
577 | } | |
578 | ||
b18610fe VS |
579 | static grub_err_t |
580 | grub_btrfs_read_logical (struct grub_btrfs_data *data, | |
db51e201 | 581 | grub_disk_addr_t addr, |
b18610fe VS |
582 | void *buf, grub_size_t size) |
583 | { | |
584 | while (size > 0) | |
585 | { | |
586 | grub_uint8_t *ptr; | |
587 | struct grub_btrfs_key *key; | |
588 | struct grub_btrfs_chunk_item *chunk; | |
db51e201 | 589 | grub_ssize_t csize; |
b18610fe | 590 | grub_err_t err; |
b18610fe VS |
591 | struct grub_btrfs_key key_out; |
592 | int challoc = 0; | |
db51e201 | 593 | grub_device_t dev; |
ec25b87d VS |
594 | grub_dprintf ("btrfs", "searching for laddr %" PRIxGRUB_UINT64_T "\n", |
595 | addr); | |
b18610fe VS |
596 | for (ptr = data->sblock.bootstrap_mapping; |
597 | ptr < data->sblock.bootstrap_mapping | |
598 | + sizeof (data->sblock.bootstrap_mapping) | |
599 | - sizeof (struct grub_btrfs_key); | |
600 | ) | |
601 | { | |
602 | key = (struct grub_btrfs_key *) ptr; | |
603 | if (key->type != GRUB_BTRFS_ITEM_TYPE_CHUNK) | |
604 | break; | |
605 | chunk = (struct grub_btrfs_chunk_item *) (key + 1); | |
606 | grub_dprintf ("btrfs", "%" PRIxGRUB_UINT64_T " %" PRIxGRUB_UINT64_T " \n", | |
607 | grub_le_to_cpu64 (key->offset), | |
608 | grub_le_to_cpu64 (chunk->size)); | |
609 | if (grub_le_to_cpu64 (key->offset) <= addr | |
610 | && addr < grub_le_to_cpu64 (key->offset) | |
611 | + grub_le_to_cpu64 (chunk->size)) | |
612 | goto chunk_found; | |
613 | ptr += sizeof (*key) + sizeof (*chunk) | |
79282228 VS |
614 | + sizeof (struct grub_btrfs_chunk_stripe) |
615 | * grub_le_to_cpu16 (chunk->nstripes); | |
b18610fe VS |
616 | } |
617 | struct grub_btrfs_key key_in; | |
618 | grub_size_t chsize; | |
619 | grub_disk_addr_t chaddr; | |
620 | key_in.object_id = GRUB_BTRFS_OBJECT_ID_CHUNK; | |
621 | key_in.type = GRUB_BTRFS_ITEM_TYPE_CHUNK; | |
622 | key_in.offset = addr; | |
db51e201 | 623 | err = lower_bound (data, &key_in, &key_out, |
b18610fe VS |
624 | grub_le_to_cpu64 (data->sblock.chunk_tree), |
625 | &chaddr, &chsize, NULL); | |
626 | if (err) | |
627 | return err; | |
628 | key = &key_out; | |
629 | if (key->type != GRUB_BTRFS_ITEM_TYPE_CHUNK | |
630 | || !(grub_le_to_cpu64 (key->offset) <= addr)) | |
631 | return grub_error (GRUB_ERR_BAD_FS, | |
632 | "couldn't find the chunk descriptor"); | |
633 | ||
634 | chunk = grub_malloc (chsize); | |
635 | if (!chunk) | |
636 | return grub_errno; | |
637 | ||
638 | challoc = 1; | |
db51e201 | 639 | err = grub_btrfs_read_logical (data, chaddr, chunk, chsize); |
b18610fe VS |
640 | if (err) |
641 | { | |
642 | grub_free (chunk); | |
643 | return err; | |
644 | } | |
b18610fe VS |
645 | |
646 | chunk_found: | |
db51e201 VS |
647 | { |
648 | grub_uint32_t stripen; | |
649 | grub_uint32_t stripe_offset; | |
650 | grub_uint64_t off = addr - grub_le_to_cpu64 (key->offset); | |
79282228 VS |
651 | unsigned redundancy = 1; |
652 | unsigned i, j; | |
db51e201 | 653 | |
ec25b87d VS |
654 | grub_dprintf ("btrfs", "chunk 0x%" PRIxGRUB_UINT64_T |
655 | "+0x%" PRIxGRUB_UINT64_T | |
656 | " (%d stripes (%d substripes) of %" | |
657 | PRIxGRUB_UINT64_T ")\n", | |
658 | grub_le_to_cpu64 (key->offset), | |
659 | grub_le_to_cpu64 (chunk->size), | |
660 | grub_le_to_cpu16 (chunk->nstripes), | |
661 | grub_le_to_cpu16 (chunk->nsubstripes), | |
662 | grub_le_to_cpu64 (chunk->stripe_length)); | |
663 | ||
db51e201 VS |
664 | switch (grub_le_to_cpu64 (chunk->type) |
665 | & ~GRUB_BTRFS_CHUNK_TYPE_BITS_DONTCARE) | |
666 | { | |
667 | case GRUB_BTRFS_CHUNK_TYPE_SINGLE: | |
668 | { | |
669 | grub_uint32_t stripe_length; | |
670 | stripe_length = grub_divmod64 (grub_le_to_cpu64 (chunk->size), | |
671 | grub_le_to_cpu16 (chunk->nstripes), | |
672 | NULL); | |
673 | stripen = grub_divmod64 (off, stripe_length, &stripe_offset); | |
674 | csize = (stripen + 1) * stripe_length - off; | |
675 | break; | |
676 | } | |
677 | case GRUB_BTRFS_CHUNK_TYPE_DUPLICATED: | |
678 | case GRUB_BTRFS_CHUNK_TYPE_RAID1: | |
db51e201 | 679 | { |
db51e201 VS |
680 | stripen = 0; |
681 | stripe_offset = off; | |
099821e9 | 682 | csize = grub_le_to_cpu64 (chunk->size) - off; |
79282228 | 683 | redundancy = 2; |
db51e201 VS |
684 | break; |
685 | } | |
686 | case GRUB_BTRFS_CHUNK_TYPE_RAID0: | |
687 | { | |
688 | grub_uint64_t middle, high; | |
689 | grub_uint32_t low; | |
690 | middle = grub_divmod64 (off, | |
691 | grub_le_to_cpu64 (chunk->stripe_length), | |
692 | &low); | |
693 | ||
694 | high = grub_divmod64 (middle, grub_le_to_cpu16 (chunk->nstripes), | |
695 | &stripen); | |
696 | stripe_offset = low + grub_le_to_cpu64 (chunk->stripe_length) | |
697 | * high; | |
698 | csize = grub_le_to_cpu64 (chunk->stripe_length) - low; | |
699 | break; | |
700 | } | |
6333f1e9 | 701 | case GRUB_BTRFS_CHUNK_TYPE_RAID10: |
6333f1e9 VS |
702 | { |
703 | grub_uint64_t middle, high; | |
704 | grub_uint32_t low; | |
705 | middle = grub_divmod64 (off, | |
706 | grub_le_to_cpu64 (chunk->stripe_length), | |
707 | &low); | |
708 | ||
709 | high = grub_divmod64 (middle, | |
710 | grub_le_to_cpu16 (chunk->nsubstripes), | |
711 | &stripen); | |
712 | stripen *= grub_le_to_cpu16 (chunk->nstripes) | |
713 | / grub_le_to_cpu16 (chunk->nsubstripes); | |
79282228 VS |
714 | redundancy = grub_le_to_cpu16 (chunk->nstripes) |
715 | / grub_le_to_cpu16 (chunk->nsubstripes); | |
6333f1e9 VS |
716 | stripe_offset = low + grub_le_to_cpu64 (chunk->stripe_length) |
717 | * high; | |
718 | csize = grub_le_to_cpu64 (chunk->stripe_length) - low; | |
719 | break; | |
720 | } | |
db51e201 | 721 | default: |
db51e201 VS |
722 | return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, |
723 | "unsupported RAID flags %" PRIxGRUB_UINT64_T, | |
724 | grub_le_to_cpu64 (chunk->type)); | |
725 | } | |
db51e201 VS |
726 | if (csize <= 0) |
727 | return grub_error (GRUB_ERR_BAD_FS, | |
728 | "couldn't find the chunk descriptor"); | |
729 | if ((grub_size_t) csize > size) | |
730 | csize = size; | |
79282228 VS |
731 | |
732 | for (j = 0; j < 2; j++) | |
733 | { | |
734 | for (i = 0; i < redundancy; i++) | |
735 | { | |
736 | struct grub_btrfs_chunk_stripe *stripe; | |
737 | grub_disk_addr_t paddr; | |
738 | ||
739 | stripe = (struct grub_btrfs_chunk_stripe *) (chunk + 1); | |
5870a4a0 | 740 | /* Right now the redundancy handling is easy. |
79282228 VS |
741 | With RAID5-like it will be more difficult. */ |
742 | stripe += stripen + i; | |
743 | ||
744 | paddr = stripe->offset + stripe_offset; | |
745 | ||
746 | grub_dprintf ("btrfs", "chunk 0x%" PRIxGRUB_UINT64_T | |
747 | "+0x%" PRIxGRUB_UINT64_T " (%d stripes (%d substripes) of %" | |
748 | PRIxGRUB_UINT64_T ") stripe %" PRIxGRUB_UINT32_T | |
749 | " maps to 0x%" PRIxGRUB_UINT64_T "\n", | |
750 | grub_le_to_cpu64 (key->offset), | |
751 | grub_le_to_cpu64 (chunk->size), | |
752 | grub_le_to_cpu16 (chunk->nstripes), | |
753 | grub_le_to_cpu16 (chunk->nsubstripes), | |
754 | grub_le_to_cpu64 (chunk->stripe_length), | |
755 | stripen, stripe->offset); | |
756 | grub_dprintf ("btrfs", "reading paddr 0x%" PRIxGRUB_UINT64_T | |
757 | " for laddr 0x%" PRIxGRUB_UINT64_T"\n", paddr, | |
758 | addr); | |
759 | ||
760 | dev = find_device (data, stripe->device_id, j); | |
761 | if (!dev) | |
762 | { | |
763 | err = grub_errno; | |
764 | grub_errno = GRUB_ERR_NONE; | |
765 | continue; | |
766 | } | |
767 | ||
768 | err = grub_disk_read (dev->disk, paddr >> GRUB_DISK_SECTOR_BITS, | |
769 | paddr & (GRUB_DISK_SECTOR_SIZE - 1), | |
770 | csize, buf); | |
771 | if (!err) | |
772 | break; | |
773 | grub_errno = GRUB_ERR_NONE; | |
774 | } | |
775 | if (i != redundancy) | |
776 | break; | |
777 | } | |
db51e201 | 778 | if (err) |
79282228 | 779 | return grub_errno = err; |
db51e201 | 780 | } |
b18610fe VS |
781 | size -= csize; |
782 | buf = (grub_uint8_t *) buf + csize; | |
783 | addr += csize; | |
784 | if (challoc) | |
785 | grub_free (chunk); | |
786 | } | |
787 | return GRUB_ERR_NONE; | |
788 | } | |
789 | ||
4dfbc574 | 790 | static struct grub_btrfs_data * |
db51e201 | 791 | grub_btrfs_mount (grub_device_t dev) |
4dfbc574 | 792 | { |
db51e201 VS |
793 | struct grub_btrfs_data *data; |
794 | grub_err_t err; | |
b18610fe | 795 | |
db51e201 VS |
796 | if (!dev->disk) |
797 | { | |
798 | grub_error (GRUB_ERR_BAD_FS, "not BtrFS"); | |
799 | return NULL; | |
800 | } | |
801 | ||
802 | data = grub_zalloc (sizeof (*data)); | |
4dfbc574 RM |
803 | if (! data) |
804 | return NULL; | |
805 | ||
db51e201 VS |
806 | err = read_sblock (dev->disk, &data->sblock); |
807 | if (err) | |
b18610fe | 808 | { |
db51e201 VS |
809 | grub_free (data); |
810 | return NULL; | |
b18610fe VS |
811 | } |
812 | ||
db51e201 VS |
813 | data->n_devices_allocated = 16; |
814 | data->devices_attached = grub_malloc (sizeof (data->devices_attached[0]) | |
815 | * data->n_devices_allocated); | |
816 | if (!data->devices_attached) | |
4dfbc574 | 817 | { |
db51e201 VS |
818 | grub_free (data); |
819 | return NULL; | |
4dfbc574 | 820 | } |
db51e201 VS |
821 | data->n_devices_attached = 1; |
822 | data->devices_attached[0].dev = dev; | |
823 | data->devices_attached[0].id = data->sblock.this_device.device_id; | |
b18610fe | 824 | |
4dfbc574 | 825 | return data; |
db51e201 | 826 | } |
4dfbc574 | 827 | |
db51e201 VS |
828 | static void |
829 | grub_btrfs_unmount (struct grub_btrfs_data *data) | |
830 | { | |
831 | unsigned i; | |
832 | /* The device 0 is closed one layer upper. */ | |
833 | for (i = 1; i < data->n_devices_attached; i++) | |
834 | grub_device_close (data->devices_attached[i].dev); | |
835 | grub_free (data->devices_attached); | |
836 | grub_free (data->extent); | |
4dfbc574 | 837 | grub_free (data); |
4dfbc574 RM |
838 | } |
839 | ||
34018a7d | 840 | static grub_err_t |
db51e201 | 841 | grub_btrfs_read_inode (struct grub_btrfs_data *data, |
34018a7d VS |
842 | struct grub_btrfs_inode *inode, grub_uint64_t num, |
843 | grub_uint64_t tree) | |
844 | { | |
845 | struct grub_btrfs_key key_in, key_out; | |
846 | grub_disk_addr_t elemaddr; | |
847 | grub_size_t elemsize; | |
848 | grub_err_t err; | |
849 | ||
850 | key_in.object_id = num; | |
851 | key_in.type = GRUB_BTRFS_ITEM_TYPE_INODE_ITEM; | |
852 | key_in.offset = 0; | |
853 | ||
db51e201 | 854 | err = lower_bound (data, &key_in, &key_out, tree, |
34018a7d VS |
855 | &elemaddr, &elemsize, NULL); |
856 | if (err) | |
857 | return err; | |
858 | if (num != key_out.object_id | |
859 | || key_out.type != GRUB_BTRFS_ITEM_TYPE_INODE_ITEM) | |
860 | return grub_error (GRUB_ERR_BAD_FS, "inode not found"); | |
861 | ||
db51e201 | 862 | return grub_btrfs_read_logical (data, elemaddr, inode, sizeof (*inode)); |
34018a7d VS |
863 | } |
864 | ||
865 | static grub_ssize_t | |
db51e201 | 866 | grub_btrfs_extent_read (struct grub_btrfs_data *data, |
34018a7d VS |
867 | grub_uint64_t ino, grub_uint64_t tree, |
868 | grub_off_t pos0, char *buf, grub_size_t len) | |
869 | { | |
870 | grub_off_t pos = pos0; | |
871 | while (len) | |
872 | { | |
873 | grub_size_t csize; | |
874 | grub_err_t err; | |
875 | grub_off_t extoff; | |
876 | if (!data->extent || data->extstart > pos || data->extino != ino | |
6a01f54a | 877 | || data->exttree != tree || data->extend <= pos) |
34018a7d VS |
878 | { |
879 | struct grub_btrfs_key key_in, key_out; | |
880 | grub_disk_addr_t elemaddr; | |
881 | grub_size_t elemsize; | |
6a01f54a | 882 | |
34018a7d VS |
883 | grub_free (data->extent); |
884 | key_in.object_id = ino; | |
885 | key_in.type = GRUB_BTRFS_ITEM_TYPE_EXTENT_ITEM; | |
886 | key_in.offset = grub_cpu_to_le64 (pos); | |
db51e201 | 887 | err = lower_bound (data, &key_in, &key_out, tree, |
34018a7d VS |
888 | &elemaddr, &elemsize, NULL); |
889 | if (err) | |
890 | return -1; | |
891 | if (key_out.object_id != ino | |
892 | || key_out.type != GRUB_BTRFS_ITEM_TYPE_EXTENT_ITEM) | |
893 | { | |
894 | grub_error (GRUB_ERR_BAD_FS, "extent not found"); | |
895 | return -1; | |
896 | } | |
897 | data->extstart = grub_le_to_cpu64 (key_out.offset); | |
3be8e5ea | 898 | data->extsize = elemsize; |
34018a7d VS |
899 | data->extent = grub_malloc (elemsize); |
900 | data->extino = ino; | |
901 | data->exttree = tree; | |
902 | if (!data->extent) | |
903 | return grub_errno; | |
904 | ||
db51e201 | 905 | err = grub_btrfs_read_logical (data, elemaddr, |
34018a7d VS |
906 | data->extent, elemsize); |
907 | if (err) | |
908 | return err; | |
6a01f54a VS |
909 | |
910 | data->extend = data->extstart | |
911 | + grub_le_to_cpu64 (data->extent->size); | |
912 | if (data->extent->type == GRUB_BTRFS_EXTENT_REGULAR | |
913 | && (char *) &data->extent + elemsize | |
914 | >= (char *) &data->extent->filled | |
915 | + sizeof (data->extent->filled)) | |
916 | data->extend = data->extstart | |
917 | + grub_le_to_cpu64 (data->extent->filled); | |
918 | ||
34018a7d | 919 | grub_dprintf ("btrfs", "extent 0x%" PRIxGRUB_UINT64_T "+0x%" |
6a01f54a VS |
920 | PRIxGRUB_UINT64_T " (0x%" |
921 | PRIxGRUB_UINT64_T ")\n", | |
34018a7d | 922 | grub_le_to_cpu64 (key_out.offset), |
6a01f54a VS |
923 | grub_le_to_cpu64 (data->extent->size), |
924 | grub_le_to_cpu64 (data->extent->filled)); | |
925 | if (data->extend <= pos) | |
926 | { | |
927 | grub_error (GRUB_ERR_BAD_FS, "extent not found"); | |
928 | return -1; | |
929 | } | |
34018a7d | 930 | } |
6a01f54a | 931 | csize = data->extend - pos; |
34018a7d VS |
932 | extoff = pos - data->extstart; |
933 | if (csize > len) | |
934 | csize = len; | |
935 | ||
936 | if (data->extent->encryption) | |
937 | { | |
938 | grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, | |
939 | "encryption not supported"); | |
940 | return -1; | |
941 | } | |
942 | ||
3be8e5ea VS |
943 | if (data->extent->compression != GRUB_BTRFS_COMPRESSION_NONE |
944 | && data->extent->compression != GRUB_BTRFS_COMPRESSION_ZLIB) | |
34018a7d VS |
945 | { |
946 | grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, | |
3be8e5ea VS |
947 | "compression type 0x%x not supported", |
948 | data->extent->compression); | |
34018a7d VS |
949 | return -1; |
950 | } | |
951 | ||
34018a7d VS |
952 | if (data->extent->encoding) |
953 | { | |
954 | grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, | |
955 | "encoding not supported"); | |
956 | return -1; | |
957 | } | |
958 | ||
959 | switch (data->extent->type) | |
960 | { | |
961 | case GRUB_BTRFS_EXTENT_INLINE: | |
3be8e5ea VS |
962 | if (data->extent->compression == GRUB_BTRFS_COMPRESSION_ZLIB) |
963 | { | |
964 | if (grub_zlib_decompress (data->extent->inl, data->extsize - | |
965 | ((grub_uint8_t *) data->extent->inl | |
966 | - (grub_uint8_t *) data->extent), | |
967 | extoff, buf, csize) | |
968 | != (grub_ssize_t) csize) | |
969 | return -1; | |
970 | } | |
971 | else | |
972 | grub_memcpy (buf, data->extent->inl + extoff, csize); | |
34018a7d VS |
973 | break; |
974 | case GRUB_BTRFS_EXTENT_REGULAR: | |
975 | if (!data->extent->laddr) | |
976 | { | |
977 | grub_memset (buf, 0, csize); | |
978 | break; | |
979 | } | |
3be8e5ea VS |
980 | if (data->extent->compression == GRUB_BTRFS_COMPRESSION_ZLIB) |
981 | { | |
982 | char *tmp; | |
983 | grub_uint64_t zsize; | |
984 | zsize = grub_le_to_cpu64 (data->extent->compressed_size); | |
985 | tmp = grub_malloc (zsize); | |
986 | if (!tmp) | |
987 | return -1; | |
988 | err = grub_btrfs_read_logical (data, | |
989 | grub_le_to_cpu64 (data->extent->laddr), | |
990 | tmp, zsize); | |
991 | if (err) | |
992 | { | |
993 | grub_free (tmp); | |
994 | return -1; | |
995 | } | |
996 | if (grub_zlib_decompress (tmp, zsize, extoff | |
997 | + grub_le_to_cpu64 (data->extent->offset), | |
998 | buf, csize) != (grub_ssize_t) csize) | |
999 | { | |
1000 | grub_free (tmp); | |
1001 | return -1; | |
1002 | } | |
1003 | grub_free (tmp); | |
1004 | break; | |
1005 | } | |
db51e201 | 1006 | err = grub_btrfs_read_logical (data, |
34018a7d | 1007 | grub_le_to_cpu64 (data->extent->laddr) |
565f0763 | 1008 | + grub_le_to_cpu64 (data->extent->offset) |
34018a7d VS |
1009 | + extoff, |
1010 | buf, csize); | |
1011 | if (err) | |
1012 | return -1; | |
1013 | break; | |
1014 | default: | |
1015 | grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, | |
1016 | "unsupported extent type 0x%x", data->extent->type); | |
1017 | return -1; | |
1018 | } | |
1019 | buf += csize; | |
1020 | pos += csize; | |
1021 | len -= csize; | |
1022 | } | |
1023 | return pos - pos0; | |
1024 | } | |
1025 | ||
4dfbc574 | 1026 | static grub_err_t |
b18610fe | 1027 | find_path (struct grub_btrfs_data *data, |
b18610fe | 1028 | const char *path, struct grub_btrfs_key *key, |
df80cd06 | 1029 | grub_uint64_t *tree, grub_uint8_t *type) |
4dfbc574 | 1030 | { |
d9865a25 | 1031 | const char *slash = path; |
b18610fe VS |
1032 | grub_err_t err; |
1033 | grub_disk_addr_t elemaddr; | |
1034 | grub_size_t elemsize; | |
1035 | grub_size_t allocated = 0; | |
1036 | struct grub_btrfs_dir_item *direl = NULL; | |
1037 | struct grub_btrfs_key key_out; | |
34018a7d | 1038 | int skip_default; |
d9865a25 VS |
1039 | const char *ctoken; |
1040 | grub_size_t ctokenlen; | |
34018a7d | 1041 | char *path_alloc = NULL; |
db51e201 | 1042 | unsigned symlinks_max = 32; |
b18610fe | 1043 | |
df80cd06 | 1044 | *type = GRUB_BTRFS_DIR_ITEM_TYPE_DIRECTORY; |
b18610fe VS |
1045 | *tree = data->sblock.root_tree; |
1046 | key->object_id = data->sblock.root_dir_objectid; | |
1f60e353 VS |
1047 | key->type = GRUB_BTRFS_ITEM_TYPE_DIR_ITEM; |
1048 | key->offset = 0; | |
34018a7d | 1049 | skip_default = 1; |
b18610fe VS |
1050 | |
1051 | while (1) | |
1052 | { | |
d9865a25 VS |
1053 | if (!skip_default) |
1054 | { | |
1055 | while (path[0] == '/') | |
1056 | path++; | |
1057 | if (!path[0]) | |
1058 | break; | |
1059 | slash = grub_strchr (path, '/'); | |
1060 | if (!slash) | |
1061 | slash = path + grub_strlen (path); | |
1062 | ctoken = path; | |
1063 | ctokenlen = slash - path; | |
1064 | } | |
1065 | else | |
1066 | { | |
1067 | ctoken = "default"; | |
1068 | ctokenlen = sizeof ("default") - 1; | |
1069 | } | |
df80cd06 VS |
1070 | |
1071 | if (*type != GRUB_BTRFS_DIR_ITEM_TYPE_DIRECTORY) | |
34018a7d VS |
1072 | { |
1073 | grub_free (path_alloc); | |
1074 | return grub_error (GRUB_ERR_BAD_FILE_TYPE, "not a directory"); | |
1075 | } | |
df80cd06 VS |
1076 | |
1077 | key->type = GRUB_BTRFS_ITEM_TYPE_DIR_ITEM; | |
d9865a25 | 1078 | key->offset = grub_cpu_to_le64 (~grub_getcrc32c (1, ctoken, ctokenlen)); |
b18610fe | 1079 | |
db51e201 | 1080 | err = lower_bound (data, key, &key_out, *tree, |
b18610fe VS |
1081 | &elemaddr, &elemsize, NULL); |
1082 | if (err) | |
1083 | { | |
1084 | grub_free (direl); | |
34018a7d | 1085 | grub_free (path_alloc); |
b18610fe VS |
1086 | return err; |
1087 | } | |
1088 | if (key_cmp (key, &key_out) != 0) | |
1089 | { | |
1090 | grub_free (direl); | |
34018a7d | 1091 | grub_free (path_alloc); |
b18610fe VS |
1092 | return grub_error (GRUB_ERR_FILE_NOT_FOUND, "file not found"); |
1093 | } | |
1094 | ||
1095 | struct grub_btrfs_dir_item *cdirel; | |
1096 | if (elemsize > allocated) | |
1097 | { | |
1098 | allocated = 2 * elemsize; | |
1099 | grub_free (direl); | |
1100 | direl = grub_malloc (allocated + 1); | |
1101 | if (!direl) | |
34018a7d VS |
1102 | { |
1103 | grub_free (path_alloc); | |
1104 | return grub_errno; | |
1105 | } | |
b18610fe VS |
1106 | } |
1107 | ||
db51e201 | 1108 | err = grub_btrfs_read_logical (data, elemaddr, direl, elemsize); |
b18610fe VS |
1109 | if (err) |
1110 | { | |
1111 | grub_free (direl); | |
34018a7d | 1112 | grub_free (path_alloc); |
b18610fe VS |
1113 | return err; |
1114 | } | |
1115 | ||
1116 | for (cdirel = direl; | |
1117 | (grub_uint8_t *) cdirel - (grub_uint8_t *) direl | |
1118 | < (grub_ssize_t) elemsize; | |
1119 | cdirel = (void *) ((grub_uint8_t *) (direl + 1) | |
1120 | + grub_le_to_cpu16 (cdirel->n) | |
1121 | + grub_le_to_cpu16 (cdirel->m))) | |
1122 | { | |
228f95a2 VS |
1123 | if (ctokenlen == grub_le_to_cpu16 (cdirel->n) |
1124 | && grub_memcmp (cdirel->name, ctoken, ctokenlen) == 0) | |
b18610fe | 1125 | break; |
b18610fe VS |
1126 | } |
1127 | if ((grub_uint8_t *) cdirel - (grub_uint8_t *) direl | |
1128 | >= (grub_ssize_t) elemsize) | |
1129 | { | |
1130 | grub_free (direl); | |
34018a7d | 1131 | grub_free (path_alloc); |
b18610fe VS |
1132 | return grub_error (GRUB_ERR_FILE_NOT_FOUND, "file not found"); |
1133 | } | |
1134 | ||
d9865a25 VS |
1135 | if (!skip_default) |
1136 | path = slash; | |
1137 | skip_default = 0; | |
34018a7d | 1138 | if (cdirel->type == GRUB_BTRFS_DIR_ITEM_TYPE_SYMLINK) |
df80cd06 | 1139 | { |
34018a7d VS |
1140 | struct grub_btrfs_inode inode; |
1141 | char *tmp; | |
db51e201 VS |
1142 | if (--symlinks_max == 0) |
1143 | { | |
1144 | grub_free (direl); | |
1145 | grub_free (path_alloc); | |
1146 | return grub_error (GRUB_ERR_SYMLINK_LOOP, | |
1147 | "too deep nesting of symlinks"); | |
1148 | } | |
1149 | ||
1150 | err = grub_btrfs_read_inode (data, &inode, | |
34018a7d VS |
1151 | cdirel->key.object_id, *tree); |
1152 | if (err) | |
1153 | { | |
1154 | grub_free (direl); | |
1155 | grub_free (path_alloc); | |
1156 | return err; | |
1157 | } | |
1158 | tmp = grub_malloc (grub_le_to_cpu64 (inode.size) | |
1159 | + grub_strlen (path) + 1); | |
1160 | if (!tmp) | |
1161 | { | |
1162 | grub_free (direl); | |
1163 | grub_free (path_alloc); | |
1164 | return grub_errno; | |
1165 | } | |
1166 | ||
db51e201 | 1167 | if (grub_btrfs_extent_read (data, cdirel->key.object_id, |
34018a7d VS |
1168 | *tree, 0, tmp, |
1169 | grub_le_to_cpu64 (inode.size)) | |
1170 | != (grub_ssize_t) grub_le_to_cpu64 (inode.size)) | |
1171 | { | |
1172 | grub_free (direl); | |
1173 | grub_free (path_alloc); | |
1174 | grub_free (tmp); | |
1175 | return grub_errno; | |
1176 | } | |
1177 | grub_memcpy (tmp + grub_le_to_cpu64 (inode.size), path, | |
1178 | grub_strlen (path) + 1); | |
1179 | grub_free (path_alloc); | |
1180 | path = path_alloc = tmp; | |
1181 | if (path[0] == '/') | |
1182 | { | |
1183 | *type = GRUB_BTRFS_DIR_ITEM_TYPE_DIRECTORY; | |
1184 | *tree = data->sblock.root_tree; | |
1185 | key->object_id = data->sblock.root_dir_objectid; | |
1186 | key->type = GRUB_BTRFS_ITEM_TYPE_DIR_ITEM; | |
1187 | key->offset = 0; | |
1188 | skip_default = 1; | |
1189 | } | |
1190 | continue; | |
df80cd06 | 1191 | } |
34018a7d | 1192 | *type = cdirel->type; |
93e0c7a7 | 1193 | |
b18610fe VS |
1194 | switch (cdirel->key.type) |
1195 | { | |
1196 | case GRUB_BTRFS_ITEM_TYPE_ROOT_ITEM: | |
1197 | { | |
1198 | struct grub_btrfs_root_item ri; | |
db51e201 | 1199 | err = lower_bound (data, &cdirel->key, &key_out, |
93e0c7a7 | 1200 | data->sblock.root_tree, |
b18610fe VS |
1201 | &elemaddr, &elemsize, NULL); |
1202 | if (err) | |
34018a7d VS |
1203 | { |
1204 | grub_free (direl); | |
1205 | grub_free (path_alloc); | |
1206 | return err; | |
1207 | } | |
b18610fe VS |
1208 | if (cdirel->key.object_id != key_out.object_id |
1209 | || cdirel->key.type != key_out.type) | |
34018a7d VS |
1210 | { |
1211 | grub_free (direl); | |
1212 | grub_free (path_alloc); | |
1213 | return grub_error (GRUB_ERR_FILE_NOT_FOUND, "file not found"); | |
1214 | } | |
db51e201 | 1215 | err = grub_btrfs_read_logical (data, elemaddr, |
b18610fe VS |
1216 | &ri, sizeof (ri)); |
1217 | if (err) | |
34018a7d VS |
1218 | { |
1219 | grub_free (direl); | |
1220 | grub_free (path_alloc); | |
1221 | return err; | |
1222 | } | |
b18610fe VS |
1223 | key->type = GRUB_BTRFS_ITEM_TYPE_DIR_ITEM; |
1224 | key->offset = 0; | |
1225 | key->object_id = GRUB_BTRFS_OBJECT_ID_CHUNK; | |
1226 | *tree = grub_le_to_cpu64 (ri.tree); | |
1227 | break; | |
1228 | } | |
1229 | case GRUB_BTRFS_ITEM_TYPE_INODE_ITEM: | |
9b4cb862 | 1230 | if (*slash && *type == GRUB_BTRFS_DIR_ITEM_TYPE_REGULAR) |
34018a7d VS |
1231 | { |
1232 | grub_free (direl); | |
1233 | grub_free (path_alloc); | |
1234 | return grub_error (GRUB_ERR_FILE_NOT_FOUND, "file not found"); | |
1235 | } | |
b18610fe | 1236 | *key = cdirel->key; |
9b4cb862 | 1237 | if (*type == GRUB_BTRFS_DIR_ITEM_TYPE_DIRECTORY) |
34018a7d | 1238 | key->type = GRUB_BTRFS_ITEM_TYPE_DIR_ITEM; |
b18610fe VS |
1239 | break; |
1240 | default: | |
34018a7d VS |
1241 | grub_free (path_alloc); |
1242 | grub_free (direl); | |
b18610fe VS |
1243 | return grub_error (GRUB_ERR_BAD_FS, "unrecognised object type 0x%x", |
1244 | cdirel->key.type); | |
1245 | } | |
1246 | } | |
1247 | ||
1248 | grub_free (direl); | |
b18610fe | 1249 | return GRUB_ERR_NONE; |
4dfbc574 RM |
1250 | } |
1251 | ||
1252 | static grub_err_t | |
eb82b856 | 1253 | grub_btrfs_dir (grub_device_t device, const char *path, |
4dfbc574 | 1254 | int (*hook) (const char *filename, |
b18610fe | 1255 | const struct grub_dirhook_info *info)) |
4dfbc574 | 1256 | { |
db51e201 | 1257 | struct grub_btrfs_data *data = grub_btrfs_mount (device); |
b18610fe VS |
1258 | struct grub_btrfs_key key_in, key_out; |
1259 | grub_err_t err; | |
1260 | grub_disk_addr_t elemaddr; | |
1261 | grub_size_t elemsize; | |
1262 | grub_size_t allocated = 0; | |
1263 | struct grub_btrfs_dir_item *direl = NULL; | |
1264 | struct grub_btrfs_leaf_descriptor desc; | |
a3d1fcfb | 1265 | int r = 0; |
b18610fe | 1266 | grub_uint64_t tree; |
df80cd06 | 1267 | grub_uint8_t type; |
b18610fe VS |
1268 | |
1269 | if (!data) | |
4dfbc574 RM |
1270 | return grub_errno; |
1271 | ||
db51e201 | 1272 | err = find_path (data, path, &key_in, &tree, &type); |
b18610fe VS |
1273 | if (err) |
1274 | return err; | |
df80cd06 VS |
1275 | if (type != GRUB_BTRFS_DIR_ITEM_TYPE_DIRECTORY) |
1276 | return grub_error (GRUB_ERR_BAD_FILE_TYPE, "not a directory"); | |
b18610fe | 1277 | |
db51e201 | 1278 | err = lower_bound (data, &key_in, &key_out, tree, |
b18610fe VS |
1279 | &elemaddr, &elemsize, &desc); |
1280 | if (err) | |
1281 | return err; | |
1282 | if (key_out.type != GRUB_BTRFS_ITEM_TYPE_DIR_ITEM | |
1283 | || key_out.object_id != key_in.object_id) | |
1284 | { | |
db51e201 | 1285 | r = next (data, &desc, &elemaddr, &elemsize, &key_out); |
b18610fe VS |
1286 | if (r <= 0) |
1287 | { | |
1288 | free_iterator (&desc); | |
1289 | return -r; | |
1290 | } | |
1291 | } | |
1292 | do | |
1293 | { | |
b18610fe VS |
1294 | struct grub_btrfs_dir_item *cdirel; |
1295 | if (key_out.type != GRUB_BTRFS_ITEM_TYPE_DIR_ITEM | |
1296 | || key_out.object_id != key_in.object_id) | |
1297 | { | |
1298 | r = 0; | |
1299 | break; | |
1300 | } | |
1301 | if (elemsize > allocated) | |
1302 | { | |
1303 | allocated = 2 * elemsize; | |
1304 | grub_free (direl); | |
1305 | direl = grub_malloc (allocated + 1); | |
1306 | if (!direl) | |
1307 | { | |
1308 | free_iterator (&desc); | |
1309 | return grub_errno; | |
1310 | } | |
1311 | } | |
1312 | ||
db51e201 | 1313 | err = grub_btrfs_read_logical (data, elemaddr, direl, elemsize); |
b18610fe VS |
1314 | if (err) |
1315 | return err; | |
1316 | ||
1317 | for (cdirel = direl; | |
1318 | (grub_uint8_t *) cdirel - (grub_uint8_t *) direl | |
1319 | < (grub_ssize_t) elemsize; | |
1320 | cdirel = (void *) ((grub_uint8_t *) (direl + 1) | |
1321 | + grub_le_to_cpu16 (cdirel->n) | |
1322 | + grub_le_to_cpu16 (cdirel->m))) | |
1323 | { | |
1324 | char c; | |
d6f07b29 VS |
1325 | struct grub_btrfs_inode inode; |
1326 | struct grub_dirhook_info info; | |
1327 | err = grub_btrfs_read_inode (data, &inode, cdirel->key.object_id, | |
1328 | tree); | |
1329 | grub_memset (&info, 0, sizeof (info)); | |
1330 | if (err) | |
1331 | grub_errno = GRUB_ERR_NONE; | |
1332 | else | |
1333 | { | |
1334 | info.mtime = inode.mtime.sec; | |
1335 | info.mtimeset = 1; | |
1336 | } | |
b18610fe VS |
1337 | c = cdirel->name[grub_le_to_cpu16 (cdirel->n)]; |
1338 | cdirel->name[grub_le_to_cpu16 (cdirel->n)] = 0; | |
df80cd06 | 1339 | info.dir = (cdirel->type == GRUB_BTRFS_DIR_ITEM_TYPE_DIRECTORY); |
b18610fe VS |
1340 | if (hook (cdirel->name, &info)) |
1341 | goto out; | |
1342 | cdirel->name[grub_le_to_cpu16 (cdirel->n)] = c; | |
1343 | } | |
db51e201 | 1344 | r = next (data, &desc, &elemaddr, &elemsize, &key_out); |
b18610fe VS |
1345 | } |
1346 | while (r > 0); | |
1347 | ||
1348 | out: | |
1349 | grub_free (direl); | |
1350 | ||
1351 | free_iterator (&desc); | |
db51e201 | 1352 | grub_btrfs_unmount (data); |
4dfbc574 | 1353 | |
b18610fe VS |
1354 | return -r; |
1355 | } | |
1356 | ||
1357 | static grub_err_t | |
1358 | grub_btrfs_open (struct grub_file *file, const char *name) | |
1359 | { | |
db51e201 | 1360 | struct grub_btrfs_data *data = grub_btrfs_mount (file->device); |
b18610fe | 1361 | grub_err_t err; |
b18610fe | 1362 | struct grub_btrfs_inode inode; |
df80cd06 | 1363 | grub_uint8_t type; |
34018a7d | 1364 | struct grub_btrfs_key key_in; |
b18610fe VS |
1365 | |
1366 | if (!data) | |
1367 | return grub_errno; | |
1368 | ||
db51e201 | 1369 | err = find_path (data, name, &key_in, &data->tree, &type); |
b18610fe VS |
1370 | if (err) |
1371 | { | |
db51e201 | 1372 | grub_btrfs_unmount (data); |
b18610fe VS |
1373 | return err; |
1374 | } | |
df80cd06 | 1375 | if (type != GRUB_BTRFS_DIR_ITEM_TYPE_REGULAR) |
34018a7d | 1376 | { |
db51e201 | 1377 | grub_btrfs_unmount (data); |
34018a7d VS |
1378 | return grub_error (GRUB_ERR_BAD_FILE_TYPE, "not a regular file"); |
1379 | } | |
b18610fe | 1380 | |
34018a7d | 1381 | data->inode = key_in.object_id; |
db51e201 | 1382 | err = grub_btrfs_read_inode (data, &inode, data->inode, data->tree); |
b18610fe | 1383 | if (err) |
34018a7d | 1384 | { |
db51e201 | 1385 | grub_btrfs_unmount (data); |
34018a7d VS |
1386 | return err; |
1387 | } | |
b18610fe VS |
1388 | |
1389 | file->data = data; | |
1390 | file->size = grub_le_to_cpu64 (inode.size); | |
1391 | ||
34018a7d | 1392 | return err; |
b18610fe VS |
1393 | } |
1394 | ||
1395 | static grub_err_t | |
1396 | grub_btrfs_close (grub_file_t file) | |
1397 | { | |
db51e201 | 1398 | grub_btrfs_unmount (file->data); |
b18610fe | 1399 | |
4dfbc574 RM |
1400 | return GRUB_ERR_NONE; |
1401 | } | |
1402 | ||
b18610fe VS |
1403 | static grub_ssize_t |
1404 | grub_btrfs_read (grub_file_t file, char *buf, grub_size_t len) | |
1405 | { | |
1406 | struct grub_btrfs_data *data = file->data; | |
b18610fe | 1407 | |
db51e201 | 1408 | return grub_btrfs_extent_read (data, data->inode, |
34018a7d | 1409 | data->tree, file->offset, buf, len); |
b18610fe VS |
1410 | } |
1411 | ||
4dfbc574 RM |
1412 | static grub_err_t |
1413 | grub_btrfs_uuid (grub_device_t device, char **uuid) | |
1414 | { | |
1415 | struct grub_btrfs_data *data; | |
1416 | ||
1417 | *uuid = NULL; | |
1418 | ||
db51e201 | 1419 | data = grub_btrfs_mount (device); |
4dfbc574 RM |
1420 | if (! data) |
1421 | return grub_errno; | |
1422 | ||
1423 | *uuid = grub_xasprintf ("%04x%04x-%04x-%04x-%04x-%04x%04x%04x", | |
1424 | grub_be_to_cpu16 (data->sblock.uuid[0]), | |
1425 | grub_be_to_cpu16 (data->sblock.uuid[1]), | |
1426 | grub_be_to_cpu16 (data->sblock.uuid[2]), | |
1427 | grub_be_to_cpu16 (data->sblock.uuid[3]), | |
1428 | grub_be_to_cpu16 (data->sblock.uuid[4]), | |
1429 | grub_be_to_cpu16 (data->sblock.uuid[5]), | |
1430 | grub_be_to_cpu16 (data->sblock.uuid[6]), | |
1431 | grub_be_to_cpu16 (data->sblock.uuid[7])); | |
1432 | ||
db51e201 | 1433 | grub_btrfs_unmount (data); |
4dfbc574 RM |
1434 | |
1435 | return grub_errno; | |
1436 | } | |
1437 | ||
b18610fe VS |
1438 | static grub_err_t |
1439 | grub_btrfs_label (grub_device_t device, char **label) | |
1440 | { | |
1441 | struct grub_btrfs_data *data; | |
1442 | ||
1443 | *label = NULL; | |
1444 | ||
db51e201 | 1445 | data = grub_btrfs_mount (device); |
b18610fe VS |
1446 | if (! data) |
1447 | return grub_errno; | |
1448 | ||
1449 | *label = grub_strndup (data->sblock.label, sizeof (data->sblock.label)); | |
1450 | ||
db51e201 | 1451 | grub_btrfs_unmount (data); |
b18610fe VS |
1452 | |
1453 | return grub_errno; | |
1454 | } | |
1455 | ||
4dfbc574 RM |
1456 | static struct grub_fs grub_btrfs_fs = |
1457 | { | |
1458 | .name = "btrfs", | |
1459 | .dir = grub_btrfs_dir, | |
1460 | .open = grub_btrfs_open, | |
b18610fe VS |
1461 | .read = grub_btrfs_read, |
1462 | .close = grub_btrfs_close, | |
4dfbc574 | 1463 | .uuid = grub_btrfs_uuid, |
b18610fe | 1464 | .label = grub_btrfs_label, |
4dfbc574 RM |
1465 | }; |
1466 | ||
1467 | GRUB_MOD_INIT(btrfs) | |
1468 | { | |
1469 | grub_fs_register (&grub_btrfs_fs); | |
1470 | } | |
1471 | ||
1472 | GRUB_MOD_FINI(btrfs) | |
1473 | { | |
1474 | grub_fs_unregister (&grub_btrfs_fs); | |
1475 | } |