malloc->zalloc
[grub2/phcoder.git] / fs / hfsplus.c
blobeae28d72a940e4b2c8e6ef59d2a4918d4367a5ca
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 #include <grub/err.h>
23 #include <grub/file.h>
24 #include <grub/mm.h>
25 #include <grub/misc.h>
26 #include <grub/disk.h>
27 #include <grub/dl.h>
28 #include <grub/types.h>
29 #include <grub/fshelp.h>
30 #include <grub/hfs.h>
31 #include <grub/hfsplus.h>
33 /* Internal representation of a catalog key. */
34 struct grub_hfsplus_catkey_internal
36 int parent;
37 char *name;
40 /* Internal representation of an extent overflow key. */
41 struct grub_hfsplus_extkey_internal
43 grub_uint32_t fileid;
44 grub_uint32_t start;
47 struct grub_hfsplus_key_internal
49 union
51 struct grub_hfsplus_extkey_internal extkey;
52 struct grub_hfsplus_catkey_internal catkey;
58 struct grub_fshelp_node
60 struct grub_hfsplus_data *data;
61 struct grub_hfsplus_extent extents[8];
62 grub_uint64_t size;
63 grub_uint32_t fileid;
64 grub_int32_t mtime;
67 struct grub_hfsplus_btree
69 grub_uint32_t root;
70 int nodesize;
72 /* Catalog file node. */
73 struct grub_fshelp_node file;
76 /* Information about a "mounted" HFS+ filesystem. */
77 struct grub_hfsplus_data
79 struct grub_hfsplus_volheader volheader;
80 grub_disk_t disk;
82 unsigned int log2blksize;
84 struct grub_hfsplus_btree catalog_tree;
85 struct grub_hfsplus_btree extoverflow_tree;
87 struct grub_fshelp_node dirroot;
88 struct grub_fshelp_node opened_file;
90 /* This is the offset into the physical disk for an embedded HFS+
91 filesystem (one inside a plain HFS wrapper). */
92 int embedded_offset;
93 int case_sensitive;
96 static grub_dl_t my_mod;
99 /* Return the offset of the record with the index INDEX, in the node
100 NODE which is part of the B+ tree BTREE. */
101 static inline unsigned int
102 grub_hfsplus_btree_recoffset (struct grub_hfsplus_btree *btree,
103 struct grub_hfsplus_btnode *node, int index)
105 char *cnode = (char *) node;
106 grub_uint16_t *recptr;
107 recptr = (grub_uint16_t *) (&cnode[btree->nodesize
108 - index * sizeof (grub_uint16_t) - 2]);
109 return grub_be_to_cpu16 (*recptr);
112 /* Return a pointer to the record with the index INDEX, in the node
113 NODE which is part of the B+ tree BTREE. */
114 static inline struct grub_hfsplus_key *
115 grub_hfsplus_btree_recptr (struct grub_hfsplus_btree *btree,
116 struct grub_hfsplus_btnode *node, int index)
118 char *cnode = (char *) node;
119 unsigned int offset;
120 offset = grub_hfsplus_btree_recoffset (btree, node, index);
121 return (struct grub_hfsplus_key *) &cnode[offset];
125 /* Find the extent that points to FILEBLOCK. If it is not in one of
126 the 8 extents described by EXTENT, return -1. In that case set
127 FILEBLOCK to the next block. */
128 static int
129 grub_hfsplus_find_block (struct grub_hfsplus_extent *extent,
130 int *fileblock)
132 int i;
133 grub_size_t blksleft = *fileblock;
135 /* First lookup the file in the given extents. */
136 for (i = 0; i < 8; i++)
138 if (blksleft < grub_be_to_cpu32 (extent[i].count))
139 return grub_be_to_cpu32 (extent[i].start) + blksleft;
140 blksleft -= grub_be_to_cpu32 (extent[i].count);
143 *fileblock = blksleft;
144 return -1;
147 static grub_err_t
148 grub_hfsplus_btree_search (struct grub_hfsplus_btree *btree,
149 struct grub_hfsplus_key_internal *key,
150 int (*compare_keys) (struct grub_hfsplus_key *keya,
151 struct grub_hfsplus_key_internal *keyb),
152 struct grub_hfsplus_btnode **matchnode, int *keyoffset);
154 static int grub_hfsplus_cmp_extkey (struct grub_hfsplus_key *keya,
155 struct grub_hfsplus_key_internal *keyb);
157 /* Search for the block FILEBLOCK inside the file NODE. Return the
158 blocknumber of this block on disk. */
159 static grub_disk_addr_t
160 grub_hfsplus_read_block (grub_fshelp_node_t node, grub_disk_addr_t fileblock)
162 struct grub_hfsplus_btnode *nnode = 0;
163 int blksleft = fileblock;
164 struct grub_hfsplus_extent *extents = &node->extents[0];
166 while (1)
168 struct grub_hfsplus_extkey *key;
169 struct grub_hfsplus_extkey_internal extoverflow;
170 int blk;
171 int ptr;
173 /* Try to find this block in the current set of extents. */
174 blk = grub_hfsplus_find_block (extents, &blksleft);
176 /* The previous iteration of this loop allocated memory. The
177 code above used this memory, it can be freed now. */
178 grub_free (nnode);
179 nnode = 0;
181 if (blk != -1)
182 return (blk
183 + (node->data->embedded_offset >> (node->data->log2blksize
184 - GRUB_DISK_SECTOR_BITS)));
186 /* For the extent overflow file, extra extents can't be found in
187 the extent overflow file. If this happens, you found a
188 bug... */
189 if (node->fileid == GRUB_HFSPLUS_FILEID_OVERFLOW)
191 grub_error (GRUB_ERR_READ_ERROR,
192 "extra extents found in an extend overflow file");
193 break;
196 /* Set up the key to look for in the extent overflow file. */
197 extoverflow.fileid = node->fileid;
198 extoverflow.start = fileblock - blksleft;
200 if (grub_hfsplus_btree_search (&node->data->extoverflow_tree,
201 (struct grub_hfsplus_key_internal *) &extoverflow,
202 grub_hfsplus_cmp_extkey, &nnode, &ptr))
204 grub_error (GRUB_ERR_READ_ERROR,
205 "no block found for the file id 0x%x and the block offset 0x%x",
206 node->fileid, fileblock);
207 break;
210 /* The extent overflow file has 8 extents right after the key. */
211 key = (struct grub_hfsplus_extkey *)
212 grub_hfsplus_btree_recptr (&node->data->extoverflow_tree, nnode, ptr);
213 extents = (struct grub_hfsplus_extent *) (key + 1);
215 /* The block wasn't found. Perhaps the next iteration will find
216 it. The last block we found is stored in BLKSLEFT now. */
219 grub_free (nnode);
221 /* Too bad, you lose. */
222 return -1;
226 /* Read LEN bytes from the file described by DATA starting with byte
227 POS. Return the amount of read bytes in READ. */
228 static grub_ssize_t
229 grub_hfsplus_read_file (grub_fshelp_node_t node,
230 void NESTED_FUNC_ATTR (*read_hook) (grub_disk_addr_t sector,
231 unsigned offset, unsigned length),
232 int pos, grub_size_t len, char *buf)
234 return grub_fshelp_read_file (node->data->disk, node, read_hook,
235 pos, len, buf, grub_hfsplus_read_block,
236 node->size,
237 node->data->log2blksize - GRUB_DISK_SECTOR_BITS);
240 static struct grub_hfsplus_data *
241 grub_hfsplus_mount (grub_disk_t disk)
243 struct grub_hfsplus_data *data;
244 struct grub_hfsplus_btheader header;
245 struct grub_hfsplus_btnode node;
246 grub_uint16_t magic;
247 union {
248 struct grub_hfs_sblock hfs;
249 struct grub_hfsplus_volheader hfsplus;
250 } volheader;
252 data = grub_malloc (sizeof (*data));
253 if (!data)
254 return 0;
256 data->disk = disk;
258 /* Read the bootblock. */
259 grub_disk_read (disk, GRUB_HFSPLUS_SBLOCK, 0, sizeof (volheader),
260 &volheader);
261 if (grub_errno)
262 goto fail;
264 data->embedded_offset = 0;
265 if (grub_be_to_cpu16 (volheader.hfs.magic) == GRUB_HFS_MAGIC)
267 int extent_start;
268 int ablk_size;
269 int ablk_start;
271 /* See if there's an embedded HFS+ filesystem. */
272 if (grub_be_to_cpu16 (volheader.hfs.embed_sig) != GRUB_HFSPLUS_MAGIC)
274 grub_error (GRUB_ERR_BAD_FS, "not a HFS+ filesystem");
275 goto fail;
278 /* Calculate the offset needed to translate HFS+ sector numbers. */
279 extent_start = grub_be_to_cpu16 (volheader.hfs.embed_extent.first_block);
280 ablk_size = grub_be_to_cpu32 (volheader.hfs.blksz);
281 ablk_start = grub_be_to_cpu16 (volheader.hfs.first_block);
282 data->embedded_offset = (ablk_start
283 + extent_start
284 * (ablk_size >> GRUB_DISK_SECTOR_BITS));
286 grub_disk_read (disk, data->embedded_offset + GRUB_HFSPLUS_SBLOCK, 0,
287 sizeof (volheader), &volheader);
288 if (grub_errno)
289 goto fail;
292 /* Make sure this is an HFS+ filesystem. XXX: Do we really support
293 HFX? */
294 magic = grub_be_to_cpu16 (volheader.hfsplus.magic);
295 if ((magic != GRUB_HFSPLUS_MAGIC) && (magic != GRUB_HFSPLUSX_MAGIC))
297 grub_error (GRUB_ERR_BAD_FS, "not a HFS+ filesystem");
298 goto fail;
301 grub_memcpy (&data->volheader, &volheader.hfsplus,
302 sizeof (volheader.hfsplus));
304 if (grub_fshelp_log2blksize (grub_be_to_cpu32 (data->volheader.blksize),
305 &data->log2blksize))
306 goto fail;
308 /* Make a new node for the catalog tree. */
309 data->catalog_tree.file.data = data;
310 data->catalog_tree.file.fileid = GRUB_HFSPLUS_FILEID_CATALOG;
311 grub_memcpy (&data->catalog_tree.file.extents,
312 data->volheader.catalog_file.extents,
313 sizeof data->volheader.catalog_file.extents);
314 data->catalog_tree.file.size =
315 grub_be_to_cpu64 (data->volheader.catalog_file.size);
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 grub_memcpy (&data->extoverflow_tree.file.extents,
321 data->volheader.extents_file.extents,
322 sizeof data->volheader.catalog_file.extents);
324 data->extoverflow_tree.file.size =
325 grub_be_to_cpu64 (data->volheader.extents_file.size);
327 /* Read the essential information about the trees. */
328 if (! grub_hfsplus_read_file (&data->catalog_tree.file, 0,
329 sizeof (struct grub_hfsplus_btnode),
330 sizeof (header), (char *) &header))
331 goto fail;
333 data->catalog_tree.root = grub_be_to_cpu32 (header.root);
334 data->catalog_tree.nodesize = grub_be_to_cpu16 (header.nodesize);
335 data->case_sensitive = ((magic == GRUB_HFSPLUSX_MAGIC) &&
336 (header.key_compare == GRUB_HFSPLUSX_BINARYCOMPARE));
338 if (! grub_hfsplus_read_file (&data->extoverflow_tree.file, 0,
339 sizeof (struct grub_hfsplus_btnode),
340 sizeof (header), (char *) &header))
341 goto fail;
343 data->extoverflow_tree.root = grub_be_to_cpu32 (header.root);
345 if (! grub_hfsplus_read_file (&data->extoverflow_tree.file, 0, 0,
346 sizeof (node), (char *) &node))
347 goto fail;
349 data->extoverflow_tree.root = grub_be_to_cpu32 (header.root);
350 data->extoverflow_tree.nodesize = grub_be_to_cpu16 (header.nodesize);
352 data->dirroot.data = data;
353 data->dirroot.fileid = GRUB_HFSPLUS_FILEID_ROOTDIR;
355 return data;
357 fail:
359 if (grub_errno == GRUB_ERR_OUT_OF_RANGE)
360 grub_error (GRUB_ERR_BAD_FS, "not a hfsplus filesystem");
362 grub_free (data);
363 return 0;
366 /* Compare the on disk catalog key KEYA with the catalog key we are
367 looking for (KEYB). */
368 static int
369 grub_hfsplus_cmp_catkey (struct grub_hfsplus_key *keya,
370 struct grub_hfsplus_key_internal *keyb)
372 struct grub_hfsplus_catkey *catkey_a = &keya->catkey;
373 struct grub_hfsplus_catkey_internal *catkey_b = &keyb->catkey;
374 char *filename;
375 int i;
376 int diff;
378 diff = grub_be_to_cpu32 (catkey_a->parent) - catkey_b->parent;
379 if (diff)
380 return diff;
382 /* Change the filename in keya so the endianness is correct. */
383 for (i = 0; i < grub_be_to_cpu16 (catkey_a->namelen); i++)
384 catkey_a->name[i] = grub_be_to_cpu16 (catkey_a->name[i]);
386 filename = grub_malloc (grub_be_to_cpu16 (catkey_a->namelen) + 1);
388 if (! grub_utf16_to_utf8 ((grub_uint8_t *) filename, catkey_a->name,
389 grub_be_to_cpu16 (catkey_a->namelen)))
390 return -1; /* XXX: This error never occurs, but in case it happens
391 just skip this entry. */
393 diff = grub_strncmp (filename, catkey_b->name,
394 grub_be_to_cpu16 (catkey_a->namelen));
396 grub_free (filename);
398 /* The endianness was changed to host format, change it back to
399 whatever it was. */
400 for (i = 0; i < grub_be_to_cpu16 (catkey_a->namelen); i++)
401 catkey_a->name[i] = grub_cpu_to_be16 (catkey_a->name[i]);
402 return diff;
405 /* Compare the on disk extent overflow key KEYA with the extent
406 overflow key we are looking for (KEYB). */
407 static int
408 grub_hfsplus_cmp_extkey (struct grub_hfsplus_key *keya,
409 struct grub_hfsplus_key_internal *keyb)
411 struct grub_hfsplus_extkey *extkey_a = &keya->extkey;
412 struct grub_hfsplus_extkey_internal *extkey_b = &keyb->extkey;
413 int diff;
415 diff = grub_be_to_cpu32 (extkey_a->fileid) - extkey_b->fileid;
417 if (diff)
418 return diff;
420 diff = grub_be_to_cpu32 (extkey_a->start) - extkey_b->start;
421 return diff;
424 static char *
425 grub_hfsplus_read_symlink (grub_fshelp_node_t node)
427 char *symlink;
428 grub_ssize_t numread;
430 symlink = grub_malloc (node->size + 1);
431 if (!symlink)
432 return 0;
434 numread = grub_hfsplus_read_file (node, 0, 0, node->size, symlink);
435 if (numread != (grub_ssize_t) node->size)
437 grub_free (symlink);
438 return 0;
440 symlink[node->size] = '\0';
442 return symlink;
445 static int
446 grub_hfsplus_btree_iterate_node (struct grub_hfsplus_btree *btree,
447 struct grub_hfsplus_btnode *first_node,
448 int first_rec,
449 int (*hook) (void *record))
451 int rec;
453 for (;;)
455 char *cnode = (char *) first_node;
457 /* Iterate over all records in this node. */
458 for (rec = first_rec; rec < grub_be_to_cpu16 (first_node->count); rec++)
460 if (hook (grub_hfsplus_btree_recptr (btree, first_node, rec)))
461 return 1;
464 if (! first_node->next)
465 break;
467 if (! grub_hfsplus_read_file (&btree->file, 0,
468 (grub_be_to_cpu32 (first_node->next)
469 * btree->nodesize),
470 btree->nodesize, cnode))
471 return 1;
473 /* Don't skip any record in the next iteration. */
474 first_rec = 0;
477 return 0;
480 /* Lookup the node described by KEY in the B+ Tree BTREE. Compare
481 keys using the function COMPARE_KEYS. When a match is found,
482 return the node in MATCHNODE and a pointer to the data in this node
483 in KEYOFFSET. MATCHNODE should be freed by the caller. */
484 static grub_err_t
485 grub_hfsplus_btree_search (struct grub_hfsplus_btree *btree,
486 struct grub_hfsplus_key_internal *key,
487 int (*compare_keys) (struct grub_hfsplus_key *keya,
488 struct grub_hfsplus_key_internal *keyb),
489 struct grub_hfsplus_btnode **matchnode, int *keyoffset)
491 grub_uint64_t currnode;
492 char *node;
493 struct grub_hfsplus_btnode *nodedesc;
494 int rec;
496 node = grub_malloc (btree->nodesize);
497 if (! node)
498 return grub_errno;
500 currnode = btree->root;
501 while (1)
503 int match = 0;
505 /* Read a node. */
506 if (! grub_hfsplus_read_file (&btree->file, 0,
507 (long)currnode * (long)btree->nodesize,
508 btree->nodesize, (char *) node))
510 grub_free (node);
511 return grub_errno;
514 nodedesc = (struct grub_hfsplus_btnode *) node;
516 /* Find the record in this tree. */
517 for (rec = 0; rec < grub_be_to_cpu16 (nodedesc->count); rec++)
519 struct grub_hfsplus_key *currkey;
520 currkey = grub_hfsplus_btree_recptr (btree, nodedesc, rec);
522 /* The action that has to be taken depend on the type of
523 record. */
524 if (nodedesc->type == GRUB_HFSPLUS_BTNODE_TYPE_LEAF
525 && compare_keys (currkey, key) == 0)
527 /* An exact match was found! */
529 *matchnode = nodedesc;
530 *keyoffset = rec;
532 return 0;
534 else if (nodedesc->type == GRUB_HFSPLUS_BTNODE_TYPE_INDEX)
536 grub_uint32_t *pointer;
538 /* The place where the key could have been found didn't
539 contain the key. This means that the previous match
540 is the one that should be followed. */
541 if (compare_keys (currkey, key) > 0)
542 break;
544 /* Mark the last key which is lower or equal to the key
545 that we are looking for. The last match that is
546 found will be used to locate the child which can
547 contain the record. */
548 pointer = (grub_uint32_t *) ((char *) currkey
549 + grub_be_to_cpu16 (currkey->keylen)
550 + 2);
551 currnode = grub_be_to_cpu32 (*pointer);
552 match = 1;
556 /* No match is found, no record with this key exists in the
557 tree. */
558 if (! match)
560 *matchnode = 0;
561 grub_free (node);
562 return 1;
567 static int
568 grub_hfsplus_iterate_dir (grub_fshelp_node_t dir,
569 int NESTED_FUNC_ATTR
570 (*hook) (const char *filename,
571 enum grub_fshelp_filetype filetype,
572 grub_fshelp_node_t node))
574 int ret = 0;
576 auto int list_nodes (void *record);
577 int list_nodes (void *record)
579 struct grub_hfsplus_catkey *catkey;
580 char *filename;
581 int i;
582 struct grub_fshelp_node *node;
583 struct grub_hfsplus_catfile *fileinfo;
584 enum grub_fshelp_filetype type = GRUB_FSHELP_UNKNOWN;
586 catkey = (struct grub_hfsplus_catkey *) record;
588 fileinfo =
589 (struct grub_hfsplus_catfile *) ((char *) record
590 + grub_be_to_cpu16 (catkey->keylen)
591 + 2 + (grub_be_to_cpu16(catkey->keylen)
592 % 2));
594 /* Stop iterating when the last directory entry is found. */
595 if (grub_be_to_cpu32 (catkey->parent) != dir->fileid)
596 return 1;
598 /* Determine the type of the node that is found. */
599 if (grub_be_to_cpu16 (fileinfo->type) == GRUB_HFSPLUS_FILETYPE_REG)
601 int mode = (grub_be_to_cpu16 (fileinfo->mode)
602 & GRUB_HFSPLUS_FILEMODE_MASK);
604 if (mode == GRUB_HFSPLUS_FILEMODE_REG)
605 type = GRUB_FSHELP_REG;
606 else if (mode == GRUB_HFSPLUS_FILEMODE_SYMLINK)
607 type = GRUB_FSHELP_SYMLINK;
608 else
609 type = GRUB_FSHELP_UNKNOWN;
611 else if (grub_be_to_cpu16 (fileinfo->type) == GRUB_HFSPLUS_FILETYPE_DIR)
612 type = GRUB_FSHELP_DIR;
614 if (type == GRUB_FSHELP_UNKNOWN)
615 return 0;
617 /* Make sure the byte order of the UTF16 string is correct. */
618 for (i = 0; i < grub_be_to_cpu16 (catkey->namelen); i++)
620 catkey->name[i] = grub_be_to_cpu16 (catkey->name[i]);
622 /* If the name is obviously invalid, skip this node. */
623 if (catkey->name[i] == 0)
624 return 0;
627 filename = grub_malloc (grub_be_to_cpu16 (catkey->namelen) + 1);
628 if (! filename)
629 return 0;
631 if (! grub_utf16_to_utf8 ((grub_uint8_t *) filename, catkey->name,
632 grub_be_to_cpu16 (catkey->namelen)))
634 grub_free (filename);
635 return 0;
638 filename[grub_be_to_cpu16 (catkey->namelen)] = '\0';
640 /* Restore the byte order to what it was previously. */
641 for (i = 0; i < grub_be_to_cpu16 (catkey->namelen); i++)
642 catkey->name[i] = grub_be_to_cpu16 (catkey->name[i]);
644 /* hfs+ is case insensitive. */
645 if (! dir->data->case_sensitive)
646 type |= GRUB_FSHELP_CASE_INSENSITIVE;
648 /* Only accept valid nodes. */
649 if (grub_strlen (filename) == grub_be_to_cpu16 (catkey->namelen))
651 /* A valid node is found; setup the node and call the
652 callback function. */
653 node = grub_malloc (sizeof (*node));
654 node->data = dir->data;
656 grub_memcpy (node->extents, fileinfo->data.extents,
657 sizeof (node->extents));
658 node->mtime = grub_be_to_cpu32 (fileinfo->mtime) - 2082844800;
659 node->size = grub_be_to_cpu64 (fileinfo->data.size);
660 node->fileid = grub_be_to_cpu32 (fileinfo->fileid);
662 ret = hook (filename, type, node);
665 grub_free (filename);
667 return ret;
670 struct grub_hfsplus_key_internal intern;
671 struct grub_hfsplus_btnode *node;
672 int ptr;
674 /* Create a key that points to the first entry in the directory. */
675 intern.catkey.parent = dir->fileid;
676 intern.catkey.name = "";
678 /* First lookup the first entry. */
679 if (grub_hfsplus_btree_search (&dir->data->catalog_tree, &intern,
680 grub_hfsplus_cmp_catkey, &node, &ptr))
681 return 0;
683 /* Iterate over all entries in this directory. */
684 grub_hfsplus_btree_iterate_node (&dir->data->catalog_tree, node, ptr,
685 list_nodes);
687 grub_free (node);
689 return ret;
692 /* Open a file named NAME and initialize FILE. */
693 static grub_err_t
694 grub_hfsplus_open (struct grub_file *file, const char *name)
696 struct grub_hfsplus_data *data;
697 struct grub_fshelp_node *fdiro = 0;
699 grub_dl_ref (my_mod);
701 data = grub_hfsplus_mount (file->device->disk);
702 if (!data)
703 goto fail;
705 grub_fshelp_find_file (name, &data->dirroot, &fdiro,
706 grub_hfsplus_iterate_dir,
707 grub_hfsplus_read_symlink, GRUB_FSHELP_REG);
708 if (grub_errno)
709 goto fail;
711 file->size = fdiro->size;
712 data->opened_file = *fdiro;
713 grub_free (fdiro);
715 file->data = data;
716 file->offset = 0;
718 return 0;
720 fail:
721 if (data && fdiro != &data->dirroot)
722 grub_free (fdiro);
723 grub_free (data);
725 grub_dl_unref (my_mod);
727 return grub_errno;
731 static grub_err_t
732 grub_hfsplus_close (grub_file_t file)
734 grub_free (file->data);
736 grub_dl_unref (my_mod);
738 return GRUB_ERR_NONE;
742 /* Read LEN bytes data from FILE into BUF. */
743 static grub_ssize_t
744 grub_hfsplus_read (grub_file_t file, char *buf, grub_size_t len)
746 struct grub_hfsplus_data *data =
747 (struct grub_hfsplus_data *) file->data;
749 int size = grub_hfsplus_read_file (&data->opened_file, file->read_hook,
750 file->offset, len, buf);
752 return size;
756 static grub_err_t
757 grub_hfsplus_dir (grub_device_t device, const char *path,
758 int (*hook) (const char *filename,
759 const struct grub_dirhook_info *info))
761 struct grub_hfsplus_data *data = 0;
762 struct grub_fshelp_node *fdiro = 0;
764 auto int NESTED_FUNC_ATTR iterate (const char *filename,
765 enum grub_fshelp_filetype filetype,
766 grub_fshelp_node_t node);
768 int NESTED_FUNC_ATTR iterate (const char *filename,
769 enum grub_fshelp_filetype filetype,
770 grub_fshelp_node_t node)
772 struct grub_dirhook_info info;
773 grub_memset (&info, 0, sizeof (info));
774 info.dir = ((filetype & GRUB_FSHELP_TYPE_MASK) == GRUB_FSHELP_DIR);
775 info.mtimeset = 1;
776 info.mtime = node->mtime;
777 info.inodeset = 1;
778 info.inode = node->fileid;
779 info.case_insensitive = !! (filetype & GRUB_FSHELP_CASE_INSENSITIVE);
780 grub_free (node);
781 return hook (filename, &info);
784 grub_dl_ref (my_mod);
786 data = grub_hfsplus_mount (device->disk);
787 if (!data)
788 goto fail;
790 /* Find the directory that should be opened. */
791 grub_fshelp_find_file (path, &data->dirroot, &fdiro,
792 grub_hfsplus_iterate_dir,
793 grub_hfsplus_read_symlink, GRUB_FSHELP_DIR);
794 if (grub_errno)
795 goto fail;
797 /* Iterate over all entries in this directory. */
798 grub_hfsplus_iterate_dir (fdiro, iterate);
800 fail:
801 if (data && fdiro != &data->dirroot)
802 grub_free (fdiro);
803 grub_free (data);
805 grub_dl_unref (my_mod);
807 return grub_errno;
811 static grub_err_t
812 grub_hfsplus_label (grub_device_t device __attribute__((unused))
813 , char **label __attribute__((unused)))
815 /* XXX: It's not documented how to read a label. */
816 return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
817 "reading the label of a HFS+ "
818 "partition is not implemented");
821 /* Get mtime. */
822 static grub_err_t
823 grub_hfsplus_mtime (grub_device_t device, grub_int32_t *tm)
825 struct grub_hfsplus_data *data;
826 grub_disk_t disk = device->disk;
828 grub_dl_ref (my_mod);
830 data = grub_hfsplus_mount (disk);
831 if (!data)
832 *tm = 0;
833 else
834 *tm = grub_be_to_cpu32 (data->volheader.utime) - 2082844800;
836 grub_dl_unref (my_mod);
838 grub_free (data);
840 return grub_errno;
844 static grub_err_t
845 grub_hfsplus_uuid (grub_device_t device, char **uuid)
847 struct grub_hfsplus_data *data;
848 grub_disk_t disk = device->disk;
850 grub_dl_ref (my_mod);
852 data = grub_hfsplus_mount (disk);
853 if (data)
855 *uuid = grub_malloc (16 + sizeof ('\0'));
856 grub_sprintf (*uuid, "%016llx",
857 (unsigned long long)
858 grub_be_to_cpu64 (data->volheader.num_serial));
860 else
861 *uuid = NULL;
863 grub_dl_unref (my_mod);
865 grub_free (data);
867 return grub_errno;
872 static struct grub_fs grub_hfsplus_fs =
874 .name = "hfsplus",
875 .dir = grub_hfsplus_dir,
876 .open = grub_hfsplus_open,
877 .read = grub_hfsplus_read,
878 .close = grub_hfsplus_close,
879 .label = grub_hfsplus_label,
880 .mtime = grub_hfsplus_mtime,
881 .uuid = grub_hfsplus_uuid,
882 .next = 0
885 GRUB_MOD_INIT(hfsplus)
887 grub_fs_register (&grub_hfsplus_fs);
888 my_mod = mod;
891 GRUB_MOD_FINI(hfsplus)
893 grub_fs_unregister (&grub_hfsplus_fs);