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