]>
Commit | Line | Data |
---|---|---|
9a909877 | 1 | /* sfs.c - Amiga Smart FileSystem. */ |
2 | /* | |
3 | * GRUB -- GRand Unified Bootloader | |
5a79f472 | 4 | * Copyright (C) 2005,2006,2007 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 | ||
175 | for (i = 0; i < tree->nodes; i++) | |
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. */ | |
183 | if ((grub_be_to_cpu32 (EXTNODE(tree, i)->key) >= block) | |
184 | && !tree->leaf) | |
185 | { | |
186 | next = grub_be_to_cpu32 (EXTNODE (tree, i - 1)->data); | |
187 | break; | |
188 | } | |
189 | ||
190 | /* In case the last node is reached just use that one, it is | |
191 | the right match. */ | |
192 | if (i + 1 == tree->nodes && !tree->leaf) | |
193 | { | |
194 | next = grub_be_to_cpu32 (EXTNODE (tree, i)->data); | |
195 | break; | |
196 | } | |
197 | ||
198 | /* If the leaf level is reached, just find the correct extent. */ | |
199 | if (grub_be_to_cpu32 (EXTNODE (tree, i)->key) == block && tree->leaf) | |
200 | { | |
201 | struct grub_sfs_btree_extent *extent; | |
202 | extent = (struct grub_sfs_btree_extent *) EXTNODE (tree, i); | |
203 | ||
204 | /* We found a correct leaf. */ | |
205 | *size = grub_be_to_cpu16 (extent->size); | |
206 | *nextext = grub_be_to_cpu32 (extent->next); | |
207 | ||
208 | grub_free (treeblock); | |
209 | return 0; | |
210 | } | |
211 | ||
212 | #undef EXTNODE | |
213 | ||
214 | } | |
215 | } while (!tree->leaf); | |
216 | ||
217 | grub_free (treeblock); | |
218 | ||
219 | return grub_error (GRUB_ERR_FILE_READ_ERROR, "SFS extent not found"); | |
220 | } | |
221 | ||
887d2619 | 222 | static grub_disk_addr_t |
223 | grub_sfs_read_block (grub_fshelp_node_t node, grub_disk_addr_t fileblock) | |
9a909877 | 224 | { |
225 | int blk = node->block; | |
226 | int size = 0; | |
227 | int next = 0; | |
228 | ||
229 | while (blk) | |
230 | { | |
231 | grub_err_t err; | |
232 | ||
233 | /* In case of the first block we don't have to lookup the | |
234 | extent, the minimum size is always 1. */ | |
235 | if (fileblock == 0) | |
236 | return blk; | |
237 | ||
238 | err = grub_sfs_read_extent (node->data, blk, &size, &next); | |
239 | if (err) | |
240 | return 0; | |
241 | ||
887d2619 | 242 | if (fileblock < (unsigned int) size) |
9a909877 | 243 | return fileblock + blk; |
244 | ||
245 | fileblock -= size; | |
246 | ||
247 | blk = next; | |
248 | } | |
249 | ||
250 | grub_error (GRUB_ERR_FILE_READ_ERROR, | |
251 | "reading a SFS block outside the extent"); | |
252 | ||
253 | return 0; | |
254 | } | |
255 | ||
256 | ||
257 | /* Read LEN bytes from the file described by DATA starting with byte | |
258 | POS. Return the amount of read bytes in READ. */ | |
259 | static grub_ssize_t | |
260 | grub_sfs_read_file (grub_fshelp_node_t node, | |
9959f7db | 261 | void NESTED_FUNC_ATTR (*read_hook) (grub_disk_addr_t sector, |
9a909877 | 262 | unsigned offset, unsigned length), |
524a1e6a | 263 | int pos, grub_size_t len, char *buf) |
9a909877 | 264 | { |
265 | return grub_fshelp_read_file (node->data->disk, node, read_hook, | |
266 | pos, len, buf, grub_sfs_read_block, | |
267 | node->size, 0); | |
268 | } | |
269 | ||
270 | ||
271 | static struct grub_sfs_data * | |
272 | grub_sfs_mount (grub_disk_t disk) | |
273 | { | |
274 | struct grub_sfs_data *data; | |
275 | struct grub_sfs_objc *rootobjc; | |
276 | char *rootobjc_data = 0; | |
277 | unsigned int blk; | |
278 | ||
279 | data = grub_malloc (sizeof (*data)); | |
280 | if (!data) | |
281 | return 0; | |
282 | ||
283 | /* Read the rootblock. */ | |
284 | grub_disk_read (disk, 0, 0, sizeof (struct grub_sfs_rblock), | |
285 | (char *) &data->rblock); | |
286 | if (grub_errno) | |
287 | goto fail; | |
288 | ||
289 | /* Make sure this is a sfs filesystem. */ | |
7b455f4d | 290 | if (grub_strncmp ((char *) (data->rblock.header.magic), "SFS", 4)) |
9a909877 | 291 | { |
292 | grub_error (GRUB_ERR_BAD_FS, "not a sfs filesystem"); | |
293 | goto fail; | |
294 | } | |
295 | ||
296 | data->blocksize = grub_be_to_cpu32 (data->rblock.blocksize); | |
297 | rootobjc_data = grub_malloc (data->blocksize); | |
7b455f4d | 298 | if (! rootobjc_data) |
9a909877 | 299 | goto fail; |
300 | ||
301 | /* Read the root object container. */ | |
302 | grub_disk_read (disk, grub_be_to_cpu32 (data->rblock.rootobject), 0, | |
303 | data->blocksize, rootobjc_data); | |
304 | if (grub_errno) | |
305 | goto fail; | |
306 | ||
307 | rootobjc = (struct grub_sfs_objc *) rootobjc_data; | |
308 | ||
309 | blk = grub_be_to_cpu32 (rootobjc->objects[0].file_dir.dir.dir_objc); | |
310 | data->diropen.size = 0; | |
311 | data->diropen.block = blk; | |
312 | data->diropen.data = data; | |
313 | data->disk = disk; | |
7b455f4d | 314 | data->label = grub_strdup ((char *) (rootobjc->objects[0].filename)); |
9a909877 | 315 | |
316 | return data; | |
317 | ||
318 | fail: | |
0a203f83 | 319 | if (grub_errno == GRUB_ERR_OUT_OF_RANGE) |
320 | grub_error (GRUB_ERR_BAD_FS, "not an sfs filesystem"); | |
321 | ||
9a909877 | 322 | grub_free (data); |
323 | grub_free (rootobjc_data); | |
324 | return 0; | |
325 | } | |
326 | ||
327 | ||
328 | static char * | |
329 | grub_sfs_read_symlink (grub_fshelp_node_t node) | |
330 | { | |
331 | struct grub_sfs_data *data = node->data; | |
332 | char *symlink; | |
333 | char *block; | |
334 | ||
335 | block = grub_malloc (data->blocksize); | |
336 | if (!block) | |
337 | return 0; | |
338 | ||
339 | grub_disk_read (data->disk, node->block, 0, data->blocksize, block); | |
340 | if (grub_errno) | |
341 | { | |
342 | grub_free (block); | |
343 | return 0; | |
344 | } | |
345 | ||
346 | /* This is just a wild guess, but it always worked for me. How the | |
347 | SLNK block looks like is not documented in the SFS docs. */ | |
348 | symlink = grub_strdup (&block[24]); | |
349 | grub_free (block); | |
350 | if (!symlink) | |
351 | return 0; | |
352 | ||
353 | return symlink; | |
354 | } | |
355 | ||
356 | static int | |
357 | grub_sfs_iterate_dir (grub_fshelp_node_t dir, | |
358 | int NESTED_FUNC_ATTR | |
359 | (*hook) (const char *filename, | |
360 | enum grub_fshelp_filetype filetype, | |
361 | grub_fshelp_node_t node)) | |
362 | { | |
363 | struct grub_fshelp_node *node = 0; | |
364 | struct grub_sfs_data *data = dir->data; | |
365 | char *objc_data; | |
366 | struct grub_sfs_objc *objc; | |
367 | unsigned int next = dir->block; | |
368 | int pos; | |
369 | ||
370 | auto int NESTED_FUNC_ATTR grub_sfs_create_node (const char *name, int block, | |
371 | int size, int type); | |
372 | ||
373 | int NESTED_FUNC_ATTR grub_sfs_create_node (const char *name, int block, | |
374 | int size, int type) | |
375 | { | |
376 | node = grub_malloc (sizeof (*node)); | |
377 | if (!node) | |
378 | return 1; | |
379 | ||
380 | node->data = data; | |
381 | node->size = size; | |
382 | node->block = block; | |
383 | ||
384 | return hook (name, type, node); | |
385 | } | |
386 | ||
387 | objc_data = grub_malloc (data->blocksize); | |
388 | if (!objc_data) | |
389 | goto fail; | |
390 | ||
391 | /* The Object container can consist of multiple blocks, iterate over | |
392 | every block. */ | |
393 | while (next) | |
394 | { | |
395 | grub_disk_read (data->disk, next, 0, data->blocksize, objc_data); | |
396 | if (grub_errno) | |
397 | goto fail; | |
398 | ||
399 | objc = (struct grub_sfs_objc *) objc_data; | |
400 | ||
401 | pos = (char *) &objc->objects[0] - (char *) objc; | |
402 | ||
403 | /* Iterate over all entries in this block. */ | |
404 | while (pos + sizeof (struct grub_sfs_obj) < data->blocksize) | |
405 | { | |
406 | struct grub_sfs_obj *obj; | |
407 | obj = (struct grub_sfs_obj *) ((char *) objc + pos); | |
7b455f4d | 408 | char *filename = (char *) (obj->filename); |
9a909877 | 409 | int len; |
410 | enum grub_fshelp_filetype type; | |
411 | unsigned int block; | |
412 | ||
413 | /* The filename and comment dynamically increase the size of | |
414 | the object. */ | |
415 | len = grub_strlen (filename); | |
416 | len += grub_strlen (filename + len + 1); | |
417 | ||
418 | pos += sizeof (*obj) + len; | |
419 | /* Round up to a multiple of two bytes. */ | |
420 | pos = ((pos + 1) >> 1) << 1; | |
421 | ||
422 | if (grub_strlen (filename) == 0) | |
423 | continue; | |
424 | ||
425 | /* First check if the file was not deleted. */ | |
426 | if (obj->type & GRUB_SFS_TYPE_DELETED) | |
427 | continue; | |
428 | else if (obj->type & GRUB_SFS_TYPE_SYMLINK) | |
429 | type = GRUB_FSHELP_SYMLINK; | |
430 | else if (obj->type & GRUB_SFS_TYPE_DIR) | |
431 | type = GRUB_FSHELP_DIR; | |
432 | else | |
433 | type = GRUB_FSHELP_REG; | |
434 | ||
435 | if (type == GRUB_FSHELP_DIR) | |
436 | block = grub_be_to_cpu32 (obj->file_dir.dir.dir_objc); | |
437 | else | |
438 | block = grub_be_to_cpu32 (obj->file_dir.file.first_block); | |
439 | ||
440 | if (grub_sfs_create_node (filename, block, | |
441 | grub_be_to_cpu32 (obj->file_dir.file.size), | |
442 | type)) | |
443 | { | |
444 | grub_free (objc_data); | |
445 | return 1; | |
446 | } | |
447 | } | |
448 | ||
449 | next = grub_be_to_cpu32 (objc->next); | |
450 | } | |
451 | ||
452 | fail: | |
453 | grub_free (objc_data); | |
454 | return 1; | |
455 | } | |
456 | ||
457 | ||
458 | /* Open a file named NAME and initialize FILE. */ | |
459 | static grub_err_t | |
460 | grub_sfs_open (struct grub_file *file, const char *name) | |
461 | { | |
462 | struct grub_sfs_data *data; | |
463 | struct grub_fshelp_node *fdiro = 0; | |
464 | ||
465 | #ifndef GRUB_UTIL | |
466 | grub_dl_ref (my_mod); | |
467 | #endif | |
468 | ||
469 | data = grub_sfs_mount (file->device->disk); | |
470 | if (!data) | |
471 | goto fail; | |
472 | ||
473 | grub_fshelp_find_file (name, &data->diropen, &fdiro, grub_sfs_iterate_dir, | |
474 | grub_sfs_read_symlink, GRUB_FSHELP_REG); | |
475 | if (grub_errno) | |
476 | goto fail; | |
477 | ||
478 | file->size = fdiro->size; | |
479 | data->diropen = *fdiro; | |
480 | grub_free (fdiro); | |
481 | ||
482 | file->data = data; | |
483 | file->offset = 0; | |
484 | ||
485 | return 0; | |
486 | ||
487 | fail: | |
488 | if (data && fdiro != &data->diropen) | |
489 | grub_free (fdiro); | |
bb34586c | 490 | if (data) |
491 | grub_free (data->label); | |
9a909877 | 492 | grub_free (data); |
493 | ||
494 | #ifndef GRUB_UTIL | |
495 | grub_dl_unref (my_mod); | |
496 | #endif | |
497 | ||
498 | return grub_errno; | |
499 | } | |
500 | ||
501 | ||
502 | static grub_err_t | |
503 | grub_sfs_close (grub_file_t file) | |
504 | { | |
505 | grub_free (file->data); | |
506 | ||
507 | #ifndef GRUB_UTIL | |
508 | grub_dl_unref (my_mod); | |
509 | #endif | |
510 | ||
511 | return GRUB_ERR_NONE; | |
512 | } | |
513 | ||
514 | ||
515 | /* Read LEN bytes data from FILE into BUF. */ | |
516 | static grub_ssize_t | |
524a1e6a | 517 | grub_sfs_read (grub_file_t file, char *buf, grub_size_t len) |
9a909877 | 518 | { |
519 | struct grub_sfs_data *data = (struct grub_sfs_data *) file->data; | |
520 | ||
521 | int size = grub_sfs_read_file (&data->diropen, file->read_hook, | |
522 | file->offset, len, buf); | |
523 | ||
524 | return size; | |
525 | } | |
526 | ||
527 | ||
528 | static grub_err_t | |
529 | grub_sfs_dir (grub_device_t device, const char *path, | |
530 | int (*hook) (const char *filename, int dir)) | |
531 | { | |
532 | struct grub_sfs_data *data = 0; | |
533 | struct grub_fshelp_node *fdiro = 0; | |
534 | ||
535 | auto int NESTED_FUNC_ATTR iterate (const char *filename, | |
536 | enum grub_fshelp_filetype filetype, | |
537 | grub_fshelp_node_t node); | |
538 | ||
539 | int NESTED_FUNC_ATTR iterate (const char *filename, | |
540 | enum grub_fshelp_filetype filetype, | |
541 | grub_fshelp_node_t node) | |
542 | { | |
543 | grub_free (node); | |
544 | ||
545 | if (filetype == GRUB_FSHELP_DIR) | |
546 | return hook (filename, 1); | |
547 | else | |
548 | return hook (filename, 0); | |
549 | ||
550 | return 0; | |
551 | } | |
552 | ||
553 | #ifndef GRUB_UTIL | |
554 | grub_dl_ref (my_mod); | |
555 | #endif | |
556 | ||
557 | data = grub_sfs_mount (device->disk); | |
558 | if (!data) | |
559 | goto fail; | |
560 | ||
561 | grub_fshelp_find_file (path, &data->diropen, &fdiro, grub_sfs_iterate_dir, | |
562 | grub_sfs_read_symlink, GRUB_FSHELP_DIR); | |
563 | if (grub_errno) | |
564 | goto fail; | |
565 | ||
566 | grub_sfs_iterate_dir (fdiro, iterate); | |
567 | ||
568 | fail: | |
569 | if (data && fdiro != &data->diropen) | |
570 | grub_free (fdiro); | |
bb34586c | 571 | if (data) |
572 | grub_free (data->label); | |
9a909877 | 573 | grub_free (data); |
574 | ||
575 | #ifndef GRUB_UTIL | |
576 | grub_dl_unref (my_mod); | |
577 | #endif | |
578 | ||
579 | return grub_errno; | |
580 | } | |
581 | ||
582 | ||
583 | static grub_err_t | |
584 | grub_sfs_label (grub_device_t device, char **label) | |
585 | { | |
586 | struct grub_sfs_data *data; | |
587 | grub_disk_t disk = device->disk; | |
588 | ||
589 | data = grub_sfs_mount (disk); | |
590 | if (data) | |
591 | *label = data->label; | |
592 | ||
593 | grub_free (data); | |
594 | ||
595 | return grub_errno; | |
596 | } | |
597 | ||
598 | \f | |
599 | static struct grub_fs grub_sfs_fs = | |
600 | { | |
601 | .name = "sfs", | |
602 | .dir = grub_sfs_dir, | |
603 | .open = grub_sfs_open, | |
604 | .read = grub_sfs_read, | |
605 | .close = grub_sfs_close, | |
606 | .label = grub_sfs_label, | |
607 | .next = 0 | |
608 | }; | |
609 | ||
6d099807 | 610 | GRUB_MOD_INIT(sfs) |
9a909877 | 611 | { |
612 | grub_fs_register (&grub_sfs_fs); | |
6d099807 | 613 | #ifndef GRUB_UTIL |
9a909877 | 614 | my_mod = mod; |
6d099807 | 615 | #endif |
9a909877 | 616 | } |
617 | ||
6d099807 | 618 | GRUB_MOD_FINI(sfs) |
9a909877 | 619 | { |
620 | grub_fs_unregister (&grub_sfs_fs); | |
621 | } |