2009-07-06 Daniel Mierswa <impulze@impulze.org>
[grub2/bean.git] / fs / affs.c
blob286b99f9a0a7bdfbc3e6458c13f6d05e09e77e71
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 static grub_dl_t my_mod;
110 static grub_disk_addr_t
111 grub_affs_read_block (grub_fshelp_node_t node, grub_disk_addr_t fileblock)
113 int links;
114 grub_uint32_t pos;
115 int block = node->block;
116 struct grub_affs_file file;
117 struct grub_affs_data *data = node->data;
118 grub_uint32_t mod;
120 /* Find the block that points to the fileblock we are looking up by
121 following the chain until the right table is reached. */
122 for (links = grub_divmod64 (fileblock, data->htsize, &mod); links; links--)
124 grub_disk_read (data->disk, block + data->blocksize - 1,
125 data->blocksize * (GRUB_DISK_SECTOR_SIZE
126 - GRUB_AFFS_FILE_LOCATION),
127 sizeof (file), &file);
128 if (grub_errno)
129 return 0;
131 block = grub_be_to_cpu32 (file.extension);
134 /* Translate the fileblock to the block within the right table. */
135 fileblock = mod;
136 grub_disk_read (data->disk, block,
137 GRUB_AFFS_BLOCKPTR_OFFSET
138 + (data->htsize - fileblock - 1) * sizeof (pos),
139 sizeof (pos), &pos);
140 if (grub_errno)
141 return 0;
143 return grub_be_to_cpu32 (pos);
147 /* Read LEN bytes from the file described by DATA starting with byte
148 POS. Return the amount of read bytes in READ. */
149 static grub_ssize_t
150 grub_affs_read_file (grub_fshelp_node_t node,
151 void NESTED_FUNC_ATTR (*read_hook) (grub_disk_addr_t sector,
152 unsigned offset, unsigned length),
153 int pos, grub_size_t len, char *buf)
155 return grub_fshelp_read_file (node->data->disk, node, read_hook,
156 pos, len, buf, grub_affs_read_block,
157 node->size, 0);
161 static struct grub_affs_data *
162 grub_affs_mount (grub_disk_t disk)
164 struct grub_affs_data *data;
165 grub_uint32_t *rootblock = 0;
166 struct grub_affs_rblock *rblock;
168 int checksum = 0;
169 int checksumr = 0;
170 int blocksize = 0;
172 data = grub_malloc (sizeof (struct grub_affs_data));
173 if (!data)
174 return 0;
176 /* Read the bootblock. */
177 grub_disk_read (disk, 0, 0, sizeof (struct grub_affs_bblock),
178 &data->bblock);
179 if (grub_errno)
180 goto fail;
182 /* Make sure this is an affs filesystem. */
183 if (grub_strncmp ((char *) (data->bblock.type), "DOS", 3))
185 grub_error (GRUB_ERR_BAD_FS, "not an affs filesystem");
186 goto fail;
189 /* Test if the filesystem is a OFS filesystem. */
190 if (! (data->bblock.flags & GRUB_AFFS_FLAG_FFS))
192 grub_error (GRUB_ERR_BAD_FS, "ofs not yet supported");
193 goto fail;
196 /* Read the bootblock. */
197 grub_disk_read (disk, 0, 0, sizeof (struct grub_affs_bblock),
198 &data->bblock);
199 if (grub_errno)
200 goto fail;
202 /* No sane person uses more than 8KB for a block. At least I hope
203 for that person because in that case this won't work. */
204 rootblock = grub_malloc (GRUB_DISK_SECTOR_SIZE * 16);
205 if (!rootblock)
206 goto fail;
208 rblock = (struct grub_affs_rblock *) rootblock;
210 /* Read the rootblock. */
211 grub_disk_read (disk, (disk->total_sectors >> 1) + blocksize, 0,
212 GRUB_DISK_SECTOR_SIZE * 16, rootblock);
213 if (grub_errno)
214 goto fail;
216 /* The filesystem blocksize is not stored anywhere in the filesystem
217 itself. One way to determine it is reading blocks for the
218 rootblock until the checksum is correct. */
219 checksumr = grub_be_to_cpu32 (rblock->checksum);
220 rblock->checksum = 0;
221 for (blocksize = 0; blocksize < 8; blocksize++)
223 grub_uint32_t *currblock = rootblock + GRUB_DISK_SECTOR_SIZE * blocksize;
224 unsigned int i;
226 for (i = 0; i < GRUB_DISK_SECTOR_SIZE / sizeof (*currblock); i++)
227 checksum += grub_be_to_cpu32 (currblock[i]);
229 if (checksumr == -checksum)
230 break;
232 if (-checksum != checksumr)
234 grub_error (GRUB_ERR_BAD_FS, "affs blocksize could not be determined");
235 goto fail;
237 blocksize++;
239 data->blocksize = blocksize;
240 data->disk = disk;
241 data->htsize = grub_be_to_cpu32 (rblock->htsize);
242 data->diropen.data = data;
243 data->diropen.block = (disk->total_sectors >> 1);
245 grub_free (rootblock);
247 return data;
249 fail:
250 if (grub_errno == GRUB_ERR_OUT_OF_RANGE)
251 grub_error (GRUB_ERR_BAD_FS, "not an affs filesystem");
253 grub_free (data);
254 grub_free (rootblock);
255 return 0;
259 static char *
260 grub_affs_read_symlink (grub_fshelp_node_t node)
262 struct grub_affs_data *data = node->data;
263 char *symlink;
265 symlink = grub_malloc (GRUB_AFFS_SYMLINK_SIZE (data->blocksize));
266 if (!symlink)
267 return 0;
269 grub_disk_read (data->disk, node->block, GRUB_AFFS_SYMLINK_OFFSET,
270 GRUB_AFFS_SYMLINK_SIZE (data->blocksize), symlink);
271 if (grub_errno)
273 grub_free (symlink);
274 return 0;
276 grub_printf ("Symlink: `%s'\n", symlink);
277 return symlink;
281 static int
282 grub_affs_iterate_dir (grub_fshelp_node_t dir,
283 int NESTED_FUNC_ATTR
284 (*hook) (const char *filename,
285 enum grub_fshelp_filetype filetype,
286 grub_fshelp_node_t node))
288 int i;
289 struct grub_affs_file file;
290 struct grub_fshelp_node *node = 0;
291 struct grub_affs_data *data = dir->data;
292 grub_uint32_t *hashtable;
294 auto int NESTED_FUNC_ATTR grub_affs_create_node (const char *name, int block,
295 int size, int type);
297 int NESTED_FUNC_ATTR grub_affs_create_node (const char *name, int block,
298 int size, int type)
300 node = grub_malloc (sizeof (*node));
301 if (!node)
303 grub_free (hashtable);
304 return 1;
307 node->data = data;
308 node->size = size;
309 node->block = block;
310 node->parent = grub_be_to_cpu32 (file.parent);
312 if (hook (name, type, node))
314 grub_free (hashtable);
315 return 1;
317 return 0;
320 hashtable = grub_malloc (data->htsize * sizeof (*hashtable));
321 if (!hashtable)
322 return 1;
324 grub_disk_read (data->disk, dir->block, GRUB_AFFS_HASHTABLE_OFFSET,
325 data->htsize * sizeof (*hashtable), (char *) hashtable);
326 if (grub_errno)
327 goto fail;
329 /* Create the directory entries for `.' and `..'. */
330 if (grub_affs_create_node (".", dir->block, dir->size, GRUB_FSHELP_DIR))
331 return 1;
332 if (grub_affs_create_node ("..", dir->parent ? dir->parent : dir->block,
333 dir->size, GRUB_FSHELP_DIR))
334 return 1;
336 for (i = 0; i < data->htsize; i++)
338 enum grub_fshelp_filetype type;
339 grub_uint64_t next;
341 if (!hashtable[i])
342 continue;
344 /* Every entry in the hashtable can be chained. Read the entire
345 chain. */
346 next = grub_be_to_cpu32 (hashtable[i]);
348 while (next)
350 grub_disk_read (data->disk, next + data->blocksize - 1,
351 data->blocksize * GRUB_DISK_SECTOR_SIZE
352 - GRUB_AFFS_FILE_LOCATION,
353 sizeof (file), (char *) &file);
354 if (grub_errno)
355 goto fail;
357 file.name[file.namelen] = '\0';
359 if ((int) grub_be_to_cpu32 (file.type) == GRUB_AFFS_FILETYPE_DIR)
360 type = GRUB_FSHELP_REG;
361 else if (grub_be_to_cpu32 (file.type) == GRUB_AFFS_FILETYPE_REG)
362 type = GRUB_FSHELP_DIR;
363 else if (grub_be_to_cpu32 (file.type) == GRUB_AFFS_FILETYPE_SYMLINK)
364 type = GRUB_FSHELP_SYMLINK;
365 else
366 type = GRUB_FSHELP_UNKNOWN;
368 if (grub_affs_create_node ((char *) (file.name), next,
369 grub_be_to_cpu32 (file.size), type))
370 return 1;
372 next = grub_be_to_cpu32 (file.next);
376 grub_free (hashtable);
377 return 0;
379 fail:
380 grub_free (node);
381 grub_free (hashtable);
382 return 0;
386 /* Open a file named NAME and initialize FILE. */
387 static grub_err_t
388 grub_affs_open (struct grub_file *file, const char *name)
390 struct grub_affs_data *data;
391 struct grub_fshelp_node *fdiro = 0;
393 grub_dl_ref (my_mod);
395 data = grub_affs_mount (file->device->disk);
396 if (!data)
397 goto fail;
399 grub_fshelp_find_file (name, &data->diropen, &fdiro, grub_affs_iterate_dir,
400 grub_affs_read_symlink, GRUB_FSHELP_REG);
401 if (grub_errno)
402 goto fail;
404 file->size = fdiro->size;
405 data->diropen = *fdiro;
406 grub_free (fdiro);
408 file->data = data;
409 file->offset = 0;
411 return 0;
413 fail:
414 if (data && fdiro != &data->diropen)
415 grub_free (fdiro);
416 grub_free (data);
418 grub_dl_unref (my_mod);
420 return grub_errno;
424 static grub_err_t
425 grub_affs_close (grub_file_t file)
427 grub_free (file->data);
429 grub_dl_unref (my_mod);
431 return GRUB_ERR_NONE;
435 /* Read LEN bytes data from FILE into BUF. */
436 static grub_ssize_t
437 grub_affs_read (grub_file_t file, char *buf, grub_size_t len)
439 struct grub_affs_data *data =
440 (struct grub_affs_data *) file->data;
442 int size = grub_affs_read_file (&data->diropen, file->read_hook,
443 file->offset, len, buf);
445 return size;
449 static grub_err_t
450 grub_affs_dir (grub_device_t device, const char *path,
451 int (*hook) (const char *filename,
452 const struct grub_dirhook_info *info))
454 struct grub_affs_data *data = 0;
455 struct grub_fshelp_node *fdiro = 0;
457 auto int NESTED_FUNC_ATTR iterate (const char *filename,
458 enum grub_fshelp_filetype filetype,
459 grub_fshelp_node_t node);
461 int NESTED_FUNC_ATTR iterate (const char *filename,
462 enum grub_fshelp_filetype filetype,
463 grub_fshelp_node_t node)
465 struct grub_dirhook_info info;
466 grub_memset (&info, 0, sizeof (info));
467 info.dir = ((filetype & GRUB_FSHELP_TYPE_MASK) == GRUB_FSHELP_DIR);
468 grub_free (node);
469 return hook (filename, &info);
472 grub_dl_ref (my_mod);
474 data = grub_affs_mount (device->disk);
475 if (!data)
476 goto fail;
478 grub_fshelp_find_file (path, &data->diropen, &fdiro, grub_affs_iterate_dir,
479 grub_affs_read_symlink, GRUB_FSHELP_DIR);
480 if (grub_errno)
481 goto fail;
483 grub_affs_iterate_dir (fdiro, iterate);
485 fail:
486 if (data && fdiro != &data->diropen)
487 grub_free (fdiro);
488 grub_free (data);
490 grub_dl_unref (my_mod);
492 return grub_errno;
496 static grub_err_t
497 grub_affs_label (grub_device_t device, char **label)
499 struct grub_affs_data *data;
500 struct grub_affs_file file;
501 grub_disk_t disk = device->disk;
503 grub_dl_ref (my_mod);
505 data = grub_affs_mount (disk);
506 if (data)
508 /* The rootblock maps quite well on a file header block, it's
509 something we can use here. */
510 grub_disk_read (data->disk, disk->total_sectors >> 1,
511 data->blocksize * (GRUB_DISK_SECTOR_SIZE
512 - GRUB_AFFS_FILE_LOCATION),
513 sizeof (file), &file);
514 if (grub_errno)
515 return 0;
517 *label = grub_strndup ((char *) (file.name), file.namelen);
519 else
520 *label = 0;
522 grub_dl_unref (my_mod);
524 grub_free (data);
526 return grub_errno;
530 static struct grub_fs grub_affs_fs =
532 .name = "affs",
533 .dir = grub_affs_dir,
534 .open = grub_affs_open,
535 .read = grub_affs_read,
536 .close = grub_affs_close,
537 .label = grub_affs_label,
538 .next = 0
541 GRUB_MOD_INIT(affs)
543 grub_fs_register (&grub_affs_fs);
544 my_mod = mod;
547 GRUB_MOD_FINI(affs)
549 grub_fs_unregister (&grub_affs_fs);