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
;
39 } __attribute__ ((packed
));
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];
54 } __attribute__ ((packed
));
61 } __attribute__ ((packed
));
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
;
79 } __attribute__ ((packed
));
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
103 struct grub_fshelp_node
105 struct grub_affs_data
*data
;
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
;
120 /* Log blocksize in sectors. */
123 /* The number of entries in the hashtable. */
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
;
135 struct grub_affs_file file
;
136 struct grub_affs_data
*data
= node
->data
;
139 if (!node
->block_cache
)
141 node
->block_cache
= grub_malloc (((grub_be_to_cpu32 (node
->di
.size
)
142 >> (9 + node
->data
->log_blocksize
))
144 * sizeof (node
->block_cache
[0]));
145 if (!node
->block_cache
)
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
);
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
),
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
));
194 /* Read the bootblock. */
195 grub_disk_read (disk
, 0, 0, sizeof (struct grub_affs_bblock
),
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");
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");
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
);
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
;
228 grub_uint32_t
*currblock
= rootblock
;
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
)
236 GRUB_DISK_SECTOR_SIZE
<< log_blocksize
, rootblock
);
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))
247 for (i
= 0; i
< (GRUB_DISK_SECTOR_SIZE
<< log_blocksize
)
248 / sizeof (*currblock
);
250 checksum
+= grub_be_to_cpu32 (currblock
[i
]);
255 if (log_blocksize
> AFFS_MAX_LOG_BLOCK_SIZE
)
257 grub_error (GRUB_ERR_BAD_FS
, "AFFS blocksize couldn't be determined");
261 data
->log_blocksize
= log_blocksize
;
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
);
274 if (grub_errno
== GRUB_ERR_OUT_OF_RANGE
)
275 grub_error (GRUB_ERR_BAD_FS
, "not an AFFS filesystem");
278 grub_free (rootblock
);
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);
295 grub_disk_read (data
->disk
,
296 (grub_uint64_t
) node
->block
<< data
->log_blocksize
,
297 GRUB_AFFS_SYMLINK_OFFSET
,
298 symlink_size
, latin1
);
304 latin1
[symlink_size
] = 0;
305 utf8
= grub_malloc (symlink_size
* GRUB_MAX_UTF8_PER_LATIN1
+ 1);
311 *grub_latin1_to_utf8 (utf8
, latin1
, symlink_size
) = '\0';
312 grub_dprintf ("affs", "Symlink: `%s'\n", utf8
);
316 return (char *) utf8
;
321 grub_affs_iterate_dir (grub_fshelp_node_t dir
,
323 (*hook
) (const char *filename
,
324 enum grub_fshelp_filetype filetype
,
325 grub_fshelp_node_t node
))
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
)
340 grub_uint8_t name_u8
[sizeof (fil
->name
) * GRUB_MAX_UTF8_PER_LATIN1
+ 1];
344 node
= grub_zalloc (sizeof (*node
));
347 grub_free (hashtable
);
356 if (len
> sizeof (fil
->name
))
357 len
= sizeof (fil
->name
);
358 *grub_latin1_to_utf8 (name_u8
, fil
->name
, len
) = '\0';
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
;
368 case grub_cpu_to_be32_compile_time (GRUB_AFFS_FILETYPE_DIR
):
369 type
= GRUB_FSHELP_DIR
;
371 case grub_cpu_to_be32_compile_time (GRUB_AFFS_FILETYPE_SYMLINK
):
372 type
= GRUB_FSHELP_SYMLINK
;
374 case grub_cpu_to_be32_compile_time (GRUB_AFFS_FILETYPE_HARDLINK
):
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
)
381 GRUB_DISK_SECTOR_SIZE
- GRUB_AFFS_FILE_LOCATION
,
382 sizeof (node
->di
), (char *) &node
->di
);
396 type
|= GRUB_FSHELP_CASE_INSENSITIVE
;
398 if (hook ((char *) name_u8
, type
, node
))
400 grub_free (hashtable
);
408 /* Create the directory entries for `.' and `..'. */
409 node
= grub_zalloc (sizeof (*node
));
414 if (hook (".", GRUB_FSHELP_DIR
, node
))
418 node
= grub_zalloc (sizeof (*node
));
421 *node
= *dir
->parent
;
422 if (hook ("..", GRUB_FSHELP_DIR
, node
))
426 hashtable
= grub_zalloc (data
->htsize
* sizeof (*hashtable
));
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
);
437 for (i
= 0; i
< data
->htsize
; i
++)
444 /* Every entry in the hashtable can be chained. Read the entire
446 next
= grub_be_to_cpu32 (hashtable
[i
]);
450 grub_disk_read (data
->disk
,
451 (((grub_uint64_t
) next
+ 1) << data
->log_blocksize
)
453 GRUB_DISK_SECTOR_SIZE
- GRUB_AFFS_FILE_LOCATION
,
454 sizeof (file
), (char *) &file
);
458 if (grub_affs_create_node (next
, &file
))
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
,
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;
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
);
569 info
.mtime
= aftime2ctime (&node
->di
.mtime
);
571 return hook (filename
, &info
);
574 grub_dl_ref (my_mod
);
576 data
= grub_affs_mount (device
->disk
);
580 grub_fshelp_find_file (path
, &data
->diropen
, &fdiro
, grub_affs_iterate_dir
,
581 grub_affs_read_symlink
, GRUB_FSHELP_DIR
);
585 grub_affs_iterate_dir (fdiro
, iterate
);
588 if (data
&& fdiro
!= &data
->diropen
)
592 grub_dl_unref (my_mod
);
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
);
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
,
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
);
623 if (len
> sizeof (file
.name
))
624 len
= sizeof (file
.name
);
625 *label
= grub_malloc (len
* GRUB_MAX_UTF8_PER_LATIN1
+ 1);
627 *grub_latin1_to_utf8 ((grub_uint8_t
*) *label
, file
.name
, len
) = '\0';
632 grub_dl_unref (my_mod
);
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
;
648 grub_dl_ref (my_mod
);
650 data
= grub_affs_mount (disk
);
653 grub_dl_unref (my_mod
);
657 grub_disk_read (data
->disk
,
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
);
665 grub_dl_unref (my_mod
);
670 *t
= aftime2ctime (&af_time
);
671 grub_dl_unref (my_mod
);
675 return GRUB_ERR_NONE
;
679 static struct grub_fs grub_affs_fs
=
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
,
690 .reserved_first_sector
= 0,
691 .blocklist_install
= 1,
698 grub_fs_register (&grub_affs_fs
);
704 grub_fs_unregister (&grub_affs_fs
);