1 /* affs.c - Amiga Fast FileSystem. */
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/>.
21 #include <grub/file.h>
23 #include <grub/misc.h>
24 #include <grub/disk.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
37 grub_uint32_t checksum
;
38 grub_uint32_t rootblock
;
41 /* Set if the filesystem is a AFFS filesystem. Otherwise this is an
43 #define GRUB_AFFS_FLAG_FFS 1
45 /* The affs rootblock. */
46 struct grub_affs_rblock
49 grub_uint8_t unused1
[8];
51 grub_uint32_t unused2
;
52 grub_uint32_t checksum
;
53 grub_uint32_t hashtable
[1];
63 /* The second part of a file header block. */
66 grub_uint8_t unused1
[12];
68 grub_uint8_t unused2
[92];
69 struct grub_affs_time mtime
;
71 grub_uint8_t name
[30];
72 grub_uint8_t unused3
[5];
73 grub_uint32_t hardlink
;
74 grub_uint32_t unused4
[6];
77 grub_uint32_t extension
;
81 /* The location of `struct grub_affs_file' relative to the end of a
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
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
;
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
;
121 /* Log blocksize in sectors. */
124 /* The number of entries in the hashtable. */
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
;
136 struct grub_affs_file file
;
137 struct grub_affs_data
*data
= node
->data
;
140 if (!node
->block_cache
)
142 node
->block_cache
= grub_malloc (((grub_be_to_cpu32 (node
->di
.size
)
143 >> (9 + node
->data
->log_blocksize
))
145 * sizeof (node
->block_cache
[0]));
146 if (!node
->block_cache
)
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
);
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
),
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;
192 data
= grub_zalloc (sizeof (struct grub_affs_data
));
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
),
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
))
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. */
213 rootblock
= grub_malloc (GRUB_DISK_SECTOR_SIZE
214 << AFFS_MAX_LOG_BLOCK_SIZE
);
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
;
226 grub_uint32_t
*currblock
= rootblock
;
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
)
234 GRUB_DISK_SECTOR_SIZE
<< log_blocksize
, rootblock
);
235 if (grub_errno
== GRUB_ERR_OUT_OF_RANGE
)
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))
250 for (i
= 0; i
< (GRUB_DISK_SECTOR_SIZE
<< log_blocksize
)
251 / sizeof (*currblock
);
253 checksum
+= grub_be_to_cpu32 (currblock
[i
]);
257 data
->log_blocksize
= log_blocksize
;
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
);
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");
277 grub_free (rootblock
);
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);
294 grub_disk_read (data
->disk
,
295 (grub_uint64_t
) node
->block
<< data
->log_blocksize
,
296 GRUB_AFFS_SYMLINK_OFFSET
,
297 symlink_size
, latin1
);
303 latin1
[symlink_size
] = 0;
304 utf8
= grub_malloc (symlink_size
* GRUB_MAX_UTF8_PER_LATIN1
+ 1);
310 *grub_latin1_to_utf8 (utf8
, latin1
, symlink_size
) = '\0';
311 grub_dprintf ("affs", "Symlink: `%s'\n", utf8
);
315 return (char *) utf8
;
319 /* Helper for grub_affs_iterate_dir. */
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];
333 *node
= grub_zalloc (sizeof (**node
));
336 grub_free (*hashtable
);
340 (*node
)->data
= data
;
341 (*node
)->block
= block
;
342 (*node
)->parent
= dir
;
345 if (len
> sizeof (fil
->name
))
346 len
= sizeof (fil
->name
);
347 *grub_latin1_to_utf8 (name_u8
, fil
->name
, len
) = '\0';
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
;
357 case grub_cpu_to_be32_compile_time (GRUB_AFFS_FILETYPE_DIR
):
358 type
= GRUB_FSHELP_DIR
;
360 case grub_cpu_to_be32_compile_time (GRUB_AFFS_FILETYPE_SYMLINK
):
361 type
= GRUB_FSHELP_SYMLINK
;
363 case grub_cpu_to_be32_compile_time (GRUB_AFFS_FILETYPE_HARDLINK
):
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
)
370 GRUB_DISK_SECTOR_SIZE
- GRUB_AFFS_FILE_LOCATION
,
371 sizeof ((*node
)->di
), (char *) &(*node
)->di
);
385 type
|= GRUB_FSHELP_CASE_INSENSITIVE
;
387 if (hook ((char *) name_u8
, type
, *node
, hook_data
))
389 grub_free (*hashtable
);
398 grub_affs_iterate_dir (grub_fshelp_node_t dir
,
399 grub_fshelp_iterate_dir_hook_t hook
, void *hook_data
)
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
));
413 if (hook (".", GRUB_FSHELP_DIR
, node
, hook_data
))
417 node
= grub_zalloc (sizeof (*node
));
420 *node
= *dir
->parent
;
421 if (hook ("..", GRUB_FSHELP_DIR
, node
, hook_data
))
425 hashtable
= grub_zalloc (data
->htsize
* sizeof (*hashtable
));
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
);
436 for (i
= 0; i
< data
->htsize
; i
++)
443 /* Every entry in the hashtable can be chained. Read the entire
445 next
= grub_be_to_cpu32 (hashtable
[i
]);
449 grub_disk_read (data
->disk
,
450 (((grub_uint64_t
) next
+ 1) << data
->log_blocksize
)
452 GRUB_DISK_SECTOR_SIZE
- GRUB_AFFS_FILE_LOCATION
,
453 sizeof (file
), (char *) &file
);
457 if (grub_affs_create_node (dir
, hook
, hook_data
, &node
, &hashtable
,
461 next
= grub_be_to_cpu32 (file
.next
);
465 grub_free (hashtable
);
470 grub_free (hashtable
);
475 /* Open a file named NAME and initialize FILE. */
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
);
488 grub_fshelp_find_file (name
, &data
->diropen
, &fdiro
, grub_affs_iterate_dir
,
489 grub_affs_read_symlink
, GRUB_FSHELP_REG
);
493 file
->size
= grub_be_to_cpu32 (fdiro
->di
.size
);
494 data
->diropen
= *fdiro
;
503 if (data
&& fdiro
!= &data
->diropen
)
507 grub_dl_unref (my_mod
);
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. */
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);
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
;
556 /* Helper for grub_affs_dir. */
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
);
567 info
.mtime
= aftime2ctime (&node
->di
.mtime
);
569 return ctx
->hook (filename
, &info
, ctx
->hook_data
);
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
);
586 grub_fshelp_find_file (path
, &data
->diropen
, &fdiro
, grub_affs_iterate_dir
,
587 grub_affs_read_symlink
, GRUB_FSHELP_DIR
);
591 grub_affs_iterate_dir (fdiro
, grub_affs_dir_iter
, &ctx
);
594 if (data
&& fdiro
!= &data
->diropen
)
598 grub_dl_unref (my_mod
);
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
);
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
,
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
);
629 if (len
> sizeof (file
.name
))
630 len
= sizeof (file
.name
);
631 *label
= grub_malloc (len
* GRUB_MAX_UTF8_PER_LATIN1
+ 1);
633 *grub_latin1_to_utf8 ((grub_uint8_t
*) *label
, file
.name
, len
) = '\0';
638 grub_dl_unref (my_mod
);
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
;
654 grub_dl_ref (my_mod
);
656 data
= grub_affs_mount (disk
);
659 grub_dl_unref (my_mod
);
663 grub_disk_read (data
->disk
,
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
);
671 grub_dl_unref (my_mod
);
676 *t
= aftime2ctime (&af_time
);
677 grub_dl_unref (my_mod
);
681 return GRUB_ERR_NONE
;
685 static struct grub_fs grub_affs_fs
=
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
,
696 .reserved_first_sector
= 0,
697 .blocklist_install
= 1,
704 grub_fs_register (&grub_affs_fs
);
710 grub_fs_unregister (&grub_affs_fs
);