Indentation fix, cleanup.
[AROS.git] / arch / all-pc / boot / grub2-aros / grub-core / fs / hfsplus.c
blob8f07f8544779fa276e8f6ccc060de5d2f638000e
1 /* hfsplus.c - HFS+ 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 /* HFS+ is documented at http://developer.apple.com/technotes/tn/tn1150.html */
22 #define grub_fshelp_node grub_hfsplus_file
23 #include <grub/err.h>
24 #include <grub/file.h>
25 #include <grub/mm.h>
26 #include <grub/misc.h>
27 #include <grub/disk.h>
28 #include <grub/dl.h>
29 #include <grub/types.h>
30 #include <grub/fshelp.h>
31 #include <grub/hfs.h>
32 #include <grub/charset.h>
33 #include <grub/hfsplus.h>
35 GRUB_MOD_LICENSE ("GPLv3+");
37 /* The type of node. */
38 enum grub_hfsplus_btnode_type
40 GRUB_HFSPLUS_BTNODE_TYPE_LEAF = -1,
41 GRUB_HFSPLUS_BTNODE_TYPE_INDEX = 0,
42 GRUB_HFSPLUS_BTNODE_TYPE_HEADER = 1,
43 GRUB_HFSPLUS_BTNODE_TYPE_MAP = 2,
46 /* The header of a HFS+ B+ Tree. */
47 struct grub_hfsplus_btheader
49 grub_uint16_t depth;
50 grub_uint32_t root;
51 grub_uint32_t leaf_records;
52 grub_uint32_t first_leaf_node;
53 grub_uint32_t last_leaf_node;
54 grub_uint16_t nodesize;
55 grub_uint16_t keysize;
56 grub_uint32_t total_nodes;
57 grub_uint32_t free_nodes;
58 grub_uint16_t reserved1;
59 grub_uint32_t clump_size; /* ignored */
60 grub_uint8_t btree_type;
61 grub_uint8_t key_compare;
62 grub_uint32_t attributes;
63 } GRUB_PACKED;
65 struct grub_hfsplus_catfile
67 grub_uint16_t type;
68 grub_uint16_t flags;
69 grub_uint32_t parentid; /* Thread only. */
70 grub_uint32_t fileid;
71 grub_uint8_t unused1[4];
72 grub_uint32_t mtime;
73 grub_uint8_t unused2[22];
74 grub_uint16_t mode;
75 grub_uint8_t unused3[44];
76 struct grub_hfsplus_forkdata data;
77 struct grub_hfsplus_forkdata resource;
78 } GRUB_PACKED;
80 /* Filetype information as used in inodes. */
81 #define GRUB_HFSPLUS_FILEMODE_MASK 0170000
82 #define GRUB_HFSPLUS_FILEMODE_REG 0100000
83 #define GRUB_HFSPLUS_FILEMODE_DIRECTORY 0040000
84 #define GRUB_HFSPLUS_FILEMODE_SYMLINK 0120000
86 /* Some pre-defined file IDs. */
87 enum
89 GRUB_HFSPLUS_FILEID_ROOTDIR = 2,
90 GRUB_HFSPLUS_FILEID_OVERFLOW = 3,
91 GRUB_HFSPLUS_FILEID_CATALOG = 4,
92 GRUB_HFSPLUS_FILEID_ATTR = 8
95 enum grub_hfsplus_filetype
97 GRUB_HFSPLUS_FILETYPE_DIR = 1,
98 GRUB_HFSPLUS_FILETYPE_REG = 2,
99 GRUB_HFSPLUS_FILETYPE_DIR_THREAD = 3,
100 GRUB_HFSPLUS_FILETYPE_REG_THREAD = 4
103 #define GRUB_HFSPLUSX_BINARYCOMPARE 0xBC
104 #define GRUB_HFSPLUSX_CASEFOLDING 0xCF
106 static grub_dl_t my_mod;
110 grub_err_t (*grub_hfsplus_open_compressed) (struct grub_fshelp_node *node);
111 grub_ssize_t (*grub_hfsplus_read_compressed) (struct grub_hfsplus_file *node,
112 grub_off_t pos,
113 grub_size_t len,
114 char *buf);
116 /* Find the extent that points to FILEBLOCK. If it is not in one of
117 the 8 extents described by EXTENT, return -1. In that case set
118 FILEBLOCK to the next block. */
119 static grub_disk_addr_t
120 grub_hfsplus_find_block (struct grub_hfsplus_extent *extent,
121 grub_disk_addr_t *fileblock)
123 int i;
124 grub_disk_addr_t blksleft = *fileblock;
126 /* First lookup the file in the given extents. */
127 for (i = 0; i < 8; i++)
129 if (blksleft < grub_be_to_cpu32 (extent[i].count))
130 return grub_be_to_cpu32 (extent[i].start) + blksleft;
131 blksleft -= grub_be_to_cpu32 (extent[i].count);
134 *fileblock = blksleft;
135 return 0xffffffffffffffffULL;
138 static int grub_hfsplus_cmp_extkey (struct grub_hfsplus_key *keya,
139 struct grub_hfsplus_key_internal *keyb);
141 /* Search for the block FILEBLOCK inside the file NODE. Return the
142 blocknumber of this block on disk. */
143 static grub_disk_addr_t
144 grub_hfsplus_read_block (grub_fshelp_node_t node, grub_disk_addr_t fileblock)
146 struct grub_hfsplus_btnode *nnode = 0;
147 grub_disk_addr_t blksleft = fileblock;
148 struct grub_hfsplus_extent *extents = node->compressed
149 ? &node->resource_extents[0] : &node->extents[0];
151 while (1)
153 struct grub_hfsplus_extkey *key;
154 struct grub_hfsplus_key_internal extoverflow;
155 grub_disk_addr_t blk;
156 grub_off_t ptr;
158 /* Try to find this block in the current set of extents. */
159 blk = grub_hfsplus_find_block (extents, &blksleft);
161 /* The previous iteration of this loop allocated memory. The
162 code above used this memory, it can be freed now. */
163 grub_free (nnode);
164 nnode = 0;
166 if (blk != 0xffffffffffffffffULL)
167 return blk;
169 /* For the extent overflow file, extra extents can't be found in
170 the extent overflow file. If this happens, you found a
171 bug... */
172 if (node->fileid == GRUB_HFSPLUS_FILEID_OVERFLOW)
174 grub_error (GRUB_ERR_READ_ERROR,
175 "extra extents found in an extend overflow file");
176 break;
179 /* Set up the key to look for in the extent overflow file. */
180 extoverflow.extkey.fileid = node->fileid;
181 extoverflow.extkey.type = 0;
182 extoverflow.extkey.start = fileblock - blksleft;
183 extoverflow.extkey.type = node->compressed ? 0xff : 0;
184 if (grub_hfsplus_btree_search (&node->data->extoverflow_tree,
185 &extoverflow,
186 grub_hfsplus_cmp_extkey, &nnode, &ptr)
187 || !nnode)
189 grub_error (GRUB_ERR_READ_ERROR,
190 "no block found for the file id 0x%x and the block offset 0x%x",
191 node->fileid, fileblock);
192 break;
195 /* The extent overflow file has 8 extents right after the key. */
196 key = (struct grub_hfsplus_extkey *)
197 grub_hfsplus_btree_recptr (&node->data->extoverflow_tree, nnode, ptr);
198 extents = (struct grub_hfsplus_extent *) (key + 1);
200 /* The block wasn't found. Perhaps the next iteration will find
201 it. The last block we found is stored in BLKSLEFT now. */
204 grub_free (nnode);
206 /* Too bad, you lose. */
207 return -1;
211 /* Read LEN bytes from the file described by DATA starting with byte
212 POS. Return the amount of read bytes in READ. */
213 grub_ssize_t
214 grub_hfsplus_read_file (grub_fshelp_node_t node,
215 grub_disk_read_hook_t read_hook, void *read_hook_data,
216 grub_off_t pos, grub_size_t len, char *buf)
218 return grub_fshelp_read_file (node->data->disk, node,
219 read_hook, read_hook_data,
220 pos, len, buf, grub_hfsplus_read_block,
221 node->size,
222 node->data->log2blksize - GRUB_DISK_SECTOR_BITS,
223 node->data->embedded_offset);
226 static struct grub_hfsplus_data *
227 grub_hfsplus_mount (grub_disk_t disk)
229 struct grub_hfsplus_data *data;
230 struct grub_hfsplus_btheader header;
231 struct grub_hfsplus_btnode node;
232 grub_uint16_t magic;
233 union {
234 struct grub_hfs_sblock hfs;
235 struct grub_hfsplus_volheader hfsplus;
236 } volheader;
238 data = grub_malloc (sizeof (*data));
239 if (!data)
240 return 0;
242 data->disk = disk;
244 /* Read the bootblock. */
245 grub_disk_read (disk, GRUB_HFSPLUS_SBLOCK, 0, sizeof (volheader),
246 &volheader);
247 if (grub_errno)
248 goto fail;
250 data->embedded_offset = 0;
251 if (grub_be_to_cpu16 (volheader.hfs.magic) == GRUB_HFS_MAGIC)
253 grub_disk_addr_t extent_start;
254 grub_disk_addr_t ablk_size;
255 grub_disk_addr_t ablk_start;
257 /* See if there's an embedded HFS+ filesystem. */
258 if (grub_be_to_cpu16 (volheader.hfs.embed_sig) != GRUB_HFSPLUS_MAGIC)
260 grub_error (GRUB_ERR_BAD_FS, "not a HFS+ filesystem");
261 goto fail;
264 /* Calculate the offset needed to translate HFS+ sector numbers. */
265 extent_start = grub_be_to_cpu16 (volheader.hfs.embed_extent.first_block);
266 ablk_size = grub_be_to_cpu32 (volheader.hfs.blksz);
267 ablk_start = grub_be_to_cpu16 (volheader.hfs.first_block);
268 data->embedded_offset = (ablk_start
269 + extent_start
270 * (ablk_size >> GRUB_DISK_SECTOR_BITS));
272 grub_disk_read (disk, data->embedded_offset + GRUB_HFSPLUS_SBLOCK, 0,
273 sizeof (volheader), &volheader);
274 if (grub_errno)
275 goto fail;
278 /* Make sure this is an HFS+ filesystem. XXX: Do we really support
279 HFX? */
280 magic = grub_be_to_cpu16 (volheader.hfsplus.magic);
281 if (((magic != GRUB_HFSPLUS_MAGIC) && (magic != GRUB_HFSPLUSX_MAGIC))
282 || volheader.hfsplus.blksize == 0
283 || ((volheader.hfsplus.blksize & (volheader.hfsplus.blksize - 1)) != 0)
284 || grub_be_to_cpu32 (volheader.hfsplus.blksize) < GRUB_DISK_SECTOR_SIZE)
286 grub_error (GRUB_ERR_BAD_FS, "not a HFS+ filesystem");
287 goto fail;
290 grub_memcpy (&data->volheader, &volheader.hfsplus,
291 sizeof (volheader.hfsplus));
293 for (data->log2blksize = 0;
294 (1U << data->log2blksize) < grub_be_to_cpu32 (data->volheader.blksize);
295 data->log2blksize++);
297 /* Make a new node for the catalog tree. */
298 data->catalog_tree.file.data = data;
299 data->catalog_tree.file.fileid = GRUB_HFSPLUS_FILEID_CATALOG;
300 data->catalog_tree.file.compressed = 0;
301 grub_memcpy (&data->catalog_tree.file.extents,
302 data->volheader.catalog_file.extents,
303 sizeof data->volheader.catalog_file.extents);
304 data->catalog_tree.file.size =
305 grub_be_to_cpu64 (data->volheader.catalog_file.size);
307 data->attr_tree.file.data = data;
308 data->attr_tree.file.fileid = GRUB_HFSPLUS_FILEID_ATTR;
309 grub_memcpy (&data->attr_tree.file.extents,
310 data->volheader.attr_file.extents,
311 sizeof data->volheader.attr_file.extents);
313 data->attr_tree.file.size =
314 grub_be_to_cpu64 (data->volheader.attr_file.size);
315 data->attr_tree.file.compressed = 0;
317 /* Make a new node for the extent overflow file. */
318 data->extoverflow_tree.file.data = data;
319 data->extoverflow_tree.file.fileid = GRUB_HFSPLUS_FILEID_OVERFLOW;
320 data->extoverflow_tree.file.compressed = 0;
321 grub_memcpy (&data->extoverflow_tree.file.extents,
322 data->volheader.extents_file.extents,
323 sizeof data->volheader.catalog_file.extents);
325 data->extoverflow_tree.file.size =
326 grub_be_to_cpu64 (data->volheader.extents_file.size);
328 /* Read the essential information about the trees. */
329 if (grub_hfsplus_read_file (&data->catalog_tree.file, 0, 0,
330 sizeof (struct grub_hfsplus_btnode),
331 sizeof (header), (char *) &header) <= 0)
332 goto fail;
334 data->catalog_tree.root = grub_be_to_cpu32 (header.root);
335 data->catalog_tree.nodesize = grub_be_to_cpu16 (header.nodesize);
336 data->case_sensitive = ((magic == GRUB_HFSPLUSX_MAGIC) &&
337 (header.key_compare == GRUB_HFSPLUSX_BINARYCOMPARE));
339 if (grub_hfsplus_read_file (&data->extoverflow_tree.file, 0, 0,
340 sizeof (struct grub_hfsplus_btnode),
341 sizeof (header), (char *) &header) <= 0)
342 goto fail;
344 data->extoverflow_tree.root = grub_be_to_cpu32 (header.root);
346 if (grub_hfsplus_read_file (&data->extoverflow_tree.file, 0, 0, 0,
347 sizeof (node), (char *) &node) <= 0)
348 goto fail;
350 data->extoverflow_tree.root = grub_be_to_cpu32 (header.root);
351 data->extoverflow_tree.nodesize = grub_be_to_cpu16 (header.nodesize);
353 if (grub_hfsplus_read_file (&data->attr_tree.file, 0, 0,
354 sizeof (struct grub_hfsplus_btnode),
355 sizeof (header), (char *) &header) <= 0)
357 grub_errno = 0;
358 data->attr_tree.root = 0;
359 data->attr_tree.nodesize = 0;
361 else
363 data->attr_tree.root = grub_be_to_cpu32 (header.root);
364 data->attr_tree.nodesize = grub_be_to_cpu16 (header.nodesize);
367 data->dirroot.data = data;
368 data->dirroot.fileid = GRUB_HFSPLUS_FILEID_ROOTDIR;
370 return data;
372 fail:
374 if (grub_errno == GRUB_ERR_OUT_OF_RANGE)
375 grub_error (GRUB_ERR_BAD_FS, "not a HFS+ filesystem");
377 grub_free (data);
378 return 0;
381 /* Compare the on disk catalog key KEYA with the catalog key we are
382 looking for (KEYB). */
383 static int
384 grub_hfsplus_cmp_catkey (struct grub_hfsplus_key *keya,
385 struct grub_hfsplus_key_internal *keyb)
387 struct grub_hfsplus_catkey *catkey_a = &keya->catkey;
388 struct grub_hfsplus_catkey_internal *catkey_b = &keyb->catkey;
389 int diff;
390 grub_size_t len;
392 /* Safe unsigned comparison */
393 grub_uint32_t aparent = grub_be_to_cpu32 (catkey_a->parent);
394 if (aparent > catkey_b->parent)
395 return 1;
396 if (aparent < catkey_b->parent)
397 return -1;
399 len = grub_be_to_cpu16 (catkey_a->namelen);
400 if (len > catkey_b->namelen)
401 len = catkey_b->namelen;
402 /* Since it's big-endian memcmp gives the same result as manually comparing
403 uint16_t but may be faster. */
404 diff = grub_memcmp (catkey_a->name, catkey_b->name,
405 len * sizeof (catkey_a->name[0]));
406 if (diff == 0)
407 diff = grub_be_to_cpu16 (catkey_a->namelen) - catkey_b->namelen;
409 return diff;
412 /* Compare the on disk catalog key KEYA with the catalog key we are
413 looking for (KEYB). */
414 static int
415 grub_hfsplus_cmp_catkey_id (struct grub_hfsplus_key *keya,
416 struct grub_hfsplus_key_internal *keyb)
418 struct grub_hfsplus_catkey *catkey_a = &keya->catkey;
419 struct grub_hfsplus_catkey_internal *catkey_b = &keyb->catkey;
421 /* Safe unsigned comparison */
422 grub_uint32_t aparent = grub_be_to_cpu32 (catkey_a->parent);
423 if (aparent > catkey_b->parent)
424 return 1;
425 if (aparent < catkey_b->parent)
426 return -1;
428 return 0;
431 /* Compare the on disk extent overflow key KEYA with the extent
432 overflow key we are looking for (KEYB). */
433 static int
434 grub_hfsplus_cmp_extkey (struct grub_hfsplus_key *keya,
435 struct grub_hfsplus_key_internal *keyb)
437 struct grub_hfsplus_extkey *extkey_a = &keya->extkey;
438 struct grub_hfsplus_extkey_internal *extkey_b = &keyb->extkey;
439 grub_uint32_t akey;
441 /* Safe unsigned comparison */
442 akey = grub_be_to_cpu32 (extkey_a->fileid);
443 if (akey > extkey_b->fileid)
444 return 1;
445 if (akey < extkey_b->fileid)
446 return -1;
448 if (extkey_a->type > extkey_b->type)
449 return 1;
450 if (extkey_a->type < extkey_b->type)
451 return -1;
453 if (extkey_a->type > extkey_b->type)
454 return +1;
456 if (extkey_a->type < extkey_b->type)
457 return -1;
459 akey = grub_be_to_cpu32 (extkey_a->start);
460 if (akey > extkey_b->start)
461 return 1;
462 if (akey < extkey_b->start)
463 return -1;
464 return 0;
467 static char *
468 grub_hfsplus_read_symlink (grub_fshelp_node_t node)
470 char *symlink;
471 grub_ssize_t numread;
473 symlink = grub_malloc (node->size + 1);
474 if (!symlink)
475 return 0;
477 numread = grub_hfsplus_read_file (node, 0, 0, 0, node->size, symlink);
478 if (numread != (grub_ssize_t) node->size)
480 grub_free (symlink);
481 return 0;
483 symlink[node->size] = '\0';
485 return symlink;
488 static int
489 grub_hfsplus_btree_iterate_node (struct grub_hfsplus_btree *btree,
490 struct grub_hfsplus_btnode *first_node,
491 grub_disk_addr_t first_rec,
492 int (*hook) (void *record, void *hook_arg),
493 void *hook_arg)
495 grub_disk_addr_t rec;
496 grub_uint64_t saved_node = -1;
497 grub_uint64_t node_count = 0;
499 for (;;)
501 char *cnode = (char *) first_node;
503 /* Iterate over all records in this node. */
504 for (rec = first_rec; rec < grub_be_to_cpu16 (first_node->count); rec++)
506 if (hook (grub_hfsplus_btree_recptr (btree, first_node, rec), hook_arg))
507 return 1;
510 if (! first_node->next)
511 break;
513 if (node_count && first_node->next == saved_node)
515 grub_error (GRUB_ERR_BAD_FS, "HFS+ btree loop");
516 return 0;
518 if (!(node_count & (node_count - 1)))
519 saved_node = first_node->next;
520 node_count++;
522 if (grub_hfsplus_read_file (&btree->file, 0, 0,
523 (((grub_disk_addr_t)
524 grub_be_to_cpu32 (first_node->next))
525 * btree->nodesize),
526 btree->nodesize, cnode) <= 0)
527 return 1;
529 /* Don't skip any record in the next iteration. */
530 first_rec = 0;
533 return 0;
536 /* Lookup the node described by KEY in the B+ Tree BTREE. Compare
537 keys using the function COMPARE_KEYS. When a match is found,
538 return the node in MATCHNODE and a pointer to the data in this node
539 in KEYOFFSET. MATCHNODE should be freed by the caller. */
540 grub_err_t
541 grub_hfsplus_btree_search (struct grub_hfsplus_btree *btree,
542 struct grub_hfsplus_key_internal *key,
543 int (*compare_keys) (struct grub_hfsplus_key *keya,
544 struct grub_hfsplus_key_internal *keyb),
545 struct grub_hfsplus_btnode **matchnode,
546 grub_off_t *keyoffset)
548 grub_uint64_t currnode;
549 char *node;
550 struct grub_hfsplus_btnode *nodedesc;
551 grub_disk_addr_t rec;
552 grub_uint64_t save_node;
553 grub_uint64_t node_count = 0;
555 if (!btree->nodesize)
557 *matchnode = 0;
558 return 0;
561 node = grub_malloc (btree->nodesize);
562 if (! node)
563 return grub_errno;
565 currnode = btree->root;
566 save_node = currnode - 1;
567 while (1)
569 int match = 0;
571 if (save_node == currnode)
573 grub_free (node);
574 return grub_error (GRUB_ERR_BAD_FS, "HFS+ btree loop");
576 if (!(node_count & (node_count - 1)))
577 save_node = currnode;
578 node_count++;
580 /* Read a node. */
581 if (grub_hfsplus_read_file (&btree->file, 0, 0,
582 (grub_disk_addr_t) currnode
583 * (grub_disk_addr_t) btree->nodesize,
584 btree->nodesize, (char *) node) <= 0)
586 grub_free (node);
587 return grub_error (GRUB_ERR_BAD_FS, "couldn't read i-node");
590 nodedesc = (struct grub_hfsplus_btnode *) node;
592 /* Find the record in this tree. */
593 for (rec = 0; rec < grub_be_to_cpu16 (nodedesc->count); rec++)
595 struct grub_hfsplus_key *currkey;
596 currkey = grub_hfsplus_btree_recptr (btree, nodedesc, rec);
598 /* The action that has to be taken depend on the type of
599 record. */
600 if (nodedesc->type == GRUB_HFSPLUS_BTNODE_TYPE_LEAF
601 && compare_keys (currkey, key) == 0)
603 /* An exact match was found! */
605 *matchnode = nodedesc;
606 *keyoffset = rec;
608 return 0;
610 else if (nodedesc->type == GRUB_HFSPLUS_BTNODE_TYPE_INDEX)
612 void *pointer;
614 /* The place where the key could have been found didn't
615 contain the key. This means that the previous match
616 is the one that should be followed. */
617 if (compare_keys (currkey, key) > 0)
618 break;
620 /* Mark the last key which is lower or equal to the key
621 that we are looking for. The last match that is
622 found will be used to locate the child which can
623 contain the record. */
624 pointer = ((char *) currkey
625 + grub_be_to_cpu16 (currkey->keylen)
626 + 2);
627 currnode = grub_be_to_cpu32 (grub_get_unaligned32 (pointer));
628 match = 1;
632 /* No match is found, no record with this key exists in the
633 tree. */
634 if (! match)
636 *matchnode = 0;
637 grub_free (node);
638 return 0;
643 struct list_nodes_ctx
645 int ret;
646 grub_fshelp_node_t dir;
647 grub_fshelp_iterate_dir_hook_t hook;
648 void *hook_data;
651 static int
652 list_nodes (void *record, void *hook_arg)
654 struct grub_hfsplus_catkey *catkey;
655 char *filename;
656 int i;
657 struct grub_fshelp_node *node;
658 struct grub_hfsplus_catfile *fileinfo;
659 enum grub_fshelp_filetype type = GRUB_FSHELP_UNKNOWN;
660 struct list_nodes_ctx *ctx = hook_arg;
662 catkey = (struct grub_hfsplus_catkey *) record;
664 fileinfo =
665 (struct grub_hfsplus_catfile *) ((char *) record
666 + grub_be_to_cpu16 (catkey->keylen)
667 + 2 + (grub_be_to_cpu16(catkey->keylen)
668 % 2));
670 /* Stop iterating when the last directory entry is found. */
671 if (grub_be_to_cpu32 (catkey->parent) != ctx->dir->fileid)
672 return 1;
674 /* Determine the type of the node that is found. */
675 switch (fileinfo->type)
677 case grub_cpu_to_be16_compile_time (GRUB_HFSPLUS_FILETYPE_REG):
679 int mode = (grub_be_to_cpu16 (fileinfo->mode)
680 & GRUB_HFSPLUS_FILEMODE_MASK);
682 if (mode == GRUB_HFSPLUS_FILEMODE_REG)
683 type = GRUB_FSHELP_REG;
684 else if (mode == GRUB_HFSPLUS_FILEMODE_SYMLINK)
685 type = GRUB_FSHELP_SYMLINK;
686 else
687 type = GRUB_FSHELP_UNKNOWN;
688 break;
690 case grub_cpu_to_be16_compile_time (GRUB_HFSPLUS_FILETYPE_DIR):
691 type = GRUB_FSHELP_DIR;
692 break;
693 case grub_cpu_to_be16_compile_time (GRUB_HFSPLUS_FILETYPE_DIR_THREAD):
694 if (ctx->dir->fileid == 2)
695 return 0;
696 node = grub_malloc (sizeof (*node));
697 if (!node)
698 return 1;
699 node->data = ctx->dir->data;
700 node->mtime = 0;
701 node->size = 0;
702 node->fileid = grub_be_to_cpu32 (fileinfo->parentid);
704 ctx->ret = ctx->hook ("..", GRUB_FSHELP_DIR, node, ctx->hook_data);
705 return ctx->ret;
708 if (type == GRUB_FSHELP_UNKNOWN)
709 return 0;
711 filename = grub_malloc (grub_be_to_cpu16 (catkey->namelen)
712 * GRUB_MAX_UTF8_PER_UTF16 + 1);
713 if (! filename)
714 return 0;
716 /* Make sure the byte order of the UTF16 string is correct. */
717 for (i = 0; i < grub_be_to_cpu16 (catkey->namelen); i++)
719 catkey->name[i] = grub_be_to_cpu16 (catkey->name[i]);
721 if (catkey->name[i] == '/')
722 catkey->name[i] = ':';
724 /* If the name is obviously invalid, skip this node. */
725 if (catkey->name[i] == 0)
727 grub_free (filename);
728 return 0;
732 *grub_utf16_to_utf8 ((grub_uint8_t *) filename, catkey->name,
733 grub_be_to_cpu16 (catkey->namelen)) = '\0';
735 /* Restore the byte order to what it was previously. */
736 for (i = 0; i < grub_be_to_cpu16 (catkey->namelen); i++)
738 if (catkey->name[i] == ':')
739 catkey->name[i] = '/';
740 catkey->name[i] = grub_be_to_cpu16 (catkey->name[i]);
743 /* hfs+ is case insensitive. */
744 if (! ctx->dir->data->case_sensitive)
745 type |= GRUB_FSHELP_CASE_INSENSITIVE;
747 /* A valid node is found; setup the node and call the
748 callback function. */
749 node = grub_malloc (sizeof (*node));
750 if (!node)
752 grub_free (filename);
753 return 1;
755 node->data = ctx->dir->data;
756 node->compressed = 0;
757 node->cbuf = 0;
758 node->compress_index = 0;
760 grub_memcpy (node->extents, fileinfo->data.extents,
761 sizeof (node->extents));
762 grub_memcpy (node->resource_extents, fileinfo->resource.extents,
763 sizeof (node->resource_extents));
764 node->mtime = grub_be_to_cpu32 (fileinfo->mtime) - 2082844800;
765 node->size = grub_be_to_cpu64 (fileinfo->data.size);
766 node->resource_size = grub_be_to_cpu64 (fileinfo->resource.size);
767 node->fileid = grub_be_to_cpu32 (fileinfo->fileid);
769 ctx->ret = ctx->hook (filename, type, node, ctx->hook_data);
771 grub_free (filename);
773 return ctx->ret;
776 static int
777 grub_hfsplus_iterate_dir (grub_fshelp_node_t dir,
778 grub_fshelp_iterate_dir_hook_t hook, void *hook_data)
780 struct list_nodes_ctx ctx =
782 .ret = 0,
783 .dir = dir,
784 .hook = hook,
785 .hook_data = hook_data
788 struct grub_hfsplus_key_internal intern;
789 struct grub_hfsplus_btnode *node;
790 grub_disk_addr_t ptr;
793 struct grub_fshelp_node *fsnode;
794 fsnode = grub_malloc (sizeof (*fsnode));
795 if (!fsnode)
796 return 1;
797 *fsnode = *dir;
798 if (hook (".", GRUB_FSHELP_DIR, fsnode, hook_data))
799 return 1;
802 /* Create a key that points to the first entry in the directory. */
803 intern.catkey.parent = dir->fileid;
804 intern.catkey.name = 0;
805 intern.catkey.namelen = 0;
807 /* First lookup the first entry. */
808 if (grub_hfsplus_btree_search (&dir->data->catalog_tree, &intern,
809 grub_hfsplus_cmp_catkey, &node, &ptr)
810 || !node)
811 return 0;
813 /* Iterate over all entries in this directory. */
814 grub_hfsplus_btree_iterate_node (&dir->data->catalog_tree, node, ptr,
815 list_nodes, &ctx);
817 grub_free (node);
819 return ctx.ret;
822 /* Open a file named NAME and initialize FILE. */
823 static grub_err_t
824 grub_hfsplus_open (struct grub_file *file, const char *name)
826 struct grub_hfsplus_data *data;
827 struct grub_fshelp_node *fdiro = 0;
829 grub_dl_ref (my_mod);
831 data = grub_hfsplus_mount (file->device->disk);
832 if (!data)
833 goto fail;
835 grub_fshelp_find_file (name, &data->dirroot, &fdiro,
836 grub_hfsplus_iterate_dir,
837 grub_hfsplus_read_symlink, GRUB_FSHELP_REG);
838 if (grub_errno)
839 goto fail;
841 if (grub_hfsplus_open_compressed)
843 grub_err_t err;
844 err = grub_hfsplus_open_compressed (fdiro);
845 if (err)
846 goto fail;
849 file->size = fdiro->size;
850 data->opened_file = *fdiro;
851 grub_free (fdiro);
853 file->data = data;
854 file->offset = 0;
856 return 0;
858 fail:
859 if (data && fdiro != &data->dirroot)
860 grub_free (fdiro);
861 grub_free (data);
863 grub_dl_unref (my_mod);
865 return grub_errno;
869 static grub_err_t
870 grub_hfsplus_close (grub_file_t file)
872 struct grub_hfsplus_data *data =
873 (struct grub_hfsplus_data *) file->data;
875 grub_free (data->opened_file.cbuf);
876 grub_free (data->opened_file.compress_index);
878 grub_free (data);
880 grub_dl_unref (my_mod);
882 return GRUB_ERR_NONE;
885 /* Read LEN bytes data from FILE into BUF. */
886 static grub_ssize_t
887 grub_hfsplus_read (grub_file_t file, char *buf, grub_size_t len)
889 struct grub_hfsplus_data *data =
890 (struct grub_hfsplus_data *) file->data;
892 data->opened_file.file = file;
894 if (grub_hfsplus_read_compressed && data->opened_file.compressed)
895 return grub_hfsplus_read_compressed (&data->opened_file,
896 file->offset, len, buf);
898 return grub_hfsplus_read_file (&data->opened_file,
899 file->read_hook, file->read_hook_data,
900 file->offset, len, buf);
903 /* Context for grub_hfsplus_dir. */
904 struct grub_hfsplus_dir_ctx
906 grub_fs_dir_hook_t hook;
907 void *hook_data;
910 /* Helper for grub_hfsplus_dir. */
911 static int
912 grub_hfsplus_dir_iter (const char *filename,
913 enum grub_fshelp_filetype filetype,
914 grub_fshelp_node_t node, void *data)
916 struct grub_hfsplus_dir_ctx *ctx = data;
917 struct grub_dirhook_info info;
919 grub_memset (&info, 0, sizeof (info));
920 info.dir = ((filetype & GRUB_FSHELP_TYPE_MASK) == GRUB_FSHELP_DIR);
921 info.mtimeset = 1;
922 info.mtime = node->mtime;
923 info.inodeset = 1;
924 info.inode = node->fileid;
925 info.case_insensitive = !! (filetype & GRUB_FSHELP_CASE_INSENSITIVE);
926 grub_free (node);
927 return ctx->hook (filename, &info, ctx->hook_data);
930 static grub_err_t
931 grub_hfsplus_dir (grub_device_t device, const char *path,
932 grub_fs_dir_hook_t hook, void *hook_data)
934 struct grub_hfsplus_dir_ctx ctx = { hook, hook_data };
935 struct grub_hfsplus_data *data = 0;
936 struct grub_fshelp_node *fdiro = 0;
938 grub_dl_ref (my_mod);
940 data = grub_hfsplus_mount (device->disk);
941 if (!data)
942 goto fail;
944 /* Find the directory that should be opened. */
945 grub_fshelp_find_file (path, &data->dirroot, &fdiro,
946 grub_hfsplus_iterate_dir,
947 grub_hfsplus_read_symlink, GRUB_FSHELP_DIR);
948 if (grub_errno)
949 goto fail;
951 /* Iterate over all entries in this directory. */
952 grub_hfsplus_iterate_dir (fdiro, grub_hfsplus_dir_iter, &ctx);
954 fail:
955 if (data && fdiro != &data->dirroot)
956 grub_free (fdiro);
957 grub_free (data);
959 grub_dl_unref (my_mod);
961 return grub_errno;
965 static grub_err_t
966 grub_hfsplus_label (grub_device_t device, char **label)
968 struct grub_hfsplus_data *data;
969 grub_disk_t disk = device->disk;
970 struct grub_hfsplus_catkey *catkey;
971 int i, label_len;
972 struct grub_hfsplus_key_internal intern;
973 struct grub_hfsplus_btnode *node;
974 grub_disk_addr_t ptr;
976 *label = 0;
978 data = grub_hfsplus_mount (disk);
979 if (!data)
980 return grub_errno;
982 /* Create a key that points to the label. */
983 intern.catkey.parent = 1;
984 intern.catkey.name = 0;
985 intern.catkey.namelen = 0;
987 /* First lookup the first entry. */
988 if (grub_hfsplus_btree_search (&data->catalog_tree, &intern,
989 grub_hfsplus_cmp_catkey_id, &node, &ptr)
990 || !node)
992 grub_free (data);
993 return 0;
996 catkey = (struct grub_hfsplus_catkey *)
997 grub_hfsplus_btree_recptr (&data->catalog_tree, node, ptr);
999 label_len = grub_be_to_cpu16 (catkey->namelen);
1000 for (i = 0; i < label_len; i++)
1002 catkey->name[i] = grub_be_to_cpu16 (catkey->name[i]);
1004 /* If the name is obviously invalid, skip this node. */
1005 if (catkey->name[i] == 0)
1006 return 0;
1009 *label = grub_malloc (label_len * GRUB_MAX_UTF8_PER_UTF16 + 1);
1010 if (! *label)
1011 return grub_errno;
1013 *grub_utf16_to_utf8 ((grub_uint8_t *) (*label), catkey->name,
1014 label_len) = '\0';
1016 grub_free (node);
1017 grub_free (data);
1019 return GRUB_ERR_NONE;
1022 /* Get mtime. */
1023 static grub_err_t
1024 grub_hfsplus_mtime (grub_device_t device, grub_int32_t *tm)
1026 struct grub_hfsplus_data *data;
1027 grub_disk_t disk = device->disk;
1029 grub_dl_ref (my_mod);
1031 data = grub_hfsplus_mount (disk);
1032 if (!data)
1033 *tm = 0;
1034 else
1035 *tm = grub_be_to_cpu32 (data->volheader.utime) - 2082844800;
1037 grub_dl_unref (my_mod);
1039 grub_free (data);
1041 return grub_errno;
1045 static grub_err_t
1046 grub_hfsplus_uuid (grub_device_t device, char **uuid)
1048 struct grub_hfsplus_data *data;
1049 grub_disk_t disk = device->disk;
1051 grub_dl_ref (my_mod);
1053 data = grub_hfsplus_mount (disk);
1054 if (data)
1056 *uuid = grub_xasprintf ("%016llx",
1057 (unsigned long long)
1058 grub_be_to_cpu64 (data->volheader.num_serial));
1060 else
1061 *uuid = NULL;
1063 grub_dl_unref (my_mod);
1065 grub_free (data);
1067 return grub_errno;
1072 static struct grub_fs grub_hfsplus_fs =
1074 .name = "hfsplus",
1075 .dir = grub_hfsplus_dir,
1076 .open = grub_hfsplus_open,
1077 .read = grub_hfsplus_read,
1078 .close = grub_hfsplus_close,
1079 .label = grub_hfsplus_label,
1080 .mtime = grub_hfsplus_mtime,
1081 .uuid = grub_hfsplus_uuid,
1082 #ifdef GRUB_UTIL
1083 .reserved_first_sector = 1,
1084 .blocklist_install = 1,
1085 #endif
1086 .next = 0
1089 GRUB_MOD_INIT(hfsplus)
1091 grub_fs_register (&grub_hfsplus_fs);
1092 my_mod = mod;
1095 GRUB_MOD_FINI(hfsplus)
1097 grub_fs_unregister (&grub_hfsplus_fs);