1 /* hfsplus.c - HFS+ 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/>.
20 /* HFS+ is documented at http://developer.apple.com/technotes/tn/tn1150.html */
22 #define grub_fshelp_node grub_hfsplus_file
24 #include <grub/file.h>
26 #include <grub/misc.h>
27 #include <grub/disk.h>
29 #include <grub/types.h>
30 #include <grub/fshelp.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
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
;
65 struct grub_hfsplus_catfile
69 grub_uint32_t parentid
; /* Thread only. */
71 grub_uint8_t unused1
[4];
73 grub_uint8_t unused2
[22];
75 grub_uint8_t unused3
[44];
76 struct grub_hfsplus_forkdata data
;
77 struct grub_hfsplus_forkdata resource
;
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. */
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
,
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
)
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];
153 struct grub_hfsplus_extkey
*key
;
154 struct grub_hfsplus_key_internal extoverflow
;
155 grub_disk_addr_t blk
;
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. */
166 if (blk
!= 0xffffffffffffffffULL
)
169 /* For the extent overflow file, extra extents can't be found in
170 the extent overflow file. If this happens, you found a
172 if (node
->fileid
== GRUB_HFSPLUS_FILEID_OVERFLOW
)
174 grub_error (GRUB_ERR_READ_ERROR
,
175 "extra extents found in an extend overflow file");
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
,
186 grub_hfsplus_cmp_extkey
, &nnode
, &ptr
)
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
);
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. */
206 /* Too bad, you lose. */
211 /* Read LEN bytes from the file described by DATA starting with byte
212 POS. Return the amount of read bytes in READ. */
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
,
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
;
234 struct grub_hfs_sblock hfs
;
235 struct grub_hfsplus_volheader hfsplus
;
238 data
= grub_malloc (sizeof (*data
));
244 /* Read the bootblock. */
245 grub_disk_read (disk
, GRUB_HFSPLUS_SBLOCK
, 0, sizeof (volheader
),
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");
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
270 * (ablk_size
>> GRUB_DISK_SECTOR_BITS
));
272 grub_disk_read (disk
, data
->embedded_offset
+ GRUB_HFSPLUS_SBLOCK
, 0,
273 sizeof (volheader
), &volheader
);
278 /* Make sure this is an HFS+ filesystem. XXX: Do we really support
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");
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)
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)
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)
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)
358 data
->attr_tree
.root
= 0;
359 data
->attr_tree
.nodesize
= 0;
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
;
374 if (grub_errno
== GRUB_ERR_OUT_OF_RANGE
)
375 grub_error (GRUB_ERR_BAD_FS
, "not a HFS+ filesystem");
381 /* Compare the on disk catalog key KEYA with the catalog key we are
382 looking for (KEYB). */
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
;
392 /* Safe unsigned comparison */
393 grub_uint32_t aparent
= grub_be_to_cpu32 (catkey_a
->parent
);
394 if (aparent
> catkey_b
->parent
)
396 if (aparent
< catkey_b
->parent
)
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]));
407 diff
= grub_be_to_cpu16 (catkey_a
->namelen
) - catkey_b
->namelen
;
412 /* Compare the on disk catalog key KEYA with the catalog key we are
413 looking for (KEYB). */
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
)
425 if (aparent
< catkey_b
->parent
)
431 /* Compare the on disk extent overflow key KEYA with the extent
432 overflow key we are looking for (KEYB). */
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
;
441 /* Safe unsigned comparison */
442 akey
= grub_be_to_cpu32 (extkey_a
->fileid
);
443 if (akey
> extkey_b
->fileid
)
445 if (akey
< extkey_b
->fileid
)
448 if (extkey_a
->type
> extkey_b
->type
)
450 if (extkey_a
->type
< extkey_b
->type
)
453 if (extkey_a
->type
> extkey_b
->type
)
456 if (extkey_a
->type
< extkey_b
->type
)
459 akey
= grub_be_to_cpu32 (extkey_a
->start
);
460 if (akey
> extkey_b
->start
)
462 if (akey
< extkey_b
->start
)
468 grub_hfsplus_read_symlink (grub_fshelp_node_t node
)
471 grub_ssize_t numread
;
473 symlink
= grub_malloc (node
->size
+ 1);
477 numread
= grub_hfsplus_read_file (node
, 0, 0, 0, node
->size
, symlink
);
478 if (numread
!= (grub_ssize_t
) node
->size
)
483 symlink
[node
->size
] = '\0';
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
),
495 grub_disk_addr_t rec
;
496 grub_uint64_t saved_node
= -1;
497 grub_uint64_t node_count
= 0;
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
))
510 if (! first_node
->next
)
513 if (node_count
&& first_node
->next
== saved_node
)
515 grub_error (GRUB_ERR_BAD_FS
, "HFS+ btree loop");
518 if (!(node_count
& (node_count
- 1)))
519 saved_node
= first_node
->next
;
522 if (grub_hfsplus_read_file (&btree
->file
, 0, 0,
524 grub_be_to_cpu32 (first_node
->next
))
526 btree
->nodesize
, cnode
) <= 0)
529 /* Don't skip any record in the next iteration. */
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. */
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
;
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
)
561 node
= grub_malloc (btree
->nodesize
);
565 currnode
= btree
->root
;
566 save_node
= currnode
- 1;
571 if (save_node
== currnode
)
574 return grub_error (GRUB_ERR_BAD_FS
, "HFS+ btree loop");
576 if (!(node_count
& (node_count
- 1)))
577 save_node
= currnode
;
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)
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
600 if (nodedesc
->type
== GRUB_HFSPLUS_BTNODE_TYPE_LEAF
601 && compare_keys (currkey
, key
) == 0)
603 /* An exact match was found! */
605 *matchnode
= nodedesc
;
610 else if (nodedesc
->type
== GRUB_HFSPLUS_BTNODE_TYPE_INDEX
)
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)
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
)
627 currnode
= grub_be_to_cpu32 (grub_get_unaligned32 (pointer
));
632 /* No match is found, no record with this key exists in the
643 struct list_nodes_ctx
646 grub_fshelp_node_t dir
;
647 grub_fshelp_iterate_dir_hook_t hook
;
652 list_nodes (void *record
, void *hook_arg
)
654 struct grub_hfsplus_catkey
*catkey
;
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
;
665 (struct grub_hfsplus_catfile
*) ((char *) record
666 + grub_be_to_cpu16 (catkey
->keylen
)
667 + 2 + (grub_be_to_cpu16(catkey
->keylen
)
670 /* Stop iterating when the last directory entry is found. */
671 if (grub_be_to_cpu32 (catkey
->parent
) != ctx
->dir
->fileid
)
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
;
687 type
= GRUB_FSHELP_UNKNOWN
;
690 case grub_cpu_to_be16_compile_time (GRUB_HFSPLUS_FILETYPE_DIR
):
691 type
= GRUB_FSHELP_DIR
;
693 case grub_cpu_to_be16_compile_time (GRUB_HFSPLUS_FILETYPE_DIR_THREAD
):
694 if (ctx
->dir
->fileid
== 2)
696 node
= grub_malloc (sizeof (*node
));
699 node
->data
= ctx
->dir
->data
;
702 node
->fileid
= grub_be_to_cpu32 (fileinfo
->parentid
);
704 ctx
->ret
= ctx
->hook ("..", GRUB_FSHELP_DIR
, node
, ctx
->hook_data
);
708 if (type
== GRUB_FSHELP_UNKNOWN
)
711 filename
= grub_malloc (grub_be_to_cpu16 (catkey
->namelen
)
712 * GRUB_MAX_UTF8_PER_UTF16
+ 1);
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
);
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
));
752 grub_free (filename
);
755 node
->data
= ctx
->dir
->data
;
756 node
->compressed
= 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
);
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
=
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
));
798 if (hook (".", GRUB_FSHELP_DIR
, fsnode
, hook_data
))
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
)
813 /* Iterate over all entries in this directory. */
814 grub_hfsplus_btree_iterate_node (&dir
->data
->catalog_tree
, node
, ptr
,
822 /* Open a file named NAME and initialize FILE. */
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
);
835 grub_fshelp_find_file (name
, &data
->dirroot
, &fdiro
,
836 grub_hfsplus_iterate_dir
,
837 grub_hfsplus_read_symlink
, GRUB_FSHELP_REG
);
841 if (grub_hfsplus_open_compressed
)
844 err
= grub_hfsplus_open_compressed (fdiro
);
849 file
->size
= fdiro
->size
;
850 data
->opened_file
= *fdiro
;
859 if (data
&& fdiro
!= &data
->dirroot
)
863 grub_dl_unref (my_mod
);
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
);
880 grub_dl_unref (my_mod
);
882 return GRUB_ERR_NONE
;
885 /* Read LEN bytes data from FILE into BUF. */
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
;
910 /* Helper for grub_hfsplus_dir. */
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
);
922 info
.mtime
= node
->mtime
;
924 info
.inode
= node
->fileid
;
925 info
.case_insensitive
= !! (filetype
& GRUB_FSHELP_CASE_INSENSITIVE
);
927 return ctx
->hook (filename
, &info
, ctx
->hook_data
);
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
);
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
);
951 /* Iterate over all entries in this directory. */
952 grub_hfsplus_iterate_dir (fdiro
, grub_hfsplus_dir_iter
, &ctx
);
955 if (data
&& fdiro
!= &data
->dirroot
)
959 grub_dl_unref (my_mod
);
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
;
972 struct grub_hfsplus_key_internal intern
;
973 struct grub_hfsplus_btnode
*node
;
974 grub_disk_addr_t ptr
;
978 data
= grub_hfsplus_mount (disk
);
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
)
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)
1009 *label
= grub_malloc (label_len
* GRUB_MAX_UTF8_PER_UTF16
+ 1);
1013 *grub_utf16_to_utf8 ((grub_uint8_t
*) (*label
), catkey
->name
,
1019 return GRUB_ERR_NONE
;
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
);
1035 *tm
= grub_be_to_cpu32 (data
->volheader
.utime
) - 2082844800;
1037 grub_dl_unref (my_mod
);
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
);
1056 *uuid
= grub_xasprintf ("%016llx",
1057 (unsigned long long)
1058 grub_be_to_cpu64 (data
->volheader
.num_serial
));
1063 grub_dl_unref (my_mod
);
1072 static struct grub_fs grub_hfsplus_fs
=
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
,
1083 .reserved_first_sector
= 1,
1084 .blocklist_install
= 1,
1089 GRUB_MOD_INIT(hfsplus
)
1091 grub_fs_register (&grub_hfsplus_fs
);
1095 GRUB_MOD_FINI(hfsplus
)
1097 grub_fs_unregister (&grub_hfsplus_fs
);