Upgraded GRUB2 to 2.00 release.
[AROS.git] / arch / all-pc / boot / grub2-aros / grub-core / fs / sfs.c
blobf7cdb089882dc9491237b254124aec4330262fc2
1 /* sfs.c - Amiga Smart FileSystem. */
2 /*
3 * GRUB -- GRand Unified Bootloader
4 * Copyright (C) 2005,2006,2007,2008,2009 Free Software Foundation, Inc.
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.
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.
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/>.
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 #include <grub/charset.h>
30 GRUB_MOD_LICENSE ("GPLv3+");
32 /* The common header for a block. */
33 struct grub_sfs_bheader
35 grub_uint8_t magic[4];
36 grub_uint32_t chksum;
37 grub_uint32_t ipointtomyself;
38 } __attribute__ ((packed));
40 /* The sfs rootblock. */
41 struct grub_sfs_rblock
43 struct grub_sfs_bheader header;
44 grub_uint32_t version;
45 grub_uint32_t createtime;
46 grub_uint8_t flags;
47 grub_uint8_t unused1[31];
48 grub_uint32_t blocksize;
49 grub_uint8_t unused2[40];
50 grub_uint8_t unused3[8];
51 grub_uint32_t rootobject;
52 grub_uint32_t btree;
53 } __attribute__ ((packed));
55 enum
57 FLAGS_CASE_SENSITIVE = 0x80
60 /* A SFS object container. */
61 struct grub_sfs_obj
63 grub_uint8_t unused1[4];
64 grub_uint32_t nodeid;
65 grub_uint8_t unused2[4];
66 union
68 struct
70 grub_uint32_t first_block;
71 grub_uint32_t size;
72 } file __attribute__ ((packed));
73 struct
75 grub_uint32_t hashtable;
76 grub_uint32_t dir_objc;
77 } dir __attribute__ ((packed));
78 } file_dir;
79 grub_uint32_t mtime;
80 grub_uint8_t type;
81 grub_uint8_t filename[1];
82 grub_uint8_t comment[1];
83 } __attribute__ ((packed));
85 #define GRUB_SFS_TYPE_DELETED 32
86 #define GRUB_SFS_TYPE_SYMLINK 64
87 #define GRUB_SFS_TYPE_DIR 128
89 /* A SFS object container. */
90 struct grub_sfs_objc
92 struct grub_sfs_bheader header;
93 grub_uint32_t parent;
94 grub_uint32_t next;
95 grub_uint32_t prev;
96 /* The amount of objects depends on the blocksize. */
97 struct grub_sfs_obj objects[1];
98 } __attribute__ ((packed));
100 struct grub_sfs_btree_node
102 grub_uint32_t key;
103 grub_uint32_t data;
104 } __attribute__ ((packed));
106 struct grub_sfs_btree_extent
108 grub_uint32_t key;
109 grub_uint32_t next;
110 grub_uint32_t prev;
111 grub_uint16_t size;
112 } __attribute__ ((packed));
114 struct grub_sfs_btree
116 struct grub_sfs_bheader header;
117 grub_uint16_t nodes;
118 grub_uint8_t leaf;
119 grub_uint8_t nodesize;
120 /* Normally this can be kind of node, but just extents are
121 supported. */
122 struct grub_sfs_btree_node node[1];
123 } __attribute__ ((packed));
127 struct cache_entry
129 grub_uint32_t off;
130 grub_uint32_t block;
133 struct grub_fshelp_node
135 struct grub_sfs_data *data;
136 grub_uint32_t block;
137 grub_uint32_t size;
138 grub_uint32_t mtime;
139 grub_uint32_t cache_off;
140 grub_uint32_t next_extent;
141 grub_size_t cache_allocated;
142 grub_size_t cache_size;
143 struct cache_entry *cache;
146 /* Information about a "mounted" sfs filesystem. */
147 struct grub_sfs_data
149 struct grub_sfs_rblock rblock;
150 struct grub_fshelp_node diropen;
151 grub_disk_t disk;
153 /* Log of blocksize in sectors. */
154 int log_blocksize;
156 int fshelp_flags;
158 /* Label of the filesystem. */
159 char *label;
162 static grub_dl_t my_mod;
165 /* Lookup the extent starting with BLOCK in the filesystem described
166 by DATA. Return the extent size in SIZE and the following extent
167 in NEXTEXT. */
168 static grub_err_t
169 grub_sfs_read_extent (struct grub_sfs_data *data, unsigned int block,
170 grub_uint32_t *size, grub_uint32_t *nextext)
172 char *treeblock;
173 struct grub_sfs_btree *tree;
174 int i;
175 grub_uint32_t next;
177 treeblock = grub_malloc (GRUB_DISK_SECTOR_SIZE << data->log_blocksize);
178 if (!block)
179 return 0;
181 next = grub_be_to_cpu32 (data->rblock.btree);
182 tree = (struct grub_sfs_btree *) treeblock;
184 /* Handle this level in the btree. */
187 grub_disk_read (data->disk,
188 ((grub_disk_addr_t) next) << data->log_blocksize,
189 0, GRUB_DISK_SECTOR_SIZE << data->log_blocksize,
190 treeblock);
191 if (grub_errno)
193 grub_free (treeblock);
194 return grub_errno;
197 for (i = grub_be_to_cpu16 (tree->nodes) - 1; i >= 0; i--)
200 #define EXTNODE(tree, index) \
201 ((struct grub_sfs_btree_node *) (((char *) &(tree)->node[0]) \
202 + (index) * (tree)->nodesize))
204 /* Follow the tree down to the leaf level. */
205 if ((grub_be_to_cpu32 (EXTNODE(tree, i)->key) <= block)
206 && !tree->leaf)
208 next = grub_be_to_cpu32 (EXTNODE (tree, i)->data);
209 break;
212 /* If the leaf level is reached, just find the correct extent. */
213 if (grub_be_to_cpu32 (EXTNODE (tree, i)->key) == block && tree->leaf)
215 struct grub_sfs_btree_extent *extent;
216 extent = (struct grub_sfs_btree_extent *) EXTNODE (tree, i);
218 /* We found a correct leaf. */
219 *size = grub_be_to_cpu16 (extent->size);
220 *nextext = grub_be_to_cpu32 (extent->next);
222 grub_free (treeblock);
223 return 0;
226 #undef EXTNODE
229 } while (!tree->leaf);
231 grub_free (treeblock);
233 return grub_error (GRUB_ERR_FILE_READ_ERROR, "SFS extent not found");
236 static grub_disk_addr_t
237 grub_sfs_read_block (grub_fshelp_node_t node, grub_disk_addr_t fileblock)
239 grub_uint32_t blk;
240 grub_uint32_t size = 0;
241 grub_uint32_t next = 0;
242 grub_disk_addr_t off;
243 struct grub_sfs_data *data = node->data;
245 /* In case of the first block we don't have to lookup the
246 extent, the minimum size is always 1. */
247 if (fileblock == 0)
248 return node->block;
250 if (!node->cache)
252 grub_size_t cache_size;
253 /* Assume half-max extents (32768 sectors). */
254 cache_size = ((node->size >> (data->log_blocksize + GRUB_DISK_SECTOR_BITS
255 + 15))
256 + 3);
257 if (cache_size < 8)
258 cache_size = 8;
260 node->cache_off = 0;
261 node->next_extent = node->block;
262 node->cache_size = 0;
264 node->cache = grub_malloc (sizeof (node->cache[0]) * cache_size);
265 if (!node->cache)
267 grub_errno = 0;
268 node->cache_allocated = 0;
270 else
272 node->cache_allocated = cache_size;
273 node->cache[0].off = 0;
274 node->cache[0].block = node->block;
278 if (fileblock < node->cache_off)
280 unsigned int i = 0;
281 int j, lg;
282 for (lg = 0; node->cache_size >> lg; lg++);
284 for (j = lg - 1; j >= 0; j--)
285 if ((i | (1 << j)) < node->cache_size
286 && node->cache[(i | (1 << j))].off <= fileblock)
287 i |= (1 << j);
288 return node->cache[i].block + fileblock - node->cache[i].off;
291 off = node->cache_off;
292 blk = node->next_extent;
294 while (blk)
296 grub_err_t err;
298 err = grub_sfs_read_extent (node->data, blk, &size, &next);
299 if (err)
300 return 0;
302 if (node->cache && node->cache_size >= node->cache_allocated)
304 struct cache_entry *e = node->cache;
305 e = grub_realloc (node->cache,node->cache_allocated * 2
306 * sizeof (e[0]));
307 if (!e)
309 grub_errno = 0;
310 grub_free (node->cache);
311 node->cache = 0;
313 else
315 node->cache_allocated *= 2;
316 node->cache = e;
320 if (node->cache)
322 node->cache_off = off + size;
323 node->next_extent = next;
324 node->cache[node->cache_size].off = off;
325 node->cache[node->cache_size].block = blk;
326 node->cache_size++;
329 if (fileblock - off < size)
330 return fileblock - off + blk;
332 off += size;
334 blk = next;
337 grub_error (GRUB_ERR_FILE_READ_ERROR,
338 "reading a SFS block outside the extent");
340 return 0;
344 /* Read LEN bytes from the file described by DATA starting with byte
345 POS. Return the amount of read bytes in READ. */
346 static grub_ssize_t
347 grub_sfs_read_file (grub_fshelp_node_t node,
348 void NESTED_FUNC_ATTR (*read_hook) (grub_disk_addr_t sector,
349 unsigned offset, unsigned length),
350 grub_off_t pos, grub_size_t len, char *buf)
352 return grub_fshelp_read_file (node->data->disk, node, read_hook,
353 pos, len, buf, grub_sfs_read_block,
354 node->size, node->data->log_blocksize, 0);
358 static struct grub_sfs_data *
359 grub_sfs_mount (grub_disk_t disk)
361 struct grub_sfs_data *data;
362 struct grub_sfs_objc *rootobjc;
363 char *rootobjc_data = 0;
364 grub_uint32_t blk;
366 data = grub_malloc (sizeof (*data));
367 if (!data)
368 return 0;
370 /* Read the rootblock. */
371 grub_disk_read (disk, 0, 0, sizeof (struct grub_sfs_rblock),
372 &data->rblock);
373 if (grub_errno)
374 goto fail;
376 /* Make sure this is a sfs filesystem. */
377 if (grub_strncmp ((char *) (data->rblock.header.magic), "SFS", 4)
378 || data->rblock.blocksize == 0
379 || (data->rblock.blocksize & (data->rblock.blocksize - 1)) != 0
380 || (data->rblock.blocksize & grub_cpu_to_be32_compile_time (0xf00001ff)))
382 grub_error (GRUB_ERR_BAD_FS, "not a SFS filesystem");
383 goto fail;
386 for (data->log_blocksize = 9;
387 (1U << data->log_blocksize) < grub_be_to_cpu32 (data->rblock.blocksize);
388 data->log_blocksize++);
389 data->log_blocksize -= GRUB_DISK_SECTOR_BITS;
390 if (data->rblock.flags & FLAGS_CASE_SENSITIVE)
391 data->fshelp_flags = 0;
392 else
393 data->fshelp_flags = GRUB_FSHELP_CASE_INSENSITIVE;
394 rootobjc_data = grub_malloc (GRUB_DISK_SECTOR_SIZE << data->log_blocksize);
395 if (! rootobjc_data)
396 goto fail;
398 /* Read the root object container. */
399 grub_disk_read (disk, ((grub_disk_addr_t) grub_be_to_cpu32 (data->rblock.rootobject))
400 << data->log_blocksize, 0,
401 GRUB_DISK_SECTOR_SIZE << data->log_blocksize, rootobjc_data);
402 if (grub_errno)
403 goto fail;
405 rootobjc = (struct grub_sfs_objc *) rootobjc_data;
407 blk = grub_be_to_cpu32 (rootobjc->objects[0].file_dir.dir.dir_objc);
408 data->diropen.size = 0;
409 data->diropen.block = blk;
410 data->diropen.data = data;
411 data->diropen.cache = 0;
412 data->disk = disk;
413 data->label = grub_strdup ((char *) (rootobjc->objects[0].filename));
415 grub_free (rootobjc_data);
416 return data;
418 fail:
419 if (grub_errno == GRUB_ERR_OUT_OF_RANGE)
420 grub_error (GRUB_ERR_BAD_FS, "not an SFS filesystem");
422 grub_free (data);
423 grub_free (rootobjc_data);
424 return 0;
428 static char *
429 grub_sfs_read_symlink (grub_fshelp_node_t node)
431 struct grub_sfs_data *data = node->data;
432 char *symlink;
433 char *block;
435 block = grub_malloc (GRUB_DISK_SECTOR_SIZE << data->log_blocksize);
436 if (!block)
437 return 0;
439 grub_disk_read (data->disk, ((grub_disk_addr_t) node->block)
440 << data->log_blocksize,
441 0, GRUB_DISK_SECTOR_SIZE << data->log_blocksize, block);
442 if (grub_errno)
444 grub_free (block);
445 return 0;
448 /* This is just a wild guess, but it always worked for me. How the
449 SLNK block looks like is not documented in the SFS docs. */
450 symlink = grub_malloc (((GRUB_DISK_SECTOR_SIZE << data->log_blocksize)
451 - 24) * GRUB_MAX_UTF8_PER_LATIN1 + 1);
452 if (!symlink)
454 grub_free (block);
455 return 0;
457 *grub_latin1_to_utf8 ((grub_uint8_t *) symlink, (grub_uint8_t *) &block[24],
458 (GRUB_DISK_SECTOR_SIZE << data->log_blocksize) - 24) = '\0';
459 grub_free (block);
460 return symlink;
463 static int
464 grub_sfs_iterate_dir (grub_fshelp_node_t dir,
465 int NESTED_FUNC_ATTR
466 (*hook) (const char *filename,
467 enum grub_fshelp_filetype filetype,
468 grub_fshelp_node_t node))
470 struct grub_fshelp_node *node = 0;
471 struct grub_sfs_data *data = dir->data;
472 char *objc_data;
473 struct grub_sfs_objc *objc;
474 unsigned int next = dir->block;
475 grub_uint32_t pos;
477 auto int NESTED_FUNC_ATTR grub_sfs_create_node (const char *name,
478 grub_uint32_t block,
479 grub_uint32_t size, int type,
480 grub_uint32_t mtime);
482 int NESTED_FUNC_ATTR grub_sfs_create_node (const char *name,
483 grub_uint32_t block,
484 grub_uint32_t size, int type,
485 grub_uint32_t mtime)
487 grub_size_t len = grub_strlen (name);
488 grub_uint8_t *name_u8;
489 int ret;
490 node = grub_malloc (sizeof (*node));
491 if (!node)
492 return 1;
493 name_u8 = grub_malloc (len * GRUB_MAX_UTF8_PER_LATIN1 + 1);
494 if (!name_u8)
496 grub_free (node);
497 return 1;
500 node->data = data;
501 node->size = size;
502 node->block = block;
503 node->mtime = mtime;
504 node->cache = 0;
505 node->cache_off = 0;
506 node->next_extent = block;
507 node->cache_size = 0;
508 node->cache_allocated = 0;
510 *grub_latin1_to_utf8 (name_u8, (const grub_uint8_t *) name, len) = '\0';
512 ret = hook ((char *) name_u8, type | data->fshelp_flags, node);
513 grub_free (name_u8);
514 return ret;
517 objc_data = grub_malloc (GRUB_DISK_SECTOR_SIZE << data->log_blocksize);
518 if (!objc_data)
519 goto fail;
521 /* The Object container can consist of multiple blocks, iterate over
522 every block. */
523 while (next)
525 grub_disk_read (data->disk, ((grub_disk_addr_t) next)
526 << data->log_blocksize, 0,
527 GRUB_DISK_SECTOR_SIZE << data->log_blocksize, objc_data);
528 if (grub_errno)
529 goto fail;
531 objc = (struct grub_sfs_objc *) objc_data;
533 pos = (char *) &objc->objects[0] - (char *) objc;
535 /* Iterate over all entries in this block. */
536 while (pos + sizeof (struct grub_sfs_obj)
537 < (1U << (GRUB_DISK_SECTOR_BITS + data->log_blocksize)))
539 struct grub_sfs_obj *obj;
540 obj = (struct grub_sfs_obj *) ((char *) objc + pos);
541 const char *filename = (const char *) obj->filename;
542 grub_size_t len;
543 enum grub_fshelp_filetype type;
544 grub_uint32_t block;
546 /* The filename and comment dynamically increase the size of
547 the object. */
548 len = grub_strlen (filename);
549 len += grub_strlen (filename + len + 1);
551 pos += sizeof (*obj) + len;
552 /* Round up to a multiple of two bytes. */
553 pos = ((pos + 1) >> 1) << 1;
555 if (filename[0] == 0)
556 continue;
558 /* First check if the file was not deleted. */
559 if (obj->type & GRUB_SFS_TYPE_DELETED)
560 continue;
561 else if (obj->type & GRUB_SFS_TYPE_SYMLINK)
562 type = GRUB_FSHELP_SYMLINK;
563 else if (obj->type & GRUB_SFS_TYPE_DIR)
564 type = GRUB_FSHELP_DIR;
565 else
566 type = GRUB_FSHELP_REG;
568 if (type == GRUB_FSHELP_DIR)
569 block = grub_be_to_cpu32 (obj->file_dir.dir.dir_objc);
570 else
571 block = grub_be_to_cpu32 (obj->file_dir.file.first_block);
573 if (grub_sfs_create_node (filename, block,
574 grub_be_to_cpu32 (obj->file_dir.file.size),
575 type, grub_be_to_cpu32 (obj->mtime)))
577 grub_free (objc_data);
578 return 1;
582 next = grub_be_to_cpu32 (objc->next);
585 fail:
586 grub_free (objc_data);
587 return 0;
591 /* Open a file named NAME and initialize FILE. */
592 static grub_err_t
593 grub_sfs_open (struct grub_file *file, const char *name)
595 struct grub_sfs_data *data;
596 struct grub_fshelp_node *fdiro = 0;
598 grub_dl_ref (my_mod);
600 data = grub_sfs_mount (file->device->disk);
601 if (!data)
602 goto fail;
604 grub_fshelp_find_file (name, &data->diropen, &fdiro, grub_sfs_iterate_dir,
605 grub_sfs_read_symlink, GRUB_FSHELP_REG);
606 if (grub_errno)
607 goto fail;
609 file->size = fdiro->size;
610 data->diropen = *fdiro;
611 grub_free (fdiro);
613 file->data = data;
614 file->offset = 0;
616 return 0;
618 fail:
619 if (data && fdiro != &data->diropen)
620 grub_free (fdiro);
621 if (data)
622 grub_free (data->label);
623 grub_free (data);
625 grub_dl_unref (my_mod);
627 return grub_errno;
631 static grub_err_t
632 grub_sfs_close (grub_file_t file)
634 struct grub_sfs_data *data = (struct grub_sfs_data *) file->data;
636 grub_free (data->diropen.cache);
637 grub_free (data->label);
638 grub_free (data);
640 grub_dl_unref (my_mod);
642 return GRUB_ERR_NONE;
646 /* Read LEN bytes data from FILE into BUF. */
647 static grub_ssize_t
648 grub_sfs_read (grub_file_t file, char *buf, grub_size_t len)
650 struct grub_sfs_data *data = (struct grub_sfs_data *) file->data;
652 return grub_sfs_read_file (&data->diropen, file->read_hook,
653 file->offset, len, buf);
657 static grub_err_t
658 grub_sfs_dir (grub_device_t device, const char *path,
659 int (*hook) (const char *filename,
660 const struct grub_dirhook_info *info))
662 struct grub_sfs_data *data = 0;
663 struct grub_fshelp_node *fdiro = 0;
665 auto int NESTED_FUNC_ATTR iterate (const char *filename,
666 enum grub_fshelp_filetype filetype,
667 grub_fshelp_node_t node);
669 int NESTED_FUNC_ATTR iterate (const char *filename,
670 enum grub_fshelp_filetype filetype,
671 grub_fshelp_node_t node)
673 struct grub_dirhook_info info;
674 grub_memset (&info, 0, sizeof (info));
675 info.dir = ((filetype & GRUB_FSHELP_TYPE_MASK) == GRUB_FSHELP_DIR);
676 info.mtime = node->mtime + 8 * 365 * 86400 + 86400 * 2;
677 info.mtimeset = 1;
678 grub_free (node->cache);
679 grub_free (node);
680 return hook (filename, &info);
683 grub_dl_ref (my_mod);
685 data = grub_sfs_mount (device->disk);
686 if (!data)
687 goto fail;
689 grub_fshelp_find_file (path, &data->diropen, &fdiro, grub_sfs_iterate_dir,
690 grub_sfs_read_symlink, GRUB_FSHELP_DIR);
691 if (grub_errno)
692 goto fail;
694 grub_sfs_iterate_dir (fdiro, iterate);
696 fail:
697 if (data && fdiro != &data->diropen)
698 grub_free (fdiro);
699 if (data)
700 grub_free (data->label);
701 grub_free (data);
703 grub_dl_unref (my_mod);
705 return grub_errno;
709 static grub_err_t
710 grub_sfs_label (grub_device_t device, char **label)
712 struct grub_sfs_data *data;
713 grub_disk_t disk = device->disk;
715 data = grub_sfs_mount (disk);
716 if (data)
718 grub_size_t len = grub_strlen (data->label);
719 *label = grub_malloc (len * GRUB_MAX_UTF8_PER_LATIN1 + 1);
720 if (*label)
721 *grub_latin1_to_utf8 ((grub_uint8_t *) *label,
722 (const grub_uint8_t *) data->label,
723 len) = '\0';
724 grub_free (data->label);
726 grub_free (data);
728 return grub_errno;
732 static struct grub_fs grub_sfs_fs =
734 .name = "sfs",
735 .dir = grub_sfs_dir,
736 .open = grub_sfs_open,
737 .read = grub_sfs_read,
738 .close = grub_sfs_close,
739 .label = grub_sfs_label,
740 #ifdef GRUB_UTIL
741 .reserved_first_sector = 0,
742 .blocklist_install = 1,
743 #endif
744 .next = 0
747 GRUB_MOD_INIT(sfs)
749 grub_fs_register (&grub_sfs_fs);
750 my_mod = mod;
753 GRUB_MOD_FINI(sfs)
755 grub_fs_unregister (&grub_sfs_fs);