]>
Commit | Line | Data |
---|---|---|
9a909877 | 1 | /* sfs.c - Amiga Smart FileSystem. */ |
2 | /* | |
3 | * GRUB -- GRand Unified Bootloader | |
21cf716a | 4 | * Copyright (C) 2005,2006,2007,2008 Free Software Foundation, Inc. |
9a909877 | 5 | * |
5a79f472 | 6 | * GRUB is free software: you can redistribute it and/or modify |
9a909877 | 7 | * it under the terms of the GNU General Public License as published by |
5a79f472 | 8 | * the Free Software Foundation, either version 3 of the License, or |
9a909877 | 9 | * (at your option) any later version. |
10 | * | |
5a79f472 | 11 | * GRUB is distributed in the hope that it will be useful, |
9a909877 | 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 | |
5a79f472 | 17 | * along with GRUB. If not, see <http://www.gnu.org/licenses/>. |
9a909877 | 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> | |
27 | #include <grub/fshelp.h> | |
28 | ||
29 | /* The common header for a block. */ | |
30 | struct grub_sfs_bheader | |
31 | { | |
32 | grub_uint8_t magic[4]; | |
33 | grub_uint32_t chksum; | |
34 | grub_uint32_t ipointtomyself; | |
35 | } __attribute__ ((packed)); | |
36 | ||
37 | /* The sfs rootblock. */ | |
38 | struct grub_sfs_rblock | |
39 | { | |
40 | struct grub_sfs_bheader header; | |
41 | grub_uint32_t version; | |
42 | grub_uint8_t unused1[36]; | |
43 | grub_uint32_t blocksize; | |
44 | grub_uint8_t unused2[40]; | |
45 | grub_uint8_t unused3[8]; | |
46 | grub_uint32_t rootobject; | |
47 | grub_uint32_t btree; | |
48 | } __attribute__ ((packed)); | |
49 | ||
50 | /* A SFS object container. */ | |
51 | struct grub_sfs_obj | |
52 | { | |
53 | grub_uint8_t unused1[4]; | |
54 | grub_uint32_t nodeid; | |
55 | grub_uint8_t unused2[4]; | |
56 | union | |
57 | { | |
58 | struct | |
59 | { | |
60 | grub_uint32_t first_block; | |
61 | grub_uint32_t size; | |
62 | } file __attribute__ ((packed)); | |
63 | struct | |
64 | { | |
65 | grub_uint32_t hashtable; | |
66 | grub_uint32_t dir_objc; | |
67 | } dir __attribute__ ((packed)); | |
68 | } file_dir; | |
69 | grub_uint8_t unused3[4]; | |
70 | grub_uint8_t type; | |
71 | grub_uint8_t filename[1]; | |
72 | grub_uint8_t comment[1]; | |
73 | } __attribute__ ((packed)); | |
74 | ||
75 | #define GRUB_SFS_TYPE_DELETED 32 | |
76 | #define GRUB_SFS_TYPE_SYMLINK 64 | |
77 | #define GRUB_SFS_TYPE_DIR 128 | |
78 | ||
79 | /* A SFS object container. */ | |
80 | struct grub_sfs_objc | |
81 | { | |
82 | struct grub_sfs_bheader header; | |
83 | grub_uint32_t parent; | |
84 | grub_uint32_t next; | |
85 | grub_uint32_t prev; | |
86 | /* The amount of objects depends on the blocksize. */ | |
87 | struct grub_sfs_obj objects[1]; | |
88 | } __attribute__ ((packed)); | |
89 | ||
90 | struct grub_sfs_btree_node | |
91 | { | |
92 | grub_uint32_t key; | |
93 | grub_uint32_t data; | |
94 | } __attribute__ ((packed)); | |
95 | ||
96 | struct grub_sfs_btree_extent | |
97 | { | |
98 | grub_uint32_t key; | |
99 | grub_uint32_t next; | |
100 | grub_uint32_t prev; | |
101 | grub_uint16_t size; | |
102 | } __attribute__ ((packed)); | |
103 | ||
104 | struct grub_sfs_btree | |
105 | { | |
106 | struct grub_sfs_bheader header; | |
107 | grub_uint16_t nodes; | |
108 | grub_uint8_t leaf; | |
109 | grub_uint8_t nodesize; | |
110 | /* Normally this can be kind of node, but just extents are | |
111 | supported. */ | |
112 | struct grub_sfs_btree_node node[1]; | |
113 | } __attribute__ ((packed)); | |
114 | ||
115 | \f | |
116 | ||
117 | struct grub_fshelp_node | |
118 | { | |
119 | struct grub_sfs_data *data; | |
120 | int block; | |
121 | int size; | |
122 | }; | |
123 | ||
124 | /* Information about a "mounted" sfs filesystem. */ | |
125 | struct grub_sfs_data | |
126 | { | |
127 | struct grub_sfs_rblock rblock; | |
128 | struct grub_fshelp_node diropen; | |
129 | grub_disk_t disk; | |
130 | ||
131 | /* Blocksize in sectors. */ | |
132 | unsigned int blocksize; | |
133 | ||
134 | /* Label of the filesystem. */ | |
135 | char *label; | |
136 | }; | |
137 | ||
138 | #ifndef GRUB_UTIL | |
139 | static grub_dl_t my_mod; | |
140 | #endif | |
141 | ||
142 | \f | |
143 | /* Lookup the extent starting with BLOCK in the filesystem described | |
144 | by DATA. Return the extent size in SIZE and the following extent | |
145 | in NEXTEXT. */ | |
146 | static grub_err_t | |
147 | grub_sfs_read_extent (struct grub_sfs_data *data, unsigned int block, | |
148 | int *size, int *nextext) | |
149 | { | |
150 | char *treeblock; | |
151 | struct grub_sfs_btree *tree; | |
152 | int i; | |
153 | int next; | |
154 | int prev; | |
155 | ||
156 | treeblock = grub_malloc (data->blocksize); | |
157 | if (!block) | |
158 | return 0; | |
159 | ||
160 | next = grub_be_to_cpu32 (data->rblock.btree); | |
161 | tree = (struct grub_sfs_btree *) treeblock; | |
162 | ||
163 | /* Handle this level in the btree. */ | |
164 | do | |
165 | { | |
166 | prev = 0; | |
167 | ||
168 | grub_disk_read (data->disk, next, 0, data->blocksize, treeblock); | |
169 | if (grub_errno) | |
170 | { | |
171 | grub_free (treeblock); | |
172 | return grub_errno; | |
173 | } | |
174 | ||
34519c3f | 175 | for (i = grub_be_to_cpu16 (tree->nodes) - 1; i >= 0; i--) |
9a909877 | 176 | { |
177 | ||
178 | #define EXTNODE(tree, index) \ | |
179 | ((struct grub_sfs_btree_node *) (((char *) &(tree)->node[0]) \ | |
180 | + (index) * (tree)->nodesize)) | |
181 | ||
182 | /* Follow the tree down to the leaf level. */ | |
34519c3f | 183 | if ((grub_be_to_cpu32 (EXTNODE(tree, i)->key) <= block) |
9a909877 | 184 | && !tree->leaf) |
9a909877 | 185 | { |
186 | next = grub_be_to_cpu32 (EXTNODE (tree, i)->data); | |
187 | break; | |
188 | } | |
189 | ||
190 | /* If the leaf level is reached, just find the correct extent. */ | |
191 | if (grub_be_to_cpu32 (EXTNODE (tree, i)->key) == block && tree->leaf) | |
192 | { | |
193 | struct grub_sfs_btree_extent *extent; | |
194 | extent = (struct grub_sfs_btree_extent *) EXTNODE (tree, i); | |
195 | ||
196 | /* We found a correct leaf. */ | |
197 | *size = grub_be_to_cpu16 (extent->size); | |
198 | *nextext = grub_be_to_cpu32 (extent->next); | |
199 | ||
200 | grub_free (treeblock); | |
201 | return 0; | |
202 | } | |
203 | ||
204 | #undef EXTNODE | |
205 | ||
206 | } | |
207 | } while (!tree->leaf); | |
208 | ||
209 | grub_free (treeblock); | |
210 | ||
211 | return grub_error (GRUB_ERR_FILE_READ_ERROR, "SFS extent not found"); | |
212 | } | |
213 | ||
887d2619 | 214 | static grub_disk_addr_t |
215 | grub_sfs_read_block (grub_fshelp_node_t node, grub_disk_addr_t fileblock) | |
9a909877 | 216 | { |
217 | int blk = node->block; | |
218 | int size = 0; | |
219 | int next = 0; | |
220 | ||
221 | while (blk) | |
222 | { | |
223 | grub_err_t err; | |
224 | ||
225 | /* In case of the first block we don't have to lookup the | |
226 | extent, the minimum size is always 1. */ | |
227 | if (fileblock == 0) | |
228 | return blk; | |
229 | ||
230 | err = grub_sfs_read_extent (node->data, blk, &size, &next); | |
231 | if (err) | |
232 | return 0; | |
233 | ||
887d2619 | 234 | if (fileblock < (unsigned int) size) |
9a909877 | 235 | return fileblock + blk; |
236 | ||
237 | fileblock -= size; | |
238 | ||
239 | blk = next; | |
240 | } | |
241 | ||
242 | grub_error (GRUB_ERR_FILE_READ_ERROR, | |
243 | "reading a SFS block outside the extent"); | |
244 | ||
245 | return 0; | |
246 | } | |
247 | ||
248 | ||
249 | /* Read LEN bytes from the file described by DATA starting with byte | |
250 | POS. Return the amount of read bytes in READ. */ | |
251 | static grub_ssize_t | |
252 | grub_sfs_read_file (grub_fshelp_node_t node, | |
9959f7db | 253 | void NESTED_FUNC_ATTR (*read_hook) (grub_disk_addr_t sector, |
9a909877 | 254 | unsigned offset, unsigned length), |
524a1e6a | 255 | int pos, grub_size_t len, char *buf) |
9a909877 | 256 | { |
257 | return grub_fshelp_read_file (node->data->disk, node, read_hook, | |
258 | pos, len, buf, grub_sfs_read_block, | |
259 | node->size, 0); | |
260 | } | |
261 | ||
262 | ||
263 | static struct grub_sfs_data * | |
264 | grub_sfs_mount (grub_disk_t disk) | |
265 | { | |
266 | struct grub_sfs_data *data; | |
267 | struct grub_sfs_objc *rootobjc; | |
268 | char *rootobjc_data = 0; | |
269 | unsigned int blk; | |
270 | ||
271 | data = grub_malloc (sizeof (*data)); | |
272 | if (!data) | |
273 | return 0; | |
274 | ||
275 | /* Read the rootblock. */ | |
276 | grub_disk_read (disk, 0, 0, sizeof (struct grub_sfs_rblock), | |
277 | (char *) &data->rblock); | |
278 | if (grub_errno) | |
279 | goto fail; | |
280 | ||
281 | /* Make sure this is a sfs filesystem. */ | |
7b455f4d | 282 | if (grub_strncmp ((char *) (data->rblock.header.magic), "SFS", 4)) |
9a909877 | 283 | { |
284 | grub_error (GRUB_ERR_BAD_FS, "not a sfs filesystem"); | |
285 | goto fail; | |
286 | } | |
287 | ||
288 | data->blocksize = grub_be_to_cpu32 (data->rblock.blocksize); | |
289 | rootobjc_data = grub_malloc (data->blocksize); | |
7b455f4d | 290 | if (! rootobjc_data) |
9a909877 | 291 | goto fail; |
292 | ||
293 | /* Read the root object container. */ | |
294 | grub_disk_read (disk, grub_be_to_cpu32 (data->rblock.rootobject), 0, | |
295 | data->blocksize, rootobjc_data); | |
296 | if (grub_errno) | |
297 | goto fail; | |
298 | ||
299 | rootobjc = (struct grub_sfs_objc *) rootobjc_data; | |
300 | ||
301 | blk = grub_be_to_cpu32 (rootobjc->objects[0].file_dir.dir.dir_objc); | |
302 | data->diropen.size = 0; | |
303 | data->diropen.block = blk; | |
304 | data->diropen.data = data; | |
305 | data->disk = disk; | |
7b455f4d | 306 | data->label = grub_strdup ((char *) (rootobjc->objects[0].filename)); |
9a909877 | 307 | |
308 | return data; | |
309 | ||
310 | fail: | |
0a203f83 | 311 | if (grub_errno == GRUB_ERR_OUT_OF_RANGE) |
312 | grub_error (GRUB_ERR_BAD_FS, "not an sfs filesystem"); | |
313 | ||
9a909877 | 314 | grub_free (data); |
315 | grub_free (rootobjc_data); | |
316 | return 0; | |
317 | } | |
318 | ||
319 | ||
320 | static char * | |
321 | grub_sfs_read_symlink (grub_fshelp_node_t node) | |
322 | { | |
323 | struct grub_sfs_data *data = node->data; | |
324 | char *symlink; | |
325 | char *block; | |
326 | ||
327 | block = grub_malloc (data->blocksize); | |
328 | if (!block) | |
329 | return 0; | |
330 | ||
331 | grub_disk_read (data->disk, node->block, 0, data->blocksize, block); | |
332 | if (grub_errno) | |
333 | { | |
334 | grub_free (block); | |
335 | return 0; | |
336 | } | |
337 | ||
338 | /* This is just a wild guess, but it always worked for me. How the | |
339 | SLNK block looks like is not documented in the SFS docs. */ | |
340 | symlink = grub_strdup (&block[24]); | |
341 | grub_free (block); | |
342 | if (!symlink) | |
343 | return 0; | |
344 | ||
345 | return symlink; | |
346 | } | |
347 | ||
348 | static int | |
349 | grub_sfs_iterate_dir (grub_fshelp_node_t dir, | |
350 | int NESTED_FUNC_ATTR | |
351 | (*hook) (const char *filename, | |
352 | enum grub_fshelp_filetype filetype, | |
353 | grub_fshelp_node_t node)) | |
354 | { | |
355 | struct grub_fshelp_node *node = 0; | |
356 | struct grub_sfs_data *data = dir->data; | |
357 | char *objc_data; | |
358 | struct grub_sfs_objc *objc; | |
359 | unsigned int next = dir->block; | |
360 | int pos; | |
361 | ||
362 | auto int NESTED_FUNC_ATTR grub_sfs_create_node (const char *name, int block, | |
363 | int size, int type); | |
364 | ||
365 | int NESTED_FUNC_ATTR grub_sfs_create_node (const char *name, int block, | |
366 | int size, int type) | |
367 | { | |
368 | node = grub_malloc (sizeof (*node)); | |
369 | if (!node) | |
370 | return 1; | |
371 | ||
372 | node->data = data; | |
373 | node->size = size; | |
374 | node->block = block; | |
375 | ||
376 | return hook (name, type, node); | |
377 | } | |
378 | ||
379 | objc_data = grub_malloc (data->blocksize); | |
380 | if (!objc_data) | |
381 | goto fail; | |
382 | ||
383 | /* The Object container can consist of multiple blocks, iterate over | |
384 | every block. */ | |
385 | while (next) | |
386 | { | |
387 | grub_disk_read (data->disk, next, 0, data->blocksize, objc_data); | |
388 | if (grub_errno) | |
389 | goto fail; | |
390 | ||
391 | objc = (struct grub_sfs_objc *) objc_data; | |
392 | ||
393 | pos = (char *) &objc->objects[0] - (char *) objc; | |
394 | ||
395 | /* Iterate over all entries in this block. */ | |
396 | while (pos + sizeof (struct grub_sfs_obj) < data->blocksize) | |
397 | { | |
398 | struct grub_sfs_obj *obj; | |
399 | obj = (struct grub_sfs_obj *) ((char *) objc + pos); | |
7b455f4d | 400 | char *filename = (char *) (obj->filename); |
9a909877 | 401 | int len; |
402 | enum grub_fshelp_filetype type; | |
403 | unsigned int block; | |
404 | ||
405 | /* The filename and comment dynamically increase the size of | |
406 | the object. */ | |
407 | len = grub_strlen (filename); | |
408 | len += grub_strlen (filename + len + 1); | |
409 | ||
410 | pos += sizeof (*obj) + len; | |
411 | /* Round up to a multiple of two bytes. */ | |
412 | pos = ((pos + 1) >> 1) << 1; | |
413 | ||
414 | if (grub_strlen (filename) == 0) | |
415 | continue; | |
416 | ||
417 | /* First check if the file was not deleted. */ | |
418 | if (obj->type & GRUB_SFS_TYPE_DELETED) | |
419 | continue; | |
420 | else if (obj->type & GRUB_SFS_TYPE_SYMLINK) | |
421 | type = GRUB_FSHELP_SYMLINK; | |
422 | else if (obj->type & GRUB_SFS_TYPE_DIR) | |
423 | type = GRUB_FSHELP_DIR; | |
424 | else | |
425 | type = GRUB_FSHELP_REG; | |
426 | ||
427 | if (type == GRUB_FSHELP_DIR) | |
428 | block = grub_be_to_cpu32 (obj->file_dir.dir.dir_objc); | |
429 | else | |
430 | block = grub_be_to_cpu32 (obj->file_dir.file.first_block); | |
431 | ||
432 | if (grub_sfs_create_node (filename, block, | |
433 | grub_be_to_cpu32 (obj->file_dir.file.size), | |
434 | type)) | |
435 | { | |
436 | grub_free (objc_data); | |
437 | return 1; | |
438 | } | |
439 | } | |
440 | ||
441 | next = grub_be_to_cpu32 (objc->next); | |
442 | } | |
443 | ||
444 | fail: | |
445 | grub_free (objc_data); | |
0c5e79ab | 446 | return 0; |
9a909877 | 447 | } |
448 | ||
449 | ||
450 | /* Open a file named NAME and initialize FILE. */ | |
451 | static grub_err_t | |
452 | grub_sfs_open (struct grub_file *file, const char *name) | |
453 | { | |
454 | struct grub_sfs_data *data; | |
455 | struct grub_fshelp_node *fdiro = 0; | |
456 | ||
457 | #ifndef GRUB_UTIL | |
458 | grub_dl_ref (my_mod); | |
459 | #endif | |
460 | ||
461 | data = grub_sfs_mount (file->device->disk); | |
462 | if (!data) | |
463 | goto fail; | |
464 | ||
465 | grub_fshelp_find_file (name, &data->diropen, &fdiro, grub_sfs_iterate_dir, | |
466 | grub_sfs_read_symlink, GRUB_FSHELP_REG); | |
467 | if (grub_errno) | |
468 | goto fail; | |
469 | ||
470 | file->size = fdiro->size; | |
471 | data->diropen = *fdiro; | |
472 | grub_free (fdiro); | |
473 | ||
474 | file->data = data; | |
475 | file->offset = 0; | |
476 | ||
477 | return 0; | |
478 | ||
479 | fail: | |
480 | if (data && fdiro != &data->diropen) | |
481 | grub_free (fdiro); | |
bb34586c | 482 | if (data) |
483 | grub_free (data->label); | |
9a909877 | 484 | grub_free (data); |
485 | ||
486 | #ifndef GRUB_UTIL | |
487 | grub_dl_unref (my_mod); | |
488 | #endif | |
489 | ||
490 | return grub_errno; | |
491 | } | |
492 | ||
493 | ||
494 | static grub_err_t | |
495 | grub_sfs_close (grub_file_t file) | |
496 | { | |
497 | grub_free (file->data); | |
498 | ||
499 | #ifndef GRUB_UTIL | |
500 | grub_dl_unref (my_mod); | |
501 | #endif | |
502 | ||
503 | return GRUB_ERR_NONE; | |
504 | } | |
505 | ||
506 | ||
507 | /* Read LEN bytes data from FILE into BUF. */ | |
508 | static grub_ssize_t | |
524a1e6a | 509 | grub_sfs_read (grub_file_t file, char *buf, grub_size_t len) |
9a909877 | 510 | { |
511 | struct grub_sfs_data *data = (struct grub_sfs_data *) file->data; | |
512 | ||
513 | int size = grub_sfs_read_file (&data->diropen, file->read_hook, | |
514 | file->offset, len, buf); | |
515 | ||
516 | return size; | |
517 | } | |
518 | ||
519 | ||
520 | static grub_err_t | |
521 | grub_sfs_dir (grub_device_t device, const char *path, | |
05aaebfb | 522 | int (*hook) (const char *filename, |
523 | const struct grub_dirhook_info *info)) | |
9a909877 | 524 | { |
525 | struct grub_sfs_data *data = 0; | |
526 | struct grub_fshelp_node *fdiro = 0; | |
527 | ||
528 | auto int NESTED_FUNC_ATTR iterate (const char *filename, | |
529 | enum grub_fshelp_filetype filetype, | |
530 | grub_fshelp_node_t node); | |
531 | ||
532 | int NESTED_FUNC_ATTR iterate (const char *filename, | |
533 | enum grub_fshelp_filetype filetype, | |
534 | grub_fshelp_node_t node) | |
535 | { | |
05aaebfb | 536 | struct grub_dirhook_info info; |
537 | grub_memset (&info, 0, sizeof (info)); | |
538 | info.dir = ((filetype & GRUB_FSHELP_TYPE_MASK) == GRUB_FSHELP_DIR); | |
9a909877 | 539 | grub_free (node); |
05aaebfb | 540 | return hook (filename, &info); |
9a909877 | 541 | } |
542 | ||
543 | #ifndef GRUB_UTIL | |
544 | grub_dl_ref (my_mod); | |
545 | #endif | |
546 | ||
547 | data = grub_sfs_mount (device->disk); | |
548 | if (!data) | |
549 | goto fail; | |
550 | ||
551 | grub_fshelp_find_file (path, &data->diropen, &fdiro, grub_sfs_iterate_dir, | |
552 | grub_sfs_read_symlink, GRUB_FSHELP_DIR); | |
553 | if (grub_errno) | |
554 | goto fail; | |
555 | ||
556 | grub_sfs_iterate_dir (fdiro, iterate); | |
557 | ||
558 | fail: | |
559 | if (data && fdiro != &data->diropen) | |
560 | grub_free (fdiro); | |
bb34586c | 561 | if (data) |
562 | grub_free (data->label); | |
9a909877 | 563 | grub_free (data); |
564 | ||
565 | #ifndef GRUB_UTIL | |
566 | grub_dl_unref (my_mod); | |
567 | #endif | |
568 | ||
569 | return grub_errno; | |
570 | } | |
571 | ||
572 | ||
573 | static grub_err_t | |
574 | grub_sfs_label (grub_device_t device, char **label) | |
575 | { | |
576 | struct grub_sfs_data *data; | |
577 | grub_disk_t disk = device->disk; | |
578 | ||
579 | data = grub_sfs_mount (disk); | |
580 | if (data) | |
581 | *label = data->label; | |
582 | ||
583 | grub_free (data); | |
584 | ||
585 | return grub_errno; | |
586 | } | |
587 | ||
588 | \f | |
589 | static struct grub_fs grub_sfs_fs = | |
590 | { | |
591 | .name = "sfs", | |
592 | .dir = grub_sfs_dir, | |
593 | .open = grub_sfs_open, | |
594 | .read = grub_sfs_read, | |
595 | .close = grub_sfs_close, | |
596 | .label = grub_sfs_label, | |
597 | .next = 0 | |
598 | }; | |
599 | ||
6d099807 | 600 | GRUB_MOD_INIT(sfs) |
9a909877 | 601 | { |
602 | grub_fs_register (&grub_sfs_fs); | |
6d099807 | 603 | #ifndef GRUB_UTIL |
9a909877 | 604 | my_mod = mod; |
6d099807 | 605 | #endif |
9a909877 | 606 | } |
607 | ||
6d099807 | 608 | GRUB_MOD_FINI(sfs) |
9a909877 | 609 | { |
610 | grub_fs_unregister (&grub_sfs_fs); | |
611 | } |