1
/* hfsplus.c - HFS+ Filesystem. */
3
* GRUB -- GRand Unified Bootloader
4
* Copyright (C) 2005 Free Software Foundation, Inc.
6
* This program 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 2 of the License, or
9
* (at your option) any later version.
11
* This program 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 this program; if not, write to the Free Software
18
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22
#include <grub/file.h>
24
#include <grub/misc.h>
25
#include <grub/disk.h>
27
#include <grub/types.h>
28
#include <grub/fshelp.h>
31
struct grub_hfsplus_extent
33
/* The first block of a file on disk. */
35
/* The amount of blocks described by this extent. */
37
} __attribute__ ((packed));
39
/* The descriptor of a fork. */
40
struct grub_hfsplus_forkdata
43
grub_uint32_t clumpsize;
45
struct grub_hfsplus_extent extents[8];
46
} __attribute__ ((packed));
48
/* The HFS+ Volume Header. */
49
struct grub_hfsplus_volheader
51
grub_uint8_t magic[2];
52
grub_uint16_t version;
53
grub_uint32_t attributes;
54
grub_uint8_t unused[32];
55
grub_uint32_t blksize;
56
grub_uint8_t unused2[68];
57
struct grub_hfsplus_forkdata allocations_file;
58
struct grub_hfsplus_forkdata extents_file;
59
struct grub_hfsplus_forkdata catalog_file;
60
struct grub_hfsplus_forkdata attrib_file;
61
struct grub_hfsplus_forkdata startup_file;
62
} __attribute__ ((packed));
64
/* The type of node. */
65
enum grub_hfsplus_btnode_type
67
GRUB_HFSPLUS_BTNODE_TYPE_LEAF = -1,
68
GRUB_HFSPLUS_BTNODE_TYPE_INDEX = 0,
69
GRUB_HFSPLUS_BTNODE_TYPE_HEADER = 1,
70
GRUB_HFSPLUS_BTNODE_TYPE_MAP = 2,
73
struct grub_hfsplus_btnode
81
} __attribute__ ((packed));
83
/* The header of a HFS+ B+ Tree. */
84
struct grub_hfsplus_btheader
88
grub_uint32_t leaf_records;
89
grub_uint32_t first_leaf_node;
90
grub_uint32_t last_leaf_node;
91
grub_uint16_t nodesize;
92
grub_uint16_t keysize;
93
} __attribute__ ((packed));
95
/* The on disk layout of a catalog key. */
96
struct grub_hfsplus_catkey
100
grub_uint16_t namelen;
101
grub_uint16_t name[30];
102
} __attribute__ ((packed));
104
/* The on disk layout of an extent overflow file key. */
105
struct grub_hfsplus_extkey
107
grub_uint16_t keylen;
110
grub_uint32_t fileid;
112
} __attribute__ ((packed));
114
struct grub_hfsplus_key
118
struct grub_hfsplus_extkey extkey;
119
struct grub_hfsplus_catkey catkey;
120
grub_uint16_t keylen;
122
} __attribute__ ((packed));
124
struct grub_hfsplus_catfile
128
grub_uint32_t reserved;
129
grub_uint32_t fileid;
130
grub_uint8_t unused1[30];
132
grub_uint8_t unused2[44];
133
struct grub_hfsplus_forkdata data;
134
struct grub_hfsplus_forkdata resource;
135
} __attribute__ ((packed));
137
/* Filetype information as used in inodes. */
138
#define GRUB_HFSPLUS_FILEMODE_MASK 0170000
139
#define GRUB_HFSPLUS_FILEMODE_REG 0100000
140
#define GRUB_HFSPLUS_FILEMODE_DIRECTORY 0040000
141
#define GRUB_HFSPLUS_FILEMODE_SYMLINK 0120000
143
/* Some pre-defined file IDs. */
144
#define GRUB_HFSPLUS_FILEID_ROOTDIR 2
145
#define GRUB_HFSPLUS_FILEID_OVERFLOW 3
146
#define GRUB_HFSPLUS_FILEID_CATALOG 4
148
enum grub_hfsplus_filetype
150
GRUB_HFSPLUS_FILETYPE_DIR = 1,
151
GRUB_HFSPLUS_FILETYPE_REG = 2,
152
GRUB_HFSPLUS_FILETYPE_DIR_THREAD = 3,
153
GRUB_HFSPLUS_FILETYPE_REG_THREAD = 4
156
/* Internal representation of a catalog key. */
157
struct grub_hfsplus_catkey_internal
163
/* Internal representation of an extent overflow key. */
164
struct grub_hfsplus_extkey_internal
166
grub_uint32_t fileid;
170
struct grub_hfsplus_key_internal
174
struct grub_hfsplus_extkey_internal extkey;
175
struct grub_hfsplus_catkey_internal catkey;
181
struct grub_fshelp_node
183
struct grub_hfsplus_data *data;
184
struct grub_hfsplus_extent extents[8];
186
grub_uint32_t fileid;
189
struct grub_hfsplus_btree
194
/* Catalog file node. */
195
struct grub_fshelp_node file;
198
/* Information about a "mounted" HFS+ filesystem. */
199
struct grub_hfsplus_data
201
struct grub_hfsplus_volheader volheader;
204
unsigned int log2blksize;
206
struct grub_hfsplus_btree catalog_tree;
207
struct grub_hfsplus_btree extoverflow_tree;
209
struct grub_fshelp_node dirroot;
210
struct grub_fshelp_node opened_file;
214
static grub_dl_t my_mod;
218
/* Find the extent that points to FILEBLOCK. If it is not in one of
219
the 8 extents described by EXTENT, return -1. In that case set
220
RETRY to the last block that was found in the last extent. */
222
grub_hfsplus_find_block (struct grub_hfsplus_extent *extent,
223
int fileblock, int *retry)
226
grub_size_t blksleft = fileblock;
228
/* First lookup the file in the given extents. */
229
for (i = 0; i < 8; i++)
231
if (blksleft < grub_be_to_cpu32 (extent[i].count))
232
return grub_be_to_cpu32 (extent[i].start) + blksleft;
233
blksleft -= grub_be_to_cpu32 (extent[i].count);
236
*retry = fileblock - blksleft;
241
grub_hfsplus_btree_search (struct grub_hfsplus_btree *btree,
242
struct grub_hfsplus_key_internal *key,
243
int (*compare_keys) (struct grub_hfsplus_key *keya,
244
struct grub_hfsplus_key_internal *keyb),
245
struct grub_hfsplus_btnode **matchnode, int *keyoffset);
247
static int grub_hfsplus_cmp_extkey (struct grub_hfsplus_key *keya,
248
struct grub_hfsplus_key_internal *keyb);
250
/* Search for the block FILEBLOCK inside the file NODE. Return the
251
blocknumber of this block on disk. */
253
grub_hfsplus_read_block (grub_fshelp_node_t node, int fileblock)
255
struct grub_hfsplus_extkey_internal extoverflow;
256
struct grub_hfsplus_extkey *keyfound;
258
struct grub_hfsplus_btnode *nnode = 0;
262
struct grub_hfsplus_extent *extents = &node->extents[0];
268
/* Try to find this block in the current set of extents. */
269
blk = grub_hfsplus_find_block (extents, fileblock, &retry);
273
/* The previous iteration of this loop allocated memory. The
274
code above used this memory, it can be free'ed now. */
278
/* For the extent overflow file, extra extents can't be found in
279
the extent overflow file. If this happens, you found a
281
if (node->fileid == GRUB_HFSPLUS_FILEID_OVERFLOW)
284
/* Set up the key to look for in the extent overflow file. */
285
extoverflow.fileid = node->fileid;
286
extoverflow.start = retry;
288
if (grub_hfsplus_btree_search (&node->data->extoverflow_tree,
289
(struct grub_hfsplus_key_internal *) &extoverflow,
290
grub_hfsplus_cmp_extkey, &nnode, &ptr))
293
cnode = (char *) nnode;
295
/* The extent overflow file has a 8 extents right after the key. */
296
keyfound = (struct grub_hfsplus_extkey *)
297
&cnode[(int) cnode[node->data->extoverflow_tree.nodesize - ptr - 1]];
298
extents = (struct grub_hfsplus_extent *)
299
((char *) keyfound + sizeof (struct grub_hfsplus_extkey));
301
/* The block wasn't found. Perhaps the next iteration will find
302
it. The last block we found is stored in FILEBLOCK now. */
303
/* XXX: Multiple iterations for locating the right extent was
304
not tested enough... */
311
/* Too bad, you lose. */
316
/* Read LEN bytes from the file described by DATA starting with byte
317
POS. Return the amount of read bytes in READ. */
319
grub_hfsplus_read_file (grub_fshelp_node_t node,
320
void (*read_hook) (unsigned long sector,
321
unsigned offset, unsigned length),
322
int pos, unsigned int len, char *buf)
324
return grub_fshelp_read_file (node->data->disk, node, read_hook,
325
pos, len, buf, grub_hfsplus_read_block,
327
node->data->log2blksize - GRUB_DISK_SECTOR_BITS);
330
static struct grub_hfsplus_data *
331
grub_hfsplus_mount (grub_disk_t disk)
333
struct grub_hfsplus_data *data;
334
struct grub_hfsplus_btheader header;
335
struct grub_hfsplus_btnode node;
337
data = grub_malloc (sizeof (*data));
343
/* Read the bootblock. */
344
grub_disk_read (disk, 2, 0, sizeof (struct grub_hfsplus_volheader),
345
(char *) &data->volheader);
349
/* Make sure this is an hfs+ filesystem. XXX: Do we really support
351
if (grub_strncmp (data->volheader.magic, "H+", 2)
352
&& grub_strncmp (data->volheader.magic, "HX", 2))
354
grub_error (GRUB_ERR_BAD_FS, "not a HFS+ filesystem");
358
if (grub_fshelp_log2blksize (grub_be_to_cpu32 (data->volheader.blksize),
362
/* Make a new node for the catalog tree. */
363
data->catalog_tree.file.data = data;
364
data->catalog_tree.file.fileid = GRUB_HFSPLUS_FILEID_CATALOG;
365
grub_memcpy (&data->catalog_tree.file.extents,
366
data->volheader.catalog_file.extents,
367
sizeof data->volheader.catalog_file.extents);
368
data->catalog_tree.file.size =
369
grub_be_to_cpu64 (data->volheader.catalog_file.size);
371
/* Make a new node for the extent overflow file. */
372
data->extoverflow_tree.file.data = data;
373
data->extoverflow_tree.file.fileid = GRUB_HFSPLUS_FILEID_OVERFLOW;
374
grub_memcpy (&data->extoverflow_tree.file.extents,
375
data->volheader.extents_file.extents,
376
sizeof data->volheader.catalog_file.extents);
378
data->extoverflow_tree.file.size =
379
grub_be_to_cpu64 (data->volheader.extents_file.size);
381
/* Read the essential information about the trees. */
382
if (! grub_hfsplus_read_file (&data->catalog_tree.file, 0,
383
sizeof (struct grub_hfsplus_btnode),
384
sizeof (header), (char *) &header))
387
data->catalog_tree.root = grub_be_to_cpu32 (header.root);
388
data->catalog_tree.nodesize = grub_be_to_cpu16 (header.nodesize);
390
if (! grub_hfsplus_read_file (&data->extoverflow_tree.file, 0,
391
sizeof (struct grub_hfsplus_btnode),
392
sizeof (header), (char *) &header))
395
data->extoverflow_tree.root = grub_be_to_cpu32 (header.root);
397
if (! grub_hfsplus_read_file (&data->extoverflow_tree.file, 0, 0,
398
sizeof (node), (char *) &node))
401
data->extoverflow_tree.root = grub_be_to_cpu32 (header.root);
402
data->extoverflow_tree.nodesize = grub_be_to_cpu16 (header.nodesize);
404
data->dirroot.data = data;
405
data->dirroot.fileid = GRUB_HFSPLUS_FILEID_ROOTDIR;
414
/* Compare the on disk catalog key KEYA with the catalog key we are
415
looking for (KEYB). */
417
grub_hfsplus_cmp_catkey (struct grub_hfsplus_key *keya,
418
struct grub_hfsplus_key_internal *keyb)
420
struct grub_hfsplus_catkey *catkey_a = &keya->catkey;
421
struct grub_hfsplus_catkey_internal *catkey_b = &keyb->catkey;
426
diff = grub_be_to_cpu32 (catkey_a->parent) - catkey_b->parent;
430
/* Change the filename in keya so the endianess is correct. */
431
for (i = 0; i < grub_be_to_cpu16 (catkey_a->namelen); i++)
432
catkey_a->name[i] = grub_be_to_cpu16 (catkey_a->name[i]);
434
filename = grub_malloc (grub_be_to_cpu16 (catkey_a->namelen) + 1);
436
if (! grub_utf16_to_utf8 (filename, catkey_a->name,
437
grub_be_to_cpu16 (catkey_a->namelen)))
438
return -1; /* XXX: This error never occurs, but in case it happens
439
just skip this entry. */
441
diff = grub_strncmp (filename, catkey_b->name,
442
grub_be_to_cpu16 (catkey_a->namelen));
444
grub_free (filename);
446
/* The endianess was changed to host format, change it back to
448
for (i = 0; i < grub_be_to_cpu16 (catkey_a->namelen); i++)
449
catkey_a->name[i] = grub_cpu_to_be16 (catkey_a->name[i]);
453
/* Compare the on disk extent overflow key KEYA with the extent
454
overflow key we are looking for (KEYB). */
456
grub_hfsplus_cmp_extkey (struct grub_hfsplus_key *keya,
457
struct grub_hfsplus_key_internal *keyb)
459
struct grub_hfsplus_extkey *extkey_a = &keya->extkey;
460
struct grub_hfsplus_extkey_internal *extkey_b = &keyb->extkey;
463
diff = grub_be_to_cpu32 (extkey_a->fileid) - extkey_b->fileid;
468
diff = grub_be_to_cpu32 (extkey_a->start) - extkey_b->start;
472
/* Return the offset of the record with the index INDEX, in the node
473
NODE which is part of the B+ tree BTREE. */
474
static inline unsigned int
475
grub_hfsplus_btree_recoffset (struct grub_hfsplus_btree *btree,
476
struct grub_hfsplus_btnode *node, int index)
478
char *cnode = (char *) node;
479
grub_uint16_t *recptr;
480
recptr = (grub_uint16_t *) (&cnode[btree->nodesize
481
- index * sizeof (grub_uint16_t) - 2]);
482
return grub_be_to_cpu16 (*recptr);
485
/* Return a pointer to the record with the index INDEX, in the node
486
NODE which is part of the B+ tree BTREE. */
487
static inline struct grub_hfsplus_key *
488
grub_hfsplus_btree_recptr (struct grub_hfsplus_btree *btree,
489
struct grub_hfsplus_btnode *node, int index)
491
char *cnode = (char *) node;
493
offset = grub_hfsplus_btree_recoffset (btree, node, index);
494
return (struct grub_hfsplus_key *) &cnode[offset];
499
grub_hfsplus_read_symlink (grub_fshelp_node_t node)
502
grub_ssize_t numread;
504
symlink = grub_malloc (node->size + 1);
508
numread = grub_hfsplus_read_file (node, 0, 0, node->size, symlink);
509
if (numread != (grub_ssize_t) node->size)
514
symlink[node->size] = '\0';
520
grub_hfsplus_btree_iterate_node (struct grub_hfsplus_btree *btree,
521
struct grub_hfsplus_btnode *first_node,
523
int (*hook) (void *record))
529
char *cnode = (char *) first_node;
531
/* Iterate over all records in this node. */
532
for (rec = first_rec; rec < grub_be_to_cpu16 (first_node->count); rec++)
534
if (hook (grub_hfsplus_btree_recptr (btree, first_node, rec)))
538
if (! first_node->next)
541
if (! grub_hfsplus_read_file (&btree->file, 0,
542
(grub_be_to_cpu32 (first_node->next)
544
btree->nodesize, cnode))
547
/* Don't skip any record in the next iteration. */
554
/* Lookup the node described by KEY in the B+ Tree BTREE. Compare
555
keys using the function COMPARE_KEYS. When a match is found,
556
return the node in MATCHNODE and a pointer to the data in this node
557
in KEYOFFSET. MATCHNODE should be free'ed by the caller. */
559
grub_hfsplus_btree_search (struct grub_hfsplus_btree *btree,
560
struct grub_hfsplus_key_internal *key,
561
int (*compare_keys) (struct grub_hfsplus_key *keya,
562
struct grub_hfsplus_key_internal *keyb),
563
struct grub_hfsplus_btnode **matchnode, int *keyoffset)
565
grub_uint64_t currnode;
567
struct grub_hfsplus_btnode *nodedesc;
570
node = grub_malloc (btree->nodesize);
574
currnode = btree->root;
580
if (! grub_hfsplus_read_file (&btree->file, 0,
581
(long)currnode * (long)btree->nodesize,
582
btree->nodesize, (char *) node))
588
nodedesc = (struct grub_hfsplus_btnode *) node;
590
/* Find the record in this tree. */
591
for (rec = 0; rec < grub_be_to_cpu16 (nodedesc->count); rec++)
593
struct grub_hfsplus_key *currkey;
594
currkey = grub_hfsplus_btree_recptr (btree, nodedesc, rec);
596
/* The action that has to be taken depend on the type of
598
if (nodedesc->type == GRUB_HFSPLUS_BTNODE_TYPE_LEAF
599
&& compare_keys (currkey, key) == 0)
601
/* An exact match was found! */
603
*matchnode = nodedesc;
608
else if (nodedesc->type == GRUB_HFSPLUS_BTNODE_TYPE_INDEX)
610
grub_uint32_t *pointer;
612
/* The place where the key could've been found didn't
613
contain the key. This means that the previous match
614
is the one that should be followed. */
615
if (compare_keys (currkey, key) > 0)
618
/* Mark the last key which is lower or equal to the key
619
that we are looking for. The last match that is
620
found will be used to locate the child which can
621
contain the record. */
622
pointer = (grub_uint32_t *) ((char *) currkey
623
+ grub_be_to_cpu16 (currkey->keylen)
625
currnode = grub_be_to_cpu32 (*pointer);
630
/* No match was found, no record with this key exists in the
642
grub_hfsplus_iterate_dir (grub_fshelp_node_t dir,
644
(*hook) (const char *filename,
645
enum grub_fshelp_filetype filetype,
646
grub_fshelp_node_t node))
648
auto int list_nodes (void *record);
649
int list_nodes (void *record)
651
struct grub_hfsplus_catkey *catkey;
655
struct grub_fshelp_node *node;
656
struct grub_hfsplus_catfile *fileinfo;
657
enum grub_fshelp_filetype type = GRUB_FSHELP_UNKNOWN;
659
catkey = (struct grub_hfsplus_catkey *) record;
661
fileinfo = (record + grub_be_to_cpu16 (catkey->keylen)
662
+ 2 + grub_be_to_cpu16(catkey->keylen) % 2);
664
/* Stop iterating when the last directory entry was found. */
665
if (grub_be_to_cpu32 (catkey->parent) != dir->fileid)
668
filename = grub_malloc (grub_be_to_cpu16 (catkey->namelen) + 1);
672
/* Make sure the byte order of the UTF16 string is correct. */
673
for (i = 0; i < grub_be_to_cpu16 (catkey->namelen); i++)
674
catkey->name[i] = grub_be_to_cpu16 (catkey->name[i]);
676
if (! grub_utf16_to_utf8 (filename, catkey->name,
677
grub_be_to_cpu16 (catkey->namelen)))
679
grub_free (filename);
683
filename[grub_be_to_cpu16 (catkey->namelen)] = '\0';
685
/* Restore the byte order to what it was previously. */
686
for (i = 0; i < grub_be_to_cpu16 (catkey->namelen); i++)
687
catkey->name[i] = grub_be_to_cpu16 (catkey->name[i]);
689
/* Determine the type of the node that was found. */
690
if (grub_be_to_cpu16 (fileinfo->type) == GRUB_HFSPLUS_FILETYPE_REG)
692
int mode = (grub_be_to_cpu16 (fileinfo->mode)
693
& GRUB_HFSPLUS_FILEMODE_MASK);
695
if (mode == GRUB_HFSPLUS_FILEMODE_REG)
696
type = GRUB_FSHELP_REG;
697
else if (mode == GRUB_HFSPLUS_FILEMODE_SYMLINK)
698
type = GRUB_FSHELP_SYMLINK;
700
type = GRUB_FSHELP_UNKNOWN;
702
else if (grub_be_to_cpu16 (fileinfo->type) == GRUB_HFSPLUS_FILETYPE_DIR)
703
type = GRUB_FSHELP_DIR;
705
/* Only accept valid nodes. */
706
if (type && grub_strlen (filename) == grub_be_to_cpu16 (catkey->namelen))
708
/* A valid node was found; setup the node and call the
709
callback function. */
710
node = grub_malloc (sizeof (*node));
711
node->data = dir->data;
713
grub_memcpy (node->extents, fileinfo->data.extents,
714
sizeof (*node->extents));
715
node->size = grub_be_to_cpu64 (fileinfo->data.size);
716
node->fileid = grub_be_to_cpu32 (fileinfo->fileid);
718
ret = hook (filename, type, node);
721
grub_free (filename);
726
struct grub_hfsplus_key_internal intern;
727
struct grub_hfsplus_btnode *node;
731
/* Create a key that points to the first entry in the directory. */
732
intern.catkey.parent = dir->fileid;
733
intern.catkey.name = "";
735
/* First lookup the first entry. */
736
if (grub_hfsplus_btree_search (&dir->data->catalog_tree, &intern,
737
grub_hfsplus_cmp_catkey, &node, &ptr))
740
/* Iterate over all entries in this directory. */
741
ret = grub_hfsplus_btree_iterate_node (&dir->data->catalog_tree, node, ptr,
749
/* Open a file named NAME and initialize FILE. */
751
grub_hfsplus_open (struct grub_file *file, const char *name)
753
struct grub_hfsplus_data *data;
754
struct grub_fshelp_node *fdiro = 0;
757
grub_dl_ref (my_mod);
760
data = grub_hfsplus_mount (file->device->disk);
764
grub_fshelp_find_file (name, &data->dirroot, &fdiro,
765
grub_hfsplus_iterate_dir,
766
grub_hfsplus_read_symlink, GRUB_FSHELP_REG);
770
file->size = fdiro->size;
771
data->opened_file = *fdiro;
780
if (data && fdiro != &data->dirroot)
785
grub_dl_unref (my_mod);
793
grub_hfsplus_close (grub_file_t file)
795
grub_free (file->data);
798
grub_dl_unref (my_mod);
801
return GRUB_ERR_NONE;
805
/* Read LEN bytes data from FILE into BUF. */
807
grub_hfsplus_read (grub_file_t file, char *buf, grub_ssize_t len)
809
struct grub_hfsplus_data *data =
810
(struct grub_hfsplus_data *) file->data;
812
int size = grub_hfsplus_read_file (&data->opened_file, file->read_hook,
813
file->offset, len, buf);
820
grub_hfsplus_dir (grub_device_t device, const char *path,
821
int (*hook) (const char *filename, int dir))
823
struct grub_hfsplus_data *data = 0;
824
struct grub_fshelp_node *fdiro = 0;
826
auto int NESTED_FUNC_ATTR iterate (const char *filename,
827
enum grub_fshelp_filetype filetype,
828
grub_fshelp_node_t node);
830
int NESTED_FUNC_ATTR iterate (const char *filename,
831
enum grub_fshelp_filetype filetype,
832
grub_fshelp_node_t node)
836
if (filetype == GRUB_FSHELP_DIR)
837
return hook (filename, 1);
839
return hook (filename, 0);
845
grub_dl_ref (my_mod);
848
data = grub_hfsplus_mount (device->disk);
852
/* Find the directory that should be opened. */
853
grub_fshelp_find_file (path, &data->dirroot, &fdiro,
854
grub_hfsplus_iterate_dir,
855
grub_hfsplus_read_symlink, GRUB_FSHELP_DIR);
859
/* Iterate over all entries in this directory. */
860
grub_hfsplus_iterate_dir (fdiro, iterate);
863
if (data && fdiro != &data->dirroot)
868
grub_dl_unref (my_mod);
876
grub_hfsplus_label (grub_device_t device __attribute__((unused))
877
, char **label __attribute__((unused)))
879
/* XXX: It's not documented how to read a label. */
880
return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
881
"reading the label of a HFS+ "
882
"partiton is not implemented");
886
static struct grub_fs grub_hfsplus_fs =
889
.dir = grub_hfsplus_dir,
890
.open = grub_hfsplus_open,
891
.read = grub_hfsplus_read,
892
.close = grub_hfsplus_close,
893
.label = grub_hfsplus_label,
897
GRUB_MOD_INIT(hfsplus)
899
grub_fs_register (&grub_hfsplus_fs);
905
GRUB_MOD_FINI(hfsplus)
907
grub_fs_unregister (&grub_hfsplus_fs);