2008-08-16 Robert Millan <rmh@aybabtu.com>
[grub2/jjazz.git] / fs / affs.c
blobc60fd9cf2be46b8603751305dea569996044275c
1 /* affs.c - Amiga Fast FileSystem. */
2 /*
3 * GRUB -- GRand Unified Bootloader
4 * Copyright (C) 2005,2006,2007,2008 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>
29 /* The affs bootblock. */
30 struct grub_affs_bblock
32 grub_uint8_t type[3];
33 grub_uint8_t flags;
34 grub_uint32_t checksum;
35 grub_uint32_t rootblock;
36 } __attribute__ ((packed));
38 /* Set if the filesystem is a AFFS filesystem. Otherwise this is an
39 OFS filesystem. */
40 #define GRUB_AFFS_FLAG_FFS 1
42 /* The affs rootblock. */
43 struct grub_affs_rblock
45 grub_uint8_t type[4];
46 grub_uint8_t unused1[8];
47 grub_uint32_t htsize;
48 grub_uint32_t unused2;
49 grub_uint32_t checksum;
50 grub_uint32_t hashtable[1];
51 } __attribute__ ((packed));
53 /* The second part of a file header block. */
54 struct grub_affs_file
56 grub_uint8_t unused1[12];
57 grub_uint32_t size;
58 grub_uint8_t unused2[104];
59 grub_uint8_t namelen;
60 grub_uint8_t name[30];
61 grub_uint8_t unused3[33];
62 grub_uint32_t next;
63 grub_uint32_t parent;
64 grub_uint32_t extension;
65 grub_int32_t type;
66 } __attribute__ ((packed));
68 /* The location of `struct grub_affs_file' relative to the end of a
69 file header block. */
70 #define GRUB_AFFS_FILE_LOCATION 200
72 /* The offset in both the rootblock and the file header block for the
73 hashtable, symlink and block pointers (all synonyms). */
74 #define GRUB_AFFS_HASHTABLE_OFFSET 24
75 #define GRUB_AFFS_BLOCKPTR_OFFSET 24
76 #define GRUB_AFFS_SYMLINK_OFFSET 24
78 #define GRUB_AFFS_SYMLINK_SIZE(blocksize) ((blocksize) - 225)
80 #define GRUB_AFFS_FILETYPE_DIR -3
81 #define GRUB_AFFS_FILETYPE_REG 2
82 #define GRUB_AFFS_FILETYPE_SYMLINK 3
85 struct grub_fshelp_node
87 struct grub_affs_data *data;
88 int block;
89 int size;
90 int parent;
93 /* Information about a "mounted" affs filesystem. */
94 struct grub_affs_data
96 struct grub_affs_bblock bblock;
97 struct grub_fshelp_node diropen;
98 grub_disk_t disk;
100 /* Blocksize in sectors. */
101 int blocksize;
103 /* The number of entries in the hashtable. */
104 int htsize;
107 #ifndef GRUB_UTIL
108 static grub_dl_t my_mod;
109 #endif
112 static grub_disk_addr_t
113 grub_affs_read_block (grub_fshelp_node_t node, grub_disk_addr_t fileblock)
115 int links;
116 grub_uint32_t pos;
117 int block = node->block;
118 struct grub_affs_file file;
119 struct grub_affs_data *data = node->data;
120 grub_uint32_t mod;
122 /* Find the block that points to the fileblock we are looking up by
123 following the chain until the right table is reached. */
124 for (links = grub_divmod64 (fileblock, data->htsize, &mod); links; links--)
126 grub_disk_read (data->disk, block + data->blocksize - 1,
127 data->blocksize * (GRUB_DISK_SECTOR_SIZE
128 - GRUB_AFFS_FILE_LOCATION),
129 sizeof (file), (char *) &file);
130 if (grub_errno)
131 return 0;
133 block = grub_be_to_cpu32 (file.extension);
136 /* Translate the fileblock to the block within the right table. */
137 fileblock = mod;
138 grub_disk_read (data->disk, block,
139 GRUB_AFFS_BLOCKPTR_OFFSET
140 + (data->htsize - fileblock - 1) * sizeof (pos),
141 sizeof (pos), (char *) &pos);
142 if (grub_errno)
143 return 0;
145 return grub_be_to_cpu32 (pos);
149 /* Read LEN bytes from the file described by DATA starting with byte
150 POS. Return the amount of read bytes in READ. */
151 static grub_ssize_t
152 grub_affs_read_file (grub_fshelp_node_t node,
153 void NESTED_FUNC_ATTR (*read_hook) (grub_disk_addr_t sector,
154 unsigned offset, unsigned length),
155 int pos, grub_size_t len, char *buf)
157 return grub_fshelp_read_file (node->data->disk, node, read_hook,
158 pos, len, buf, grub_affs_read_block,
159 node->size, 0);
163 static struct grub_affs_data *
164 grub_affs_mount (grub_disk_t disk)
166 struct grub_affs_data *data;
167 grub_uint32_t *rootblock = 0;
168 struct grub_affs_rblock *rblock;
170 int checksum = 0;
171 int checksumr = 0;
172 int blocksize = 0;
174 data = grub_malloc (sizeof (struct grub_affs_data));
175 if (!data)
176 return 0;
178 /* Read the bootblock. */
179 grub_disk_read (disk, 0, 0, sizeof (struct grub_affs_bblock),
180 (char *) &data->bblock);
181 if (grub_errno)
182 goto fail;
184 /* Make sure this is an affs filesystem. */
185 if (grub_strncmp ((char *) (data->bblock.type), "DOS", 3))
187 grub_error (GRUB_ERR_BAD_FS, "not an affs filesystem");
188 goto fail;
191 /* Test if the filesystem is a OFS filesystem. */
192 if (! (data->bblock.flags & GRUB_AFFS_FLAG_FFS))
194 grub_error (GRUB_ERR_BAD_FS, "ofs not yet supported");
195 goto fail;
198 /* Read the bootblock. */
199 grub_disk_read (disk, 0, 0, sizeof (struct grub_affs_bblock),
200 (char *) &data->bblock);
201 if (grub_errno)
202 goto fail;
204 /* No sane person uses more than 8KB for a block. At least I hope
205 for that person because in that case this won't work. */
206 rootblock = grub_malloc (GRUB_DISK_SECTOR_SIZE * 16);
207 if (!rootblock)
208 goto fail;
210 rblock = (struct grub_affs_rblock *) rootblock;
212 /* Read the rootblock. */
213 grub_disk_read (disk, (disk->total_sectors >> 1) + blocksize, 0,
214 GRUB_DISK_SECTOR_SIZE * 16, (char *) rootblock);
215 if (grub_errno)
216 goto fail;
218 /* The filesystem blocksize is not stored anywhere in the filesystem
219 itself. One way to determine it is reading blocks for the
220 rootblock until the checksum is correct. */
221 checksumr = grub_be_to_cpu32 (rblock->checksum);
222 rblock->checksum = 0;
223 for (blocksize = 0; blocksize < 8; blocksize++)
225 grub_uint32_t *currblock = rootblock + GRUB_DISK_SECTOR_SIZE * blocksize;
226 unsigned int i;
228 for (i = 0; i < GRUB_DISK_SECTOR_SIZE / sizeof (*currblock); i++)
229 checksum += grub_be_to_cpu32 (currblock[i]);
231 if (checksumr == -checksum)
232 break;
234 if (-checksum != checksumr)
236 grub_error (GRUB_ERR_BAD_FS, "affs blocksize could not be determined");
237 goto fail;
239 blocksize++;
241 data->blocksize = blocksize;
242 data->disk = disk;
243 data->htsize = grub_be_to_cpu32 (rblock->htsize);
244 data->diropen.data = data;
245 data->diropen.block = (disk->total_sectors >> 1);
247 grub_free (rootblock);
249 return data;
251 fail:
252 if (grub_errno == GRUB_ERR_OUT_OF_RANGE)
253 grub_error (GRUB_ERR_BAD_FS, "not an affs filesystem");
255 grub_free (data);
256 grub_free (rootblock);
257 return 0;
261 static char *
262 grub_affs_read_symlink (grub_fshelp_node_t node)
264 struct grub_affs_data *data = node->data;
265 char *symlink;
267 symlink = grub_malloc (GRUB_AFFS_SYMLINK_SIZE (data->blocksize));
268 if (!symlink)
269 return 0;
271 grub_disk_read (data->disk, node->block, GRUB_AFFS_SYMLINK_OFFSET,
272 GRUB_AFFS_SYMLINK_SIZE (data->blocksize), symlink);
273 if (grub_errno)
275 grub_free (symlink);
276 return 0;
278 grub_printf ("Symlink: `%s'\n", symlink);
279 return symlink;
283 static int
284 grub_affs_iterate_dir (grub_fshelp_node_t dir,
285 int NESTED_FUNC_ATTR
286 (*hook) (const char *filename,
287 enum grub_fshelp_filetype filetype,
288 grub_fshelp_node_t node))
290 int i;
291 struct grub_affs_file file;
292 struct grub_fshelp_node *node = 0;
293 struct grub_affs_data *data = dir->data;
294 grub_uint32_t *hashtable;
296 auto int NESTED_FUNC_ATTR grub_affs_create_node (const char *name, int block,
297 int size, int type);
299 int NESTED_FUNC_ATTR grub_affs_create_node (const char *name, int block,
300 int size, int type)
302 node = grub_malloc (sizeof (*node));
303 if (!node)
305 grub_free (hashtable);
306 return 1;
309 node->data = data;
310 node->size = size;
311 node->block = block;
312 node->parent = grub_be_to_cpu32 (file.parent);
314 if (hook (name, type, node))
316 grub_free (hashtable);
317 return 1;
319 return 0;
322 hashtable = grub_malloc (data->htsize * sizeof (*hashtable));
323 if (!hashtable)
324 return 1;
326 grub_disk_read (data->disk, dir->block, GRUB_AFFS_HASHTABLE_OFFSET,
327 data->htsize * sizeof (*hashtable), (char *) hashtable);
328 if (grub_errno)
329 goto fail;
331 /* Create the directory entries for `.' and `..'. */
332 if (grub_affs_create_node (".", dir->block, dir->size, GRUB_FSHELP_DIR))
333 return 1;
334 if (grub_affs_create_node ("..", dir->parent ? dir->parent : dir->block,
335 dir->size, GRUB_FSHELP_DIR))
336 return 1;
338 for (i = 0; i < data->htsize; i++)
340 enum grub_fshelp_filetype type;
341 grub_uint64_t next;
343 if (!hashtable[i])
344 continue;
346 /* Every entry in the hashtable can be chained. Read the entire
347 chain. */
348 next = grub_be_to_cpu32 (hashtable[i]);
350 while (next)
352 grub_disk_read (data->disk, next + data->blocksize - 1,
353 data->blocksize * GRUB_DISK_SECTOR_SIZE
354 - GRUB_AFFS_FILE_LOCATION,
355 sizeof (file), (char *) &file);
356 if (grub_errno)
357 goto fail;
359 file.name[file.namelen] = '\0';
361 if ((int) grub_be_to_cpu32 (file.type) == GRUB_AFFS_FILETYPE_DIR)
362 type = GRUB_FSHELP_REG;
363 else if (grub_be_to_cpu32 (file.type) == GRUB_AFFS_FILETYPE_REG)
364 type = GRUB_FSHELP_DIR;
365 else if (grub_be_to_cpu32 (file.type) == GRUB_AFFS_FILETYPE_SYMLINK)
366 type = GRUB_FSHELP_SYMLINK;
367 else
368 type = GRUB_FSHELP_UNKNOWN;
370 if (grub_affs_create_node ((char *) (file.name), next,
371 grub_be_to_cpu32 (file.size), type))
372 return 1;
374 next = grub_be_to_cpu32 (file.next);
378 grub_free (hashtable);
379 return 0;
381 fail:
382 grub_free (node);
383 grub_free (hashtable);
384 return 1;
388 /* Open a file named NAME and initialize FILE. */
389 static grub_err_t
390 grub_affs_open (struct grub_file *file, const char *name)
392 struct grub_affs_data *data;
393 struct grub_fshelp_node *fdiro = 0;
395 #ifndef GRUB_UTIL
396 grub_dl_ref (my_mod);
397 #endif
399 data = grub_affs_mount (file->device->disk);
400 if (!data)
401 goto fail;
403 grub_fshelp_find_file (name, &data->diropen, &fdiro, grub_affs_iterate_dir,
404 grub_affs_read_symlink, GRUB_FSHELP_REG);
405 if (grub_errno)
406 goto fail;
408 file->size = fdiro->size;
409 data->diropen = *fdiro;
410 grub_free (fdiro);
412 file->data = data;
413 file->offset = 0;
415 return 0;
417 fail:
418 if (data && fdiro != &data->diropen)
419 grub_free (fdiro);
420 grub_free (data);
422 #ifndef GRUB_UTIL
423 grub_dl_unref (my_mod);
424 #endif
426 return grub_errno;
430 static grub_err_t
431 grub_affs_close (grub_file_t file)
433 grub_free (file->data);
435 #ifndef GRUB_UTIL
436 grub_dl_unref (my_mod);
437 #endif
439 return GRUB_ERR_NONE;
443 /* Read LEN bytes data from FILE into BUF. */
444 static grub_ssize_t
445 grub_affs_read (grub_file_t file, char *buf, grub_size_t len)
447 struct grub_affs_data *data =
448 (struct grub_affs_data *) file->data;
450 int size = grub_affs_read_file (&data->diropen, file->read_hook,
451 file->offset, len, buf);
453 return size;
457 static grub_err_t
458 grub_affs_dir (grub_device_t device, const char *path,
459 int (*hook) (const char *filename, int dir))
461 struct grub_affs_data *data = 0;
462 struct grub_fshelp_node *fdiro = 0;
464 auto int NESTED_FUNC_ATTR iterate (const char *filename,
465 enum grub_fshelp_filetype filetype,
466 grub_fshelp_node_t node);
468 int NESTED_FUNC_ATTR iterate (const char *filename,
469 enum grub_fshelp_filetype filetype,
470 grub_fshelp_node_t node)
472 grub_free (node);
474 if (filetype == GRUB_FSHELP_DIR)
475 return hook (filename, 1);
476 else
477 return hook (filename, 0);
479 return 0;
482 #ifndef GRUB_UTIL
483 grub_dl_ref (my_mod);
484 #endif
486 data = grub_affs_mount (device->disk);
487 if (!data)
488 goto fail;
490 grub_fshelp_find_file (path, &data->diropen, &fdiro, grub_affs_iterate_dir,
491 grub_affs_read_symlink, GRUB_FSHELP_DIR);
492 if (grub_errno)
493 goto fail;
495 grub_affs_iterate_dir (fdiro, iterate);
497 fail:
498 if (data && fdiro != &data->diropen)
499 grub_free (fdiro);
500 grub_free (data);
502 #ifndef GRUB_UTIL
503 grub_dl_unref (my_mod);
504 #endif
506 return grub_errno;
510 static grub_err_t
511 grub_affs_label (grub_device_t device, char **label)
513 struct grub_affs_data *data;
514 struct grub_affs_file file;
515 grub_disk_t disk = device->disk;
517 #ifndef GRUB_UTIL
518 grub_dl_ref (my_mod);
519 #endif
521 data = grub_affs_mount (disk);
522 if (data)
524 /* The rootblock maps quite well on a file header block, it's
525 something we can use here. */
526 grub_disk_read (data->disk, disk->total_sectors >> 1,
527 data->blocksize * (GRUB_DISK_SECTOR_SIZE
528 - GRUB_AFFS_FILE_LOCATION),
529 sizeof (file), (char *) &file);
530 if (grub_errno)
531 return 0;
533 *label = grub_strndup ((char *) (file.name), file.namelen);
535 else
536 *label = 0;
538 #ifndef GRUB_UTIL
539 grub_dl_unref (my_mod);
540 #endif
542 grub_free (data);
544 return grub_errno;
548 static struct grub_fs grub_affs_fs =
550 .name = "affs",
551 .dir = grub_affs_dir,
552 .open = grub_affs_open,
553 .read = grub_affs_read,
554 .close = grub_affs_close,
555 .label = grub_affs_label,
556 .next = 0
559 GRUB_MOD_INIT(affs)
561 grub_fs_register (&grub_affs_fs);
562 #ifndef GRUB_UTIL
563 my_mod = mod;
564 #endif
567 GRUB_MOD_FINI(affs)
569 grub_fs_unregister (&grub_affs_fs);