Indentation fix, cleanup.
[AROS.git] / arch / all-pc / boot / grub2-aros / grub-core / fs / affs.c
blobf673897e0fd7ee795f18b6eaa3be9b42c212d91b
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 } GRUB_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 } GRUB_PACKED;
56 struct grub_affs_time
58 grub_int32_t day;
59 grub_uint32_t min;
60 grub_uint32_t hz;
61 } GRUB_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 } GRUB_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
100 #define AFFS_MAX_SUPERBLOCK 1
104 struct grub_fshelp_node
106 struct grub_affs_data *data;
107 grub_uint32_t block;
108 struct grub_fshelp_node *parent;
109 struct grub_affs_file di;
110 grub_uint32_t *block_cache;
111 grub_uint32_t last_block_cache;
114 /* Information about a "mounted" affs filesystem. */
115 struct grub_affs_data
117 struct grub_affs_bblock bblock;
118 struct grub_fshelp_node diropen;
119 grub_disk_t disk;
121 /* Log blocksize in sectors. */
122 int log_blocksize;
124 /* The number of entries in the hashtable. */
125 unsigned int htsize;
128 static grub_dl_t my_mod;
131 static grub_disk_addr_t
132 grub_affs_read_block (grub_fshelp_node_t node, grub_disk_addr_t fileblock)
134 grub_uint32_t target, curblock;
135 grub_uint32_t pos;
136 struct grub_affs_file file;
137 struct grub_affs_data *data = node->data;
138 grub_uint64_t mod;
140 if (!node->block_cache)
142 node->block_cache = grub_malloc (((grub_be_to_cpu32 (node->di.size)
143 >> (9 + node->data->log_blocksize))
144 / data->htsize + 2)
145 * sizeof (node->block_cache[0]));
146 if (!node->block_cache)
147 return -1;
148 node->last_block_cache = 0;
149 node->block_cache[0] = node->block;
152 /* Files are at most 2G on AFFS, so no need for 64-bit division. */
153 target = (grub_uint32_t) fileblock / data->htsize;
154 mod = (grub_uint32_t) fileblock % data->htsize;
155 /* Find the block that points to the fileblock we are looking up by
156 following the chain until the right table is reached. */
157 for (curblock = node->last_block_cache + 1; curblock < target + 1; curblock++)
159 grub_disk_read (data->disk,
160 (((grub_uint64_t) node->block_cache[curblock - 1] + 1)
161 << data->log_blocksize) - 1,
162 GRUB_DISK_SECTOR_SIZE - GRUB_AFFS_FILE_LOCATION,
163 sizeof (file), &file);
164 if (grub_errno)
165 return 0;
167 node->block_cache[curblock] = grub_be_to_cpu32 (file.extension);
168 node->last_block_cache = curblock;
171 /* Translate the fileblock to the block within the right table. */
172 grub_disk_read (data->disk, (grub_uint64_t) node->block_cache[target]
173 << data->log_blocksize,
174 GRUB_AFFS_BLOCKPTR_OFFSET
175 + (data->htsize - mod - 1) * sizeof (pos),
176 sizeof (pos), &pos);
177 if (grub_errno)
178 return 0;
180 return grub_be_to_cpu32 (pos);
183 static struct grub_affs_data *
184 grub_affs_mount (grub_disk_t disk)
186 struct grub_affs_data *data;
187 grub_uint32_t *rootblock = 0;
188 struct grub_affs_rblock *rblock = 0;
189 int log_blocksize = 0;
190 int bsnum = 0;
192 data = grub_zalloc (sizeof (struct grub_affs_data));
193 if (!data)
194 return 0;
196 for (bsnum = 0; bsnum < AFFS_MAX_SUPERBLOCK + 1; bsnum++)
198 /* Read the bootblock. */
199 grub_disk_read (disk, bsnum, 0, sizeof (struct grub_affs_bblock),
200 &data->bblock);
201 if (grub_errno)
202 goto fail;
204 /* Make sure this is an affs filesystem. */
205 if (grub_strncmp ((char *) (data->bblock.type), "DOS", 3) != 0
206 /* Test if the filesystem is a OFS filesystem. */
207 || !(data->bblock.flags & GRUB_AFFS_FLAG_FFS))
208 continue;
210 /* No sane person uses more than 8KB for a block. At least I hope
211 for that person because in that case this won't work. */
212 if (!rootblock)
213 rootblock = grub_malloc (GRUB_DISK_SECTOR_SIZE
214 << AFFS_MAX_LOG_BLOCK_SIZE);
215 if (!rootblock)
216 goto fail;
218 rblock = (struct grub_affs_rblock *) rootblock;
220 /* The filesystem blocksize is not stored anywhere in the filesystem
221 itself. One way to determine it is try reading blocks for the
222 rootblock until the checksum is correct. */
223 for (log_blocksize = 0; log_blocksize <= AFFS_MAX_LOG_BLOCK_SIZE;
224 log_blocksize++)
226 grub_uint32_t *currblock = rootblock;
227 unsigned int i;
228 grub_uint32_t checksum = 0;
230 /* Read the rootblock. */
231 grub_disk_read (disk,
232 (grub_uint64_t) grub_be_to_cpu32 (data->bblock.rootblock)
233 << log_blocksize, 0,
234 GRUB_DISK_SECTOR_SIZE << log_blocksize, rootblock);
235 if (grub_errno == GRUB_ERR_OUT_OF_RANGE)
237 grub_errno = 0;
238 break;
240 if (grub_errno)
241 goto fail;
243 if (rblock->type != grub_cpu_to_be32_compile_time (2)
244 || rblock->htsize == 0
245 || currblock[(GRUB_DISK_SECTOR_SIZE << log_blocksize)
246 / sizeof (*currblock) - 1]
247 != grub_cpu_to_be32_compile_time (1))
248 continue;
250 for (i = 0; i < (GRUB_DISK_SECTOR_SIZE << log_blocksize)
251 / sizeof (*currblock);
252 i++)
253 checksum += grub_be_to_cpu32 (currblock[i]);
255 if (checksum == 0)
257 data->log_blocksize = log_blocksize;
258 data->disk = disk;
259 data->htsize = grub_be_to_cpu32 (rblock->htsize);
260 data->diropen.data = data;
261 data->diropen.block = grub_be_to_cpu32 (data->bblock.rootblock);
262 data->diropen.parent = NULL;
263 grub_memcpy (&data->diropen.di, rootblock,
264 sizeof (data->diropen.di));
265 grub_free (rootblock);
267 return data;
272 fail:
273 if (grub_errno == GRUB_ERR_NONE || grub_errno == GRUB_ERR_OUT_OF_RANGE)
274 grub_error (GRUB_ERR_BAD_FS, "not an AFFS filesystem");
276 grub_free (data);
277 grub_free (rootblock);
278 return 0;
282 static char *
283 grub_affs_read_symlink (grub_fshelp_node_t node)
285 struct grub_affs_data *data = node->data;
286 grub_uint8_t *latin1, *utf8;
287 const grub_size_t symlink_size = ((GRUB_DISK_SECTOR_SIZE
288 << data->log_blocksize) - GRUB_AFFS_SYMLINK_OFFSET);
290 latin1 = grub_malloc (symlink_size + 1);
291 if (!latin1)
292 return 0;
294 grub_disk_read (data->disk,
295 (grub_uint64_t) node->block << data->log_blocksize,
296 GRUB_AFFS_SYMLINK_OFFSET,
297 symlink_size, latin1);
298 if (grub_errno)
300 grub_free (latin1);
301 return 0;
303 latin1[symlink_size] = 0;
304 utf8 = grub_malloc (symlink_size * GRUB_MAX_UTF8_PER_LATIN1 + 1);
305 if (!utf8)
307 grub_free (latin1);
308 return 0;
310 *grub_latin1_to_utf8 (utf8, latin1, symlink_size) = '\0';
311 grub_dprintf ("affs", "Symlink: `%s'\n", utf8);
312 grub_free (latin1);
313 if (utf8[0] == ':')
314 utf8[0] = '/';
315 return (char *) utf8;
319 /* Helper for grub_affs_iterate_dir. */
320 static int
321 grub_affs_create_node (grub_fshelp_node_t dir,
322 grub_fshelp_iterate_dir_hook_t hook, void *hook_data,
323 struct grub_fshelp_node **node,
324 grub_uint32_t **hashtable,
325 grub_uint32_t block, const struct grub_affs_file *fil)
327 struct grub_affs_data *data = dir->data;
328 int type = GRUB_FSHELP_REG;
329 grub_uint8_t name_u8[sizeof (fil->name) * GRUB_MAX_UTF8_PER_LATIN1 + 1];
330 grub_size_t len;
331 unsigned int nest;
333 *node = grub_zalloc (sizeof (**node));
334 if (!*node)
336 grub_free (*hashtable);
337 return 1;
340 (*node)->data = data;
341 (*node)->block = block;
342 (*node)->parent = dir;
344 len = fil->namelen;
345 if (len > sizeof (fil->name))
346 len = sizeof (fil->name);
347 *grub_latin1_to_utf8 (name_u8, fil->name, len) = '\0';
349 (*node)->di = *fil;
350 for (nest = 0; nest < 8; nest++)
352 switch ((*node)->di.type)
354 case grub_cpu_to_be32_compile_time (GRUB_AFFS_FILETYPE_REG):
355 type = GRUB_FSHELP_REG;
356 break;
357 case grub_cpu_to_be32_compile_time (GRUB_AFFS_FILETYPE_DIR):
358 type = GRUB_FSHELP_DIR;
359 break;
360 case grub_cpu_to_be32_compile_time (GRUB_AFFS_FILETYPE_SYMLINK):
361 type = GRUB_FSHELP_SYMLINK;
362 break;
363 case grub_cpu_to_be32_compile_time (GRUB_AFFS_FILETYPE_HARDLINK):
365 grub_err_t err;
366 (*node)->block = grub_be_to_cpu32 ((*node)->di.hardlink);
367 err = grub_disk_read (data->disk,
368 (((grub_uint64_t) (*node)->block + 1) << data->log_blocksize)
369 - 1,
370 GRUB_DISK_SECTOR_SIZE - GRUB_AFFS_FILE_LOCATION,
371 sizeof ((*node)->di), (char *) &(*node)->di);
372 if (err)
373 return 1;
374 continue;
376 default:
377 return 0;
379 break;
382 if (nest == 8)
383 return 0;
385 type |= GRUB_FSHELP_CASE_INSENSITIVE;
387 if (hook ((char *) name_u8, type, *node, hook_data))
389 grub_free (*hashtable);
390 *node = 0;
391 return 1;
393 *node = 0;
394 return 0;
397 static int
398 grub_affs_iterate_dir (grub_fshelp_node_t dir,
399 grub_fshelp_iterate_dir_hook_t hook, void *hook_data)
401 unsigned int i;
402 struct grub_affs_file file;
403 struct grub_fshelp_node *node = 0;
404 struct grub_affs_data *data = dir->data;
405 grub_uint32_t *hashtable;
407 /* Create the directory entries for `.' and `..'. */
408 node = grub_zalloc (sizeof (*node));
409 if (!node)
410 return 1;
412 *node = *dir;
413 if (hook (".", GRUB_FSHELP_DIR, node, hook_data))
414 return 1;
415 if (dir->parent)
417 node = grub_zalloc (sizeof (*node));
418 if (!node)
419 return 1;
420 *node = *dir->parent;
421 if (hook ("..", GRUB_FSHELP_DIR, node, hook_data))
422 return 1;
425 hashtable = grub_zalloc (data->htsize * sizeof (*hashtable));
426 if (!hashtable)
427 return 1;
429 grub_disk_read (data->disk,
430 (grub_uint64_t) dir->block << data->log_blocksize,
431 GRUB_AFFS_HASHTABLE_OFFSET,
432 data->htsize * sizeof (*hashtable), (char *) hashtable);
433 if (grub_errno)
434 goto fail;
436 for (i = 0; i < data->htsize; i++)
438 grub_uint32_t next;
440 if (!hashtable[i])
441 continue;
443 /* Every entry in the hashtable can be chained. Read the entire
444 chain. */
445 next = grub_be_to_cpu32 (hashtable[i]);
447 while (next)
449 grub_disk_read (data->disk,
450 (((grub_uint64_t) next + 1) << data->log_blocksize)
451 - 1,
452 GRUB_DISK_SECTOR_SIZE - GRUB_AFFS_FILE_LOCATION,
453 sizeof (file), (char *) &file);
454 if (grub_errno)
455 goto fail;
457 if (grub_affs_create_node (dir, hook, hook_data, &node, &hashtable,
458 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, file->read_hook_data,
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 /* Context for grub_affs_dir. */
550 struct grub_affs_dir_ctx
552 grub_fs_dir_hook_t hook;
553 void *hook_data;
556 /* Helper for grub_affs_dir. */
557 static int
558 grub_affs_dir_iter (const char *filename, enum grub_fshelp_filetype filetype,
559 grub_fshelp_node_t node, void *data)
561 struct grub_affs_dir_ctx *ctx = data;
562 struct grub_dirhook_info info;
564 grub_memset (&info, 0, sizeof (info));
565 info.dir = ((filetype & GRUB_FSHELP_TYPE_MASK) == GRUB_FSHELP_DIR);
566 info.mtimeset = 1;
567 info.mtime = aftime2ctime (&node->di.mtime);
568 grub_free (node);
569 return ctx->hook (filename, &info, ctx->hook_data);
572 static grub_err_t
573 grub_affs_dir (grub_device_t device, const char *path,
574 grub_fs_dir_hook_t hook, void *hook_data)
576 struct grub_affs_dir_ctx ctx = { hook, hook_data };
577 struct grub_affs_data *data = 0;
578 struct grub_fshelp_node *fdiro = 0;
580 grub_dl_ref (my_mod);
582 data = grub_affs_mount (device->disk);
583 if (!data)
584 goto fail;
586 grub_fshelp_find_file (path, &data->diropen, &fdiro, grub_affs_iterate_dir,
587 grub_affs_read_symlink, GRUB_FSHELP_DIR);
588 if (grub_errno)
589 goto fail;
591 grub_affs_iterate_dir (fdiro, grub_affs_dir_iter, &ctx);
593 fail:
594 if (data && fdiro != &data->diropen)
595 grub_free (fdiro);
596 grub_free (data);
598 grub_dl_unref (my_mod);
600 return grub_errno;
604 static grub_err_t
605 grub_affs_label (grub_device_t device, char **label)
607 struct grub_affs_data *data;
608 struct grub_affs_file file;
609 grub_disk_t disk = device->disk;
611 grub_dl_ref (my_mod);
613 data = grub_affs_mount (disk);
614 if (data)
616 grub_size_t len;
617 /* The rootblock maps quite well on a file header block, it's
618 something we can use here. */
619 grub_disk_read (data->disk,
620 (((grub_uint64_t)
621 grub_be_to_cpu32 (data->bblock.rootblock) + 1)
622 << data->log_blocksize) - 1,
623 GRUB_DISK_SECTOR_SIZE - GRUB_AFFS_FILE_LOCATION,
624 sizeof (file), &file);
625 if (grub_errno)
626 return grub_errno;
628 len = file.namelen;
629 if (len > sizeof (file.name))
630 len = sizeof (file.name);
631 *label = grub_malloc (len * GRUB_MAX_UTF8_PER_LATIN1 + 1);
632 if (*label)
633 *grub_latin1_to_utf8 ((grub_uint8_t *) *label, file.name, len) = '\0';
635 else
636 *label = 0;
638 grub_dl_unref (my_mod);
640 grub_free (data);
642 return grub_errno;
645 static grub_err_t
646 grub_affs_mtime (grub_device_t device, grub_int32_t *t)
648 struct grub_affs_data *data;
649 grub_disk_t disk = device->disk;
650 struct grub_affs_time af_time;
652 *t = 0;
654 grub_dl_ref (my_mod);
656 data = grub_affs_mount (disk);
657 if (!data)
659 grub_dl_unref (my_mod);
660 return grub_errno;
663 grub_disk_read (data->disk,
664 (((grub_uint64_t)
665 grub_be_to_cpu32 (data->bblock.rootblock) + 1)
666 << data->log_blocksize) - 1,
667 GRUB_DISK_SECTOR_SIZE - 40,
668 sizeof (af_time), &af_time);
669 if (grub_errno)
671 grub_dl_unref (my_mod);
672 grub_free (data);
673 return grub_errno;
676 *t = aftime2ctime (&af_time);
677 grub_dl_unref (my_mod);
679 grub_free (data);
681 return GRUB_ERR_NONE;
685 static struct grub_fs grub_affs_fs =
687 .name = "affs",
688 .dir = grub_affs_dir,
689 .open = grub_affs_open,
690 .read = grub_affs_read,
691 .close = grub_affs_close,
692 .label = grub_affs_label,
693 .mtime = grub_affs_mtime,
695 #ifdef GRUB_UTIL
696 .reserved_first_sector = 0,
697 .blocklist_install = 1,
698 #endif
699 .next = 0
702 GRUB_MOD_INIT(affs)
704 grub_fs_register (&grub_affs_fs);
705 my_mod = mod;
708 GRUB_MOD_FINI(affs)
710 grub_fs_unregister (&grub_affs_fs);