3 * GRUB -- GRand Unified Bootloader
4 * Copyright (C) 2004,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 /* HFS is documented at
21 http://developer.apple.com/documentation/mac/Files/Files-2.html */
24 #include <grub/file.h>
26 #include <grub/misc.h>
27 #include <grub/disk.h>
29 #include <grub/types.h>
32 #define GRUB_HFS_SBLOCK 2
33 #define GRUB_HFS_EMBED_HFSPLUS_SIG 0x482B
35 #define GRUB_HFS_BLKS (data->blksz >> 9)
37 #define GRUB_HFS_NODE_LEAF 0xFF
39 /* The two supported filesystems a record can have. */
42 GRUB_HFS_FILETYPE_DIR
= 1,
43 GRUB_HFS_FILETYPE_FILE
= 2
46 /* Catalog node ID (CNID). */
47 enum grub_hfs_cnid_type
49 GRUB_HFS_CNID_ROOT_PARENT
= 1,
50 GRUB_HFS_CNID_ROOT
= 2,
51 GRUB_HFS_CNID_EXT
= 3,
52 GRUB_HFS_CNID_CAT
= 4,
56 /* A node descriptor. This is the header of every node. */
65 } __attribute__ ((packed
));
67 /* The head of the B*-Tree. */
68 struct grub_hfs_treeheader
70 grub_uint16_t tree_depth
;
71 /* The number of the first node. */
72 grub_uint32_t root_node
;
74 grub_uint32_t first_leaf
;
75 grub_uint32_t last_leaf
;
76 grub_uint16_t node_size
;
77 grub_uint16_t key_size
;
79 grub_uint32_t free_nodes
;
80 grub_uint8_t unused
[76];
81 } __attribute__ ((packed
));
83 /* The state of a mounted HFS filesystem. */
86 struct grub_hfs_sblock sblock
;
88 grub_hfs_datarecord_t extents
;
100 /* The key as used on disk in a catalog tree. This is used to lookup
101 file/directory nodes by parent directory ID and filename. */
102 struct grub_hfs_catalog_key
105 grub_uint32_t parent_dir
;
107 /* Filename length. */
111 grub_uint8_t str
[31];
112 } __attribute__ ((packed
));
114 /* The key as used on disk in a extent overflow tree. Using this key
115 the extents can be looked up using a fileid and logical start block
117 struct grub_hfs_extent_key
119 /* The kind of fork. This is used to store meta information like
120 icons, attributes, etc. We will only use the datafork, which is
122 grub_uint8_t forktype
;
123 grub_uint32_t fileid
;
124 grub_uint16_t first_block
;
125 } __attribute__ ((packed
));
127 /* A directory record. This is used to find out the directory ID. */
128 struct grub_hfs_dirrec
130 /* For a directory, type == 1. */
132 grub_uint8_t unused
[5];
134 } __attribute__ ((packed
));
136 /* Information about a file. */
137 struct grub_hfs_filerec
139 /* For a file, type == 2. */
141 grub_uint8_t unused
[19];
142 grub_uint32_t fileid
;
143 grub_uint8_t unused2
[2];
145 grub_uint8_t unused3
[44];
147 /* The first 3 extents of the file. The other extents can be found
148 in the extent overflow file. */
149 grub_hfs_datarecord_t extents
;
150 } __attribute__ ((packed
));
152 /* A record descriptor, both key and data, used to pass to call back
154 struct grub_hfs_record
162 static grub_dl_t my_mod
;
164 static int grub_hfs_find_node (struct grub_hfs_data
*, char *,
165 grub_uint32_t
, int, char *, int);
167 /* Find block BLOCK of the file FILE in the mounted UFS filesystem
168 DATA. The first 3 extents are described by DAT. If cache is set,
169 using caching to improve non-random reads. */
171 grub_hfs_block (struct grub_hfs_data
*data
, grub_hfs_datarecord_t dat
,
172 int file
, int block
, int cache
)
174 grub_hfs_datarecord_t dr
;
176 struct grub_hfs_extent_key key
;
179 static int cache_file
= 0;
180 static int cache_pos
= 0;
181 static grub_hfs_datarecord_t cache_dr
;
183 grub_memcpy (dr
, dat
, sizeof (dr
));
186 key
.fileid
= grub_cpu_to_be32 (file
);
188 if (cache
&& cache_file
== file
&& block
> cache_pos
)
191 key
.first_block
= grub_cpu_to_be16 (pos
);
192 grub_memcpy (dr
, cache_dr
, sizeof (cache_dr
));
199 /* Try all 3 extents. */
200 for (i
= 0; i
< 3; i
++)
202 /* Check if the block is stored in this extent. */
203 if (grub_be_to_cpu16 (dr
[i
].count
) + pos
> block
)
205 int first
= grub_be_to_cpu16 (dr
[i
].first_block
);
207 /* If the cache is enabled, store the current position
213 grub_memcpy (cache_dr
, dr
, sizeof (cache_dr
));
216 return (grub_be_to_cpu16 (data
->sblock
.first_block
)
217 + (first
+ block
- pos
) * GRUB_HFS_BLKS
);
220 /* Try the next extent. */
221 pos
+= grub_be_to_cpu16 (dr
[i
].count
);
224 /* Lookup the block in the extent overflow file. */
225 key
.first_block
= grub_cpu_to_be16 (pos
);
227 grub_hfs_find_node (data
, (char *) &key
, data
->ext_root
,
228 1, (char *) &dr
, sizeof (dr
));
235 /* Read LEN bytes from the file described by DATA starting with byte
236 POS. Return the amount of read bytes in READ. */
238 grub_hfs_read_file (struct grub_hfs_data
*data
,
239 void NESTED_FUNC_ATTR (*read_hook
) (grub_disk_addr_t sector
,
240 unsigned offset
, unsigned length
),
241 int pos
, grub_size_t len
, char *buf
)
246 blockcnt
= ((len
+ pos
)
247 + data
->blksz
- 1) / data
->blksz
;
249 for (i
= pos
/ data
->blksz
; i
< blockcnt
; i
++)
252 int blockoff
= pos
% data
->blksz
;
253 int blockend
= data
->blksz
;
257 blknr
= grub_hfs_block (data
, data
->extents
, data
->fileid
, i
, 1);
262 if (i
== blockcnt
- 1)
264 blockend
= (len
+ pos
) % data
->blksz
;
266 /* The last portion is exactly EXT2_BLOCK_SIZE (data). */
268 blockend
= data
->blksz
;
272 if (i
== pos
/ data
->blksz
)
274 skipfirst
= blockoff
;
275 blockend
-= skipfirst
;
278 /* If the block number is 0 this block is not stored on disk but
279 is zero filled instead. */
282 data
->disk
->read_hook
= read_hook
;
283 grub_disk_read (data
->disk
, blknr
, skipfirst
,
285 data
->disk
->read_hook
= 0;
290 buf
+= data
->blksz
- skipfirst
;
297 /* Mount the filesystem on the disk DISK. */
298 static struct grub_hfs_data
*
299 grub_hfs_mount (grub_disk_t disk
)
301 struct grub_hfs_data
*data
;
302 struct grub_hfs_catalog_key key
;
303 struct grub_hfs_dirrec dir
;
308 struct grub_hfs_node node
;
309 struct grub_hfs_treeheader head
;
312 data
= grub_malloc (sizeof (struct grub_hfs_data
));
316 /* Read the superblock. */
317 if (grub_disk_read (disk
, GRUB_HFS_SBLOCK
, 0,
318 sizeof (struct grub_hfs_sblock
), &data
->sblock
))
321 /* Check if this is a HFS filesystem. */
322 if (grub_be_to_cpu16 (data
->sblock
.magic
) != GRUB_HFS_MAGIC
)
324 grub_error (GRUB_ERR_BAD_FS
, "not an HFS filesystem");
328 /* Check if this is an embedded HFS+ filesystem. */
329 if (grub_be_to_cpu16 (data
->sblock
.embed_sig
) == GRUB_HFS_EMBED_HFSPLUS_SIG
)
331 grub_error (GRUB_ERR_BAD_FS
, "embedded HFS+ filesystem");
335 data
->blksz
= grub_be_to_cpu32 (data
->sblock
.blksz
);
338 /* Lookup the root node of the extent overflow tree. */
339 first_block
= ((grub_be_to_cpu16 (data
->sblock
.extent_recs
[0].first_block
)
341 + grub_be_to_cpu16 (data
->sblock
.first_block
));
343 if (grub_disk_read (data
->disk
, first_block
, 0,
344 sizeof (treehead
), &treehead
))
346 data
->ext_root
= grub_be_to_cpu32 (treehead
.head
.root_node
);
347 data
->ext_size
= grub_be_to_cpu16 (treehead
.head
.node_size
);
349 /* Lookup the root node of the catalog tree. */
350 first_block
= ((grub_be_to_cpu16 (data
->sblock
.catalog_recs
[0].first_block
)
352 + grub_be_to_cpu16 (data
->sblock
.first_block
));
353 if (grub_disk_read (data
->disk
, first_block
, 0,
354 sizeof (treehead
), &treehead
))
356 data
->cat_root
= grub_be_to_cpu32 (treehead
.head
.root_node
);
357 data
->cat_size
= grub_be_to_cpu16 (treehead
.head
.node_size
);
359 /* Lookup the root directory node in the catalog tree using the
361 key
.parent_dir
= grub_cpu_to_be32 (1);
362 key
.strlen
= data
->sblock
.volname
[0];
363 grub_strcpy ((char *) key
.str
, (char *) (data
->sblock
.volname
+ 1));
365 if (grub_hfs_find_node (data
, (char *) &key
, data
->cat_root
,
366 0, (char *) &dir
, sizeof (dir
)) == 0)
368 grub_error (GRUB_ERR_BAD_FS
, "can not find the hfs root directory");
375 data
->rootdir
= grub_be_to_cpu32 (dir
.dirid
);
381 if (grub_errno
== GRUB_ERR_OUT_OF_RANGE
)
382 grub_error (GRUB_ERR_BAD_FS
, "not a hfs filesystem");
387 /* Compare the K1 and K2 catalog file keys using HFS character ordering. */
389 grub_hfs_cmp_catkeys (struct grub_hfs_catalog_key
*k1
,
390 struct grub_hfs_catalog_key
*k2
)
392 /* Taken from hfsutils 3.2.6 and converted to a readable form */
393 static const unsigned char hfs_charorder
[256] = {
426 [' '] = 32, [0xCA] = 32,
465 ['A'] = 71, ['a'] = 71,
466 [0x88] = 72, [0xCB] = 72,
467 [0x80] = 73, [0x8A] = 73,
468 [0x8B] = 74, [0xCC] = 74,
469 [0x81] = 75, [0x8C] = 75,
470 [0xAE] = 76, [0xBE] = 76,
475 ['B'] = 81, ['b'] = 81,
476 ['C'] = 82, ['c'] = 82,
477 [0x82] = 83, [0x8D] = 83,
478 ['D'] = 84, ['d'] = 84,
479 ['E'] = 85, ['e'] = 85,
480 [0x83] = 86, [0x8E] = 86,
484 ['F'] = 90, ['f'] = 90,
485 ['G'] = 91, ['g'] = 91,
486 ['H'] = 92, ['h'] = 92,
487 ['I'] = 93, ['i'] = 93,
492 ['J'] = 98, ['j'] = 98,
493 ['K'] = 99, ['k'] = 99,
494 ['L'] = 100, ['l'] = 100,
495 ['M'] = 101, ['m'] = 101,
496 ['N'] = 102, ['n'] = 102,
497 [0x84] = 103, [0x96] = 103,
498 ['O'] = 104, ['o'] = 104,
499 [0x85] = 105, [0x9A] = 105,
500 [0x9B] = 106, [0xCD] = 106,
501 [0xAF] = 107, [0xBF] = 107,
502 [0xCE] = 108, [0xCF] = 108,
507 ['P'] = 113, ['p'] = 113,
508 ['Q'] = 114, ['q'] = 114,
509 ['R'] = 115, ['r'] = 115,
510 ['S'] = 116, ['s'] = 116,
512 ['T'] = 118, ['t'] = 118,
513 ['U'] = 119, ['u'] = 119,
514 [0x86] = 120, [0x9F] = 120,
518 ['V'] = 124, ['v'] = 124,
519 ['W'] = 125, ['w'] = 125,
520 ['X'] = 126, ['x'] = 126,
521 ['Y'] = 127, ['y'] = 127,
523 ['Z'] = 129, ['z'] = 129,
613 int minlen
= (k1
->strlen
< k2
->strlen
) ? k1
->strlen
: k2
->strlen
;
615 cmp
= (grub_be_to_cpu32 (k1
->parent_dir
) - grub_be_to_cpu32 (k2
->parent_dir
));
619 for (i
= 0; i
< minlen
; i
++)
621 cmp
= (hfs_charorder
[k1
->str
[i
]] - hfs_charorder
[k2
->str
[i
]]);
626 /* Shorter strings precede long ones. */
627 return (k1
->strlen
- k2
->strlen
);
631 /* Compare the K1 and K2 extent overflow file keys. */
633 grub_hfs_cmp_extkeys (struct grub_hfs_extent_key
*k1
,
634 struct grub_hfs_extent_key
*k2
)
636 int cmp
= k1
->forktype
- k2
->forktype
;
638 cmp
= grub_be_to_cpu32 (k1
->fileid
) - grub_be_to_cpu32 (k2
->fileid
);
640 cmp
= (grub_be_to_cpu16 (k1
->first_block
)
641 - grub_be_to_cpu16 (k2
->first_block
));
646 /* Iterate the records in the node with index IDX in the mounted HFS
647 filesystem DATA. This node holds data of the type TYPE (0 =
648 catalog node, 1 = extent overflow node). If this is set, continue
649 iterating to the next node. For every records, call NODE_HOOK. */
651 grub_hfs_iterate_records (struct grub_hfs_data
*data
, int type
, int idx
,
652 int this, int (*node_hook
) (struct grub_hfs_node
*hnd
,
653 struct grub_hfs_record
*))
655 int nodesize
= type
== 0 ? data
->cat_size
: data
->ext_size
;
659 struct grub_hfs_node node
;
660 char rawnode
[nodesize
];
661 grub_uint16_t offsets
[nodesize
/ 2];
667 struct grub_hfs_extent
*dat
;
670 dat
= (struct grub_hfs_extent
*) (type
== 0
671 ? (&data
->sblock
.catalog_recs
)
672 : (&data
->sblock
.extent_recs
));
674 /* Read the node into memory. */
675 blk
= grub_hfs_block (data
, dat
,
676 (type
== 0) ? GRUB_HFS_CNID_CAT
: GRUB_HFS_CNID_EXT
,
677 idx
/ (data
->blksz
/ nodesize
), 0);
678 blk
+= (idx
% (data
->blksz
/ nodesize
));
682 if (grub_disk_read (data
->disk
, blk
, 0,
683 sizeof (node
), &node
))
686 /* Iterate over all records in this node. */
687 for (i
= 0; i
< grub_be_to_cpu16 (node
.node
.reccnt
); i
++)
689 int pos
= (nodesize
>> 1) - 1 - i
;
694 } __attribute__ ((packed
)) *pnt
;
695 pnt
= (struct pointer
*) (grub_be_to_cpu16 (node
.offsets
[pos
])
698 struct grub_hfs_record rec
=
702 &pnt
->key
+ pnt
->keylen
+(pnt
->keylen
+ 1) % 2,
703 nodesize
- grub_be_to_cpu16 (node
.offsets
[pos
])
707 if (node_hook (&node
.node
, &rec
))
711 idx
= grub_be_to_cpu32 (node
.node
.next
);
712 } while (idx
&& this);
718 /* Lookup a record in the mounted filesystem DATA using the key KEY.
719 The index of the node on top of the tree is IDX. The tree is of
720 the type TYPE (0 = catalog node, 1 = extent overflow node). Return
721 the data in DATAR with a maximum length of DATALEN. */
723 grub_hfs_find_node (struct grub_hfs_data
*data
, char *key
,
724 grub_uint32_t idx
, int type
, char *datar
, int datalen
)
730 auto int node_found (struct grub_hfs_node
*, struct grub_hfs_record
*);
732 int node_found (struct grub_hfs_node
*hnd
, struct grub_hfs_record
*rec
)
737 cmp
= grub_hfs_cmp_catkeys (rec
->key
, (void *) key
);
739 cmp
= grub_hfs_cmp_extkeys (rec
->key
, (void *) key
);
741 /* If the key is smaller or equal to the current node, mark the
742 entry. In case of a non-leaf mode it will be used to lookup
743 the rest of the tree. */
746 grub_uint32_t
*node
= (grub_uint32_t
*) rec
->data
;
747 found
= grub_be_to_cpu32 (*node
);
749 else /* The key can not be found in the tree. */
752 /* Check if this node is a leaf node. */
753 if (hnd
->type
== GRUB_HFS_NODE_LEAF
)
762 grub_memcpy (datar
, rec
->data
,
763 rec
->datalen
< datalen
? rec
->datalen
: datalen
);
775 if (grub_hfs_iterate_records (data
, type
, idx
, 0, node_found
))
788 /* Iterate over the directory with the id DIR. The tree is searched
789 starting with the node ROOT_IDX. For every entry in this directory
792 grub_hfs_iterate_dir (struct grub_hfs_data
*data
, grub_uint32_t root_idx
,
793 unsigned int dir
, int (*hook
) (struct grub_hfs_record
*))
799 /* The lowest key possible with DIR as root directory. */
800 struct grub_hfs_catalog_key key
= {0, grub_cpu_to_be32 (dir
), 0, ""};
802 auto int node_found (struct grub_hfs_node
*, struct grub_hfs_record
*);
803 auto int it_dir (struct grub_hfs_node
* __attribute ((unused
)),
804 struct grub_hfs_record
*);
807 int node_found (struct grub_hfs_node
*hnd
, struct grub_hfs_record
*rec
)
809 struct grub_hfs_catalog_key
*ckey
= rec
->key
;
811 if (grub_hfs_cmp_catkeys (rec
->key
, (void *) &key
) <= 0)
812 found
= grub_be_to_cpu32 (*(grub_uint32_t
*) rec
->data
);
814 if (hnd
->type
== 0xFF && ckey
->strlen
> 0)
817 next
= grub_be_to_cpu32 (hnd
->next
);
819 /* An entry was found. */
820 if (grub_be_to_cpu32 (ckey
->parent_dir
) == dir
)
827 int it_dir (struct grub_hfs_node
*hnd
__attribute ((unused
)),
828 struct grub_hfs_record
*rec
)
830 struct grub_hfs_catalog_key
*ckey
= rec
->key
;
831 struct grub_hfs_catalog_key
*origkey
= &key
;
833 /* Stop when the entries do not match anymore. */
834 if (grub_be_to_cpu32 (ckey
->parent_dir
)
835 != grub_be_to_cpu32 ((origkey
)->parent_dir
))
845 if (grub_hfs_iterate_records (data
, 0, root_idx
, 0, node_found
))
854 /* If there was a matching record in this leaf node, continue the
855 iteration until the last record was found. */
856 grub_hfs_iterate_records (data
, 0, next
, 1, it_dir
);
861 /* Find a file or directory with the pathname PATH in the filesystem
862 DATA. Return the file record in RETDATA when it is non-zero.
863 Return the directory number in RETINODE when it is non-zero. */
865 grub_hfs_find_dir (struct grub_hfs_data
*data
, const char *path
,
866 struct grub_hfs_filerec
*retdata
, int *retinode
)
868 int inode
= data
->rootdir
;
872 struct grub_hfs_filerec frec
;
873 struct grub_hfs_dirrec dir
;
876 fdrec
.frec
.type
= GRUB_HFS_FILETYPE_DIR
;
880 grub_error (GRUB_ERR_BAD_FILENAME
, "bad filename");
884 origpath
= grub_strdup (path
);
892 while (path
&& grub_strlen (path
))
894 if (fdrec
.frec
.type
!= GRUB_HFS_FILETYPE_DIR
)
896 grub_error (GRUB_ERR_BAD_FILE_TYPE
, "not a directory");
900 /* Isolate a part of the path. */
901 next
= grub_strchr (path
, '/');
908 struct grub_hfs_catalog_key key
;
910 key
.parent_dir
= grub_cpu_to_be32 (inode
);
911 key
.strlen
= grub_strlen (path
);
912 grub_strcpy ((char *) (key
.str
), path
);
914 /* Lookup this node. */
915 if (! grub_hfs_find_node (data
, (char *) &key
, data
->cat_root
,
916 0, (char *) &fdrec
.frec
, sizeof (fdrec
.frec
)))
918 grub_error (GRUB_ERR_FILE_NOT_FOUND
, "file not found");
925 inode
= grub_be_to_cpu32 (fdrec
.dir
.dirid
);
930 grub_memcpy (retdata
, &fdrec
.frec
, sizeof (fdrec
.frec
));
936 grub_free (origpath
);
943 grub_hfs_dir (grub_device_t device
, const char *path
,
944 int (*hook
) (const char *filename
,
945 const struct grub_dirhook_info
*info
))
949 auto int dir_hook (struct grub_hfs_record
*rec
);
951 int dir_hook (struct grub_hfs_record
*rec
)
953 char fname
[32] = { 0 };
954 char *filetype
= rec
->data
;
955 struct grub_hfs_catalog_key
*ckey
= rec
->key
;
956 struct grub_dirhook_info info
;
957 grub_memset (&info
, 0, sizeof (info
));
959 grub_strncpy (fname
, (char *) (ckey
->str
), ckey
->strlen
);
961 if (*filetype
== GRUB_HFS_FILETYPE_DIR
962 || *filetype
== GRUB_HFS_FILETYPE_FILE
)
964 info
.dir
= (*filetype
== GRUB_HFS_FILETYPE_DIR
);
965 return hook (fname
, &info
);
970 struct grub_hfs_data
*data
;
971 struct grub_hfs_filerec frec
;
973 grub_dl_ref (my_mod
);
975 data
= grub_hfs_mount (device
->disk
);
979 /* First the directory ID for the directory. */
980 if (grub_hfs_find_dir (data
, path
, &frec
, &inode
))
983 if (frec
.type
!= GRUB_HFS_FILETYPE_DIR
)
985 grub_error (GRUB_ERR_BAD_FILE_TYPE
, "not a directory");
989 grub_hfs_iterate_dir (data
, data
->cat_root
, inode
, dir_hook
);
994 grub_dl_unref (my_mod
);
1000 /* Open a file named NAME and initialize FILE. */
1002 grub_hfs_open (struct grub_file
*file
, const char *name
)
1004 struct grub_hfs_data
*data
;
1005 struct grub_hfs_filerec frec
;
1007 grub_dl_ref (my_mod
);
1009 data
= grub_hfs_mount (file
->device
->disk
);
1011 if (grub_hfs_find_dir (data
, name
, &frec
, 0))
1014 grub_dl_unref (my_mod
);
1018 if (frec
.type
!= GRUB_HFS_FILETYPE_FILE
)
1021 grub_error (GRUB_ERR_BAD_FILE_TYPE
, "not a file");
1022 grub_dl_unref (my_mod
);
1026 grub_memcpy (data
->extents
, frec
.extents
, sizeof (grub_hfs_datarecord_t
));
1027 file
->size
= grub_be_to_cpu32 (frec
.size
);
1028 data
->size
= grub_be_to_cpu32 (frec
.size
);
1029 data
->fileid
= grub_be_to_cpu32 (frec
.fileid
);
1038 grub_hfs_read (grub_file_t file
, char *buf
, grub_size_t len
)
1040 struct grub_hfs_data
*data
=
1041 (struct grub_hfs_data
*) file
->data
;
1043 return grub_hfs_read_file (data
, file
->read_hook
, file
->offset
, len
, buf
);
1048 grub_hfs_close (grub_file_t file
)
1050 grub_free (file
->data
);
1052 grub_dl_unref (my_mod
);
1059 grub_hfs_label (grub_device_t device
, char **label
)
1061 struct grub_hfs_data
*data
;
1063 data
= grub_hfs_mount (device
->disk
);
1066 *label
= grub_strndup ((char *) (data
->sblock
.volname
+ 1),
1067 *data
->sblock
.volname
);
1077 static struct grub_fs grub_hfs_fs
=
1080 .dir
= grub_hfs_dir
,
1081 .open
= grub_hfs_open
,
1082 .read
= grub_hfs_read
,
1083 .close
= grub_hfs_close
,
1084 .label
= grub_hfs_label
,
1090 grub_fs_register (&grub_hfs_fs
);
1096 grub_fs_unregister (&grub_hfs_fs
);