Upgraded GRUB2 to 2.00 release.
[AROS.git] / arch / all-pc / boot / grub2-aros / grub-core / fs / affs.c
blobef654791c86e78226fa0b0c4d5ebd61cbe24576e
1 /* affs.c - Amiga Fast 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 affs bootblock. */
33 struct grub_affs_bblock
35 grub_uint8_t type[3];
36 grub_uint8_t flags;
37 grub_uint32_t checksum;
38 grub_uint32_t rootblock;
39 } __attribute__ ((packed));
41 /* Set if the filesystem is a AFFS filesystem. Otherwise this is an
42 OFS filesystem. */
43 #define GRUB_AFFS_FLAG_FFS 1
45 /* The affs rootblock. */
46 struct grub_affs_rblock
48 grub_uint32_t type;
49 grub_uint8_t unused1[8];
50 grub_uint32_t htsize;
51 grub_uint32_t unused2;
52 grub_uint32_t checksum;
53 grub_uint32_t hashtable[1];
54 } __attribute__ ((packed));
56 struct grub_affs_time
58 grub_int32_t day;
59 grub_uint32_t min;
60 grub_uint32_t hz;
61 } __attribute__ ((packed));
63 /* The second part of a file header block. */
64 struct grub_affs_file
66 grub_uint8_t unused1[12];
67 grub_uint32_t size;
68 grub_uint8_t unused2[92];
69 struct grub_affs_time mtime;
70 grub_uint8_t namelen;
71 grub_uint8_t name[30];
72 grub_uint8_t unused3[5];
73 grub_uint32_t hardlink;
74 grub_uint32_t unused4[6];
75 grub_uint32_t next;
76 grub_uint32_t parent;
77 grub_uint32_t extension;
78 grub_uint32_t type;
79 } __attribute__ ((packed));
81 /* The location of `struct grub_affs_file' relative to the end of a
82 file header block. */
83 #define GRUB_AFFS_FILE_LOCATION 200
85 /* The offset in both the rootblock and the file header block for the
86 hashtable, symlink and block pointers (all synonyms). */
87 #define GRUB_AFFS_HASHTABLE_OFFSET 24
88 #define GRUB_AFFS_BLOCKPTR_OFFSET 24
89 #define GRUB_AFFS_SYMLINK_OFFSET 24
91 enum
93 GRUB_AFFS_FILETYPE_DIR = 2,
94 GRUB_AFFS_FILETYPE_SYMLINK = 3,
95 GRUB_AFFS_FILETYPE_HARDLINK = 0xfffffffc,
96 GRUB_AFFS_FILETYPE_REG = 0xfffffffd
99 #define AFFS_MAX_LOG_BLOCK_SIZE 4
103 struct grub_fshelp_node
105 struct grub_affs_data *data;
106 grub_uint32_t block;
107 struct grub_fshelp_node *parent;
108 struct grub_affs_file di;
109 grub_uint32_t *block_cache;
110 grub_uint32_t last_block_cache;
113 /* Information about a "mounted" affs filesystem. */
114 struct grub_affs_data
116 struct grub_affs_bblock bblock;
117 struct grub_fshelp_node diropen;
118 grub_disk_t disk;
120 /* Log blocksize in sectors. */
121 int log_blocksize;
123 /* The number of entries in the hashtable. */
124 unsigned int htsize;
127 static grub_dl_t my_mod;
130 static grub_disk_addr_t
131 grub_affs_read_block (grub_fshelp_node_t node, grub_disk_addr_t fileblock)
133 grub_uint32_t target, curblock;
134 grub_uint32_t pos;
135 struct grub_affs_file file;
136 struct grub_affs_data *data = node->data;
137 grub_uint64_t mod;
139 if (!node->block_cache)
141 node->block_cache = grub_malloc (((grub_be_to_cpu32 (node->di.size)
142 >> (9 + node->data->log_blocksize))
143 / data->htsize + 2)
144 * sizeof (node->block_cache[0]));
145 if (!node->block_cache)
146 return -1;
147 node->last_block_cache = 0;
148 node->block_cache[0] = node->block;
151 /* Files are at most 2G on AFFS, so no need for 64-bit division. */
152 target = (grub_uint32_t) fileblock / data->htsize;
153 mod = (grub_uint32_t) fileblock % data->htsize;
154 /* Find the block that points to the fileblock we are looking up by
155 following the chain until the right table is reached. */
156 for (curblock = node->last_block_cache + 1; curblock < target + 1; curblock++)
158 grub_disk_read (data->disk,
159 (((grub_uint64_t) node->block_cache[curblock - 1] + 1)
160 << data->log_blocksize) - 1,
161 GRUB_DISK_SECTOR_SIZE - GRUB_AFFS_FILE_LOCATION,
162 sizeof (file), &file);
163 if (grub_errno)
164 return 0;
166 node->block_cache[curblock] = grub_be_to_cpu32 (file.extension);
167 node->last_block_cache = curblock;
170 /* Translate the fileblock to the block within the right table. */
171 grub_disk_read (data->disk, (grub_uint64_t) node->block_cache[target]
172 << data->log_blocksize,
173 GRUB_AFFS_BLOCKPTR_OFFSET
174 + (data->htsize - mod - 1) * sizeof (pos),
175 sizeof (pos), &pos);
176 if (grub_errno)
177 return 0;
179 return grub_be_to_cpu32 (pos);
182 static struct grub_affs_data *
183 grub_affs_mount (grub_disk_t disk)
185 struct grub_affs_data *data;
186 grub_uint32_t *rootblock = 0;
187 struct grub_affs_rblock *rblock;
188 int log_blocksize = 0;
190 data = grub_zalloc (sizeof (struct grub_affs_data));
191 if (!data)
192 return 0;
194 /* Read the bootblock. */
195 grub_disk_read (disk, 0, 0, sizeof (struct grub_affs_bblock),
196 &data->bblock);
197 if (grub_errno)
198 goto fail;
200 /* Make sure this is an affs filesystem. */
201 if (grub_strncmp ((char *) (data->bblock.type), "DOS", 3))
203 grub_error (GRUB_ERR_BAD_FS, "not an AFFS filesystem");
204 goto fail;
207 /* Test if the filesystem is a OFS filesystem. */
208 if (! (data->bblock.flags & GRUB_AFFS_FLAG_FFS))
210 grub_error (GRUB_ERR_BAD_FS, "OFS not yet supported");
211 goto fail;
214 /* No sane person uses more than 8KB for a block. At least I hope
215 for that person because in that case this won't work. */
216 rootblock = grub_malloc (GRUB_DISK_SECTOR_SIZE << AFFS_MAX_LOG_BLOCK_SIZE);
217 if (!rootblock)
218 goto fail;
220 rblock = (struct grub_affs_rblock *) rootblock;
222 /* The filesystem blocksize is not stored anywhere in the filesystem
223 itself. One way to determine it is try reading blocks for the
224 rootblock until the checksum is correct. */
225 for (log_blocksize = 0; log_blocksize <= AFFS_MAX_LOG_BLOCK_SIZE;
226 log_blocksize++)
228 grub_uint32_t *currblock = rootblock;
229 unsigned int i;
230 grub_uint32_t checksum = 0;
232 /* Read the rootblock. */
233 grub_disk_read (disk,
234 (grub_uint64_t) grub_be_to_cpu32 (data->bblock.rootblock)
235 << log_blocksize, 0,
236 GRUB_DISK_SECTOR_SIZE << log_blocksize, rootblock);
237 if (grub_errno)
238 goto fail;
240 if (rblock->type != grub_cpu_to_be32_compile_time (2)
241 || rblock->htsize == 0
242 || currblock[(GRUB_DISK_SECTOR_SIZE << log_blocksize)
243 / sizeof (*currblock) - 1]
244 != grub_cpu_to_be32_compile_time (1))
245 continue;
247 for (i = 0; i < (GRUB_DISK_SECTOR_SIZE << log_blocksize)
248 / sizeof (*currblock);
249 i++)
250 checksum += grub_be_to_cpu32 (currblock[i]);
252 if (checksum == 0)
253 break;
255 if (log_blocksize > AFFS_MAX_LOG_BLOCK_SIZE)
257 grub_error (GRUB_ERR_BAD_FS, "AFFS blocksize couldn't be determined");
258 goto fail;
261 data->log_blocksize = log_blocksize;
262 data->disk = disk;
263 data->htsize = grub_be_to_cpu32 (rblock->htsize);
264 data->diropen.data = data;
265 data->diropen.block = grub_be_to_cpu32 (data->bblock.rootblock);
266 data->diropen.parent = NULL;
267 grub_memcpy (&data->diropen.di, rootblock, sizeof (data->diropen.di));
269 grub_free (rootblock);
271 return data;
273 fail:
274 if (grub_errno == GRUB_ERR_OUT_OF_RANGE)
275 grub_error (GRUB_ERR_BAD_FS, "not an AFFS filesystem");
277 grub_free (data);
278 grub_free (rootblock);
279 return 0;
283 static char *
284 grub_affs_read_symlink (grub_fshelp_node_t node)
286 struct grub_affs_data *data = node->data;
287 grub_uint8_t *latin1, *utf8;
288 const grub_size_t symlink_size = ((GRUB_DISK_SECTOR_SIZE
289 << data->log_blocksize) - GRUB_AFFS_SYMLINK_OFFSET);
291 latin1 = grub_malloc (symlink_size + 1);
292 if (!latin1)
293 return 0;
295 grub_disk_read (data->disk,
296 (grub_uint64_t) node->block << data->log_blocksize,
297 GRUB_AFFS_SYMLINK_OFFSET,
298 symlink_size, latin1);
299 if (grub_errno)
301 grub_free (latin1);
302 return 0;
304 latin1[symlink_size] = 0;
305 utf8 = grub_malloc (symlink_size * GRUB_MAX_UTF8_PER_LATIN1 + 1);
306 if (!utf8)
308 grub_free (latin1);
309 return 0;
311 *grub_latin1_to_utf8 (utf8, latin1, symlink_size) = '\0';
312 grub_dprintf ("affs", "Symlink: `%s'\n", utf8);
313 grub_free (latin1);
314 if (utf8[0] == ':')
315 utf8[0] = '/';
316 return (char *) utf8;
320 static int
321 grub_affs_iterate_dir (grub_fshelp_node_t dir,
322 int NESTED_FUNC_ATTR
323 (*hook) (const char *filename,
324 enum grub_fshelp_filetype filetype,
325 grub_fshelp_node_t node))
327 unsigned int i;
328 struct grub_affs_file file;
329 struct grub_fshelp_node *node = 0;
330 struct grub_affs_data *data = dir->data;
331 grub_uint32_t *hashtable;
333 auto int NESTED_FUNC_ATTR grub_affs_create_node (grub_uint32_t block,
334 const struct grub_affs_file *fil);
336 int NESTED_FUNC_ATTR grub_affs_create_node (grub_uint32_t block,
337 const struct grub_affs_file *fil)
339 int type;
340 grub_uint8_t name_u8[sizeof (fil->name) * GRUB_MAX_UTF8_PER_LATIN1 + 1];
341 grub_size_t len;
342 unsigned int nest;
344 node = grub_zalloc (sizeof (*node));
345 if (!node)
347 grub_free (hashtable);
348 return 1;
351 node->data = data;
352 node->block = block;
353 node->parent = dir;
355 len = fil->namelen;
356 if (len > sizeof (fil->name))
357 len = sizeof (fil->name);
358 *grub_latin1_to_utf8 (name_u8, fil->name, len) = '\0';
360 node->di = *fil;
361 for (nest = 0; nest < 8; nest++)
363 switch (node->di.type)
365 case grub_cpu_to_be32_compile_time (GRUB_AFFS_FILETYPE_REG):
366 type = GRUB_FSHELP_REG;
367 break;
368 case grub_cpu_to_be32_compile_time (GRUB_AFFS_FILETYPE_DIR):
369 type = GRUB_FSHELP_DIR;
370 break;
371 case grub_cpu_to_be32_compile_time (GRUB_AFFS_FILETYPE_SYMLINK):
372 type = GRUB_FSHELP_SYMLINK;
373 break;
374 case grub_cpu_to_be32_compile_time (GRUB_AFFS_FILETYPE_HARDLINK):
376 grub_err_t err;
377 node->block = grub_be_to_cpu32 (node->di.hardlink);
378 err = grub_disk_read (data->disk,
379 (((grub_uint64_t) node->block + 1) << data->log_blocksize)
380 - 1,
381 GRUB_DISK_SECTOR_SIZE - GRUB_AFFS_FILE_LOCATION,
382 sizeof (node->di), (char *) &node->di);
383 if (err)
384 return 1;
385 continue;
387 default:
388 return 0;
390 break;
393 if (nest == 8)
394 return 0;
396 type |= GRUB_FSHELP_CASE_INSENSITIVE;
398 if (hook ((char *) name_u8, type, node))
400 grub_free (hashtable);
401 node = 0;
402 return 1;
404 node = 0;
405 return 0;
408 /* Create the directory entries for `.' and `..'. */
409 node = grub_zalloc (sizeof (*node));
410 if (!node)
411 return 1;
413 *node = *dir;
414 if (hook (".", GRUB_FSHELP_DIR, node))
415 return 1;
416 if (dir->parent)
418 node = grub_zalloc (sizeof (*node));
419 if (!node)
420 return 1;
421 *node = *dir->parent;
422 if (hook ("..", GRUB_FSHELP_DIR, node))
423 return 1;
426 hashtable = grub_zalloc (data->htsize * sizeof (*hashtable));
427 if (!hashtable)
428 return 1;
430 grub_disk_read (data->disk,
431 (grub_uint64_t) dir->block << data->log_blocksize,
432 GRUB_AFFS_HASHTABLE_OFFSET,
433 data->htsize * sizeof (*hashtable), (char *) hashtable);
434 if (grub_errno)
435 goto fail;
437 for (i = 0; i < data->htsize; i++)
439 grub_uint32_t next;
441 if (!hashtable[i])
442 continue;
444 /* Every entry in the hashtable can be chained. Read the entire
445 chain. */
446 next = grub_be_to_cpu32 (hashtable[i]);
448 while (next)
450 grub_disk_read (data->disk,
451 (((grub_uint64_t) next + 1) << data->log_blocksize)
452 - 1,
453 GRUB_DISK_SECTOR_SIZE - GRUB_AFFS_FILE_LOCATION,
454 sizeof (file), (char *) &file);
455 if (grub_errno)
456 goto fail;
458 if (grub_affs_create_node (next, &file))
459 return 1;
461 next = grub_be_to_cpu32 (file.next);
465 grub_free (hashtable);
466 return 0;
468 fail:
469 grub_free (node);
470 grub_free (hashtable);
471 return 0;
475 /* Open a file named NAME and initialize FILE. */
476 static grub_err_t
477 grub_affs_open (struct grub_file *file, const char *name)
479 struct grub_affs_data *data;
480 struct grub_fshelp_node *fdiro = 0;
482 grub_dl_ref (my_mod);
484 data = grub_affs_mount (file->device->disk);
485 if (!data)
486 goto fail;
488 grub_fshelp_find_file (name, &data->diropen, &fdiro, grub_affs_iterate_dir,
489 grub_affs_read_symlink, GRUB_FSHELP_REG);
490 if (grub_errno)
491 goto fail;
493 file->size = grub_be_to_cpu32 (fdiro->di.size);
494 data->diropen = *fdiro;
495 grub_free (fdiro);
497 file->data = data;
498 file->offset = 0;
500 return 0;
502 fail:
503 if (data && fdiro != &data->diropen)
504 grub_free (fdiro);
505 grub_free (data);
507 grub_dl_unref (my_mod);
509 return grub_errno;
512 static grub_err_t
513 grub_affs_close (grub_file_t file)
515 struct grub_affs_data *data =
516 (struct grub_affs_data *) file->data;
518 grub_free (data->diropen.block_cache);
519 grub_free (file->data);
521 grub_dl_unref (my_mod);
523 return GRUB_ERR_NONE;
526 /* Read LEN bytes data from FILE into BUF. */
527 static grub_ssize_t
528 grub_affs_read (grub_file_t file, char *buf, grub_size_t len)
530 struct grub_affs_data *data =
531 (struct grub_affs_data *) file->data;
533 return grub_fshelp_read_file (data->diropen.data->disk, &data->diropen,
534 file->read_hook,
535 file->offset, len, buf, grub_affs_read_block,
536 grub_be_to_cpu32 (data->diropen.di.size),
537 data->log_blocksize, 0);
540 static grub_int32_t
541 aftime2ctime (const struct grub_affs_time *t)
543 return grub_be_to_cpu32 (t->day) * 86400
544 + grub_be_to_cpu32 (t->min) * 60
545 + grub_be_to_cpu32 (t->hz) / 50
546 + 8 * 365 * 86400 + 86400 * 2;
549 static grub_err_t
550 grub_affs_dir (grub_device_t device, const char *path,
551 int (*hook) (const char *filename,
552 const struct grub_dirhook_info *info))
554 struct grub_affs_data *data = 0;
555 struct grub_fshelp_node *fdiro = 0;
557 auto int NESTED_FUNC_ATTR iterate (const char *filename,
558 enum grub_fshelp_filetype filetype,
559 grub_fshelp_node_t node);
561 int NESTED_FUNC_ATTR iterate (const char *filename,
562 enum grub_fshelp_filetype filetype,
563 grub_fshelp_node_t node)
565 struct grub_dirhook_info info;
566 grub_memset (&info, 0, sizeof (info));
567 info.dir = ((filetype & GRUB_FSHELP_TYPE_MASK) == GRUB_FSHELP_DIR);
568 info.mtimeset = 1;
569 info.mtime = aftime2ctime (&node->di.mtime);
570 grub_free (node);
571 return hook (filename, &info);
574 grub_dl_ref (my_mod);
576 data = grub_affs_mount (device->disk);
577 if (!data)
578 goto fail;
580 grub_fshelp_find_file (path, &data->diropen, &fdiro, grub_affs_iterate_dir,
581 grub_affs_read_symlink, GRUB_FSHELP_DIR);
582 if (grub_errno)
583 goto fail;
585 grub_affs_iterate_dir (fdiro, iterate);
587 fail:
588 if (data && fdiro != &data->diropen)
589 grub_free (fdiro);
590 grub_free (data);
592 grub_dl_unref (my_mod);
594 return grub_errno;
598 static grub_err_t
599 grub_affs_label (grub_device_t device, char **label)
601 struct grub_affs_data *data;
602 struct grub_affs_file file;
603 grub_disk_t disk = device->disk;
605 grub_dl_ref (my_mod);
607 data = grub_affs_mount (disk);
608 if (data)
610 grub_size_t len;
611 /* The rootblock maps quite well on a file header block, it's
612 something we can use here. */
613 grub_disk_read (data->disk,
614 (((grub_uint64_t)
615 grub_be_to_cpu32 (data->bblock.rootblock) + 1)
616 << data->log_blocksize) - 1,
617 GRUB_DISK_SECTOR_SIZE - GRUB_AFFS_FILE_LOCATION,
618 sizeof (file), &file);
619 if (grub_errno)
620 return grub_errno;
622 len = file.namelen;
623 if (len > sizeof (file.name))
624 len = sizeof (file.name);
625 *label = grub_malloc (len * GRUB_MAX_UTF8_PER_LATIN1 + 1);
626 if (*label)
627 *grub_latin1_to_utf8 ((grub_uint8_t *) *label, file.name, len) = '\0';
629 else
630 *label = 0;
632 grub_dl_unref (my_mod);
634 grub_free (data);
636 return grub_errno;
639 static grub_err_t
640 grub_affs_mtime (grub_device_t device, grub_int32_t *t)
642 struct grub_affs_data *data;
643 grub_disk_t disk = device->disk;
644 struct grub_affs_time af_time;
646 *t = 0;
648 grub_dl_ref (my_mod);
650 data = grub_affs_mount (disk);
651 if (!data)
653 grub_dl_unref (my_mod);
654 return grub_errno;
657 grub_disk_read (data->disk,
658 (((grub_uint64_t)
659 grub_be_to_cpu32 (data->bblock.rootblock) + 1)
660 << data->log_blocksize) - 1,
661 GRUB_DISK_SECTOR_SIZE - 40,
662 sizeof (af_time), &af_time);
663 if (grub_errno)
665 grub_dl_unref (my_mod);
666 grub_free (data);
667 return grub_errno;
670 *t = aftime2ctime (&af_time);
671 grub_dl_unref (my_mod);
673 grub_free (data);
675 return GRUB_ERR_NONE;
679 static struct grub_fs grub_affs_fs =
681 .name = "affs",
682 .dir = grub_affs_dir,
683 .open = grub_affs_open,
684 .read = grub_affs_read,
685 .close = grub_affs_close,
686 .label = grub_affs_label,
687 .mtime = grub_affs_mtime,
689 #ifdef GRUB_UTIL
690 .reserved_first_sector = 0,
691 .blocklist_install = 1,
692 #endif
693 .next = 0
696 GRUB_MOD_INIT(affs)
698 grub_fs_register (&grub_affs_fs);
699 my_mod = mod;
702 GRUB_MOD_FINI(affs)
704 grub_fs_unregister (&grub_affs_fs);