123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128 |
- /* hfsplus.c - HFS+ Filesystem. */
- /*
- * GRUB -- GRand Unified Bootloader
- * Copyright (C) 2005,2006,2007,2008,2009 Free Software Foundation, Inc.
- *
- * GRUB is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * GRUB is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
- */
- /* HFS+ is documented at http://developer.apple.com/technotes/tn/tn1150.html */
- #define grub_fshelp_node grub_hfsplus_file
- #include <grub/err.h>
- #include <grub/file.h>
- #include <grub/mm.h>
- #include <grub/misc.h>
- #include <grub/disk.h>
- #include <grub/dl.h>
- #include <grub/types.h>
- #include <grub/fshelp.h>
- #include <grub/hfs.h>
- #include <grub/charset.h>
- #include <grub/hfsplus.h>
- GRUB_MOD_LICENSE ("GPLv3+");
- /* The type of node. */
- enum grub_hfsplus_btnode_type
- {
- GRUB_HFSPLUS_BTNODE_TYPE_LEAF = -1,
- GRUB_HFSPLUS_BTNODE_TYPE_INDEX = 0,
- GRUB_HFSPLUS_BTNODE_TYPE_HEADER = 1,
- GRUB_HFSPLUS_BTNODE_TYPE_MAP = 2,
- };
- /* The header of a HFS+ B+ Tree. */
- struct grub_hfsplus_btheader
- {
- grub_uint16_t depth;
- grub_uint32_t root;
- grub_uint32_t leaf_records;
- grub_uint32_t first_leaf_node;
- grub_uint32_t last_leaf_node;
- grub_uint16_t nodesize;
- grub_uint16_t keysize;
- grub_uint32_t total_nodes;
- grub_uint32_t free_nodes;
- grub_uint16_t reserved1;
- grub_uint32_t clump_size; /* ignored */
- grub_uint8_t btree_type;
- grub_uint8_t key_compare;
- grub_uint32_t attributes;
- } GRUB_PACKED;
- struct grub_hfsplus_catfile
- {
- grub_uint16_t type;
- grub_uint16_t flags;
- grub_uint32_t parentid; /* Thread only. */
- grub_uint32_t fileid;
- grub_uint8_t unused1[4];
- grub_uint32_t mtime;
- grub_uint8_t unused2[22];
- grub_uint16_t mode;
- grub_uint8_t unused3[44];
- struct grub_hfsplus_forkdata data;
- struct grub_hfsplus_forkdata resource;
- } GRUB_PACKED;
- /* Filetype information as used in inodes. */
- #define GRUB_HFSPLUS_FILEMODE_MASK 0170000
- #define GRUB_HFSPLUS_FILEMODE_REG 0100000
- #define GRUB_HFSPLUS_FILEMODE_DIRECTORY 0040000
- #define GRUB_HFSPLUS_FILEMODE_SYMLINK 0120000
- /* Some pre-defined file IDs. */
- enum
- {
- GRUB_HFSPLUS_FILEID_ROOTDIR = 2,
- GRUB_HFSPLUS_FILEID_OVERFLOW = 3,
- GRUB_HFSPLUS_FILEID_CATALOG = 4,
- GRUB_HFSPLUS_FILEID_ATTR = 8
- };
- enum grub_hfsplus_filetype
- {
- GRUB_HFSPLUS_FILETYPE_DIR = 1,
- GRUB_HFSPLUS_FILETYPE_REG = 2,
- GRUB_HFSPLUS_FILETYPE_DIR_THREAD = 3,
- GRUB_HFSPLUS_FILETYPE_REG_THREAD = 4
- };
- #define GRUB_HFSPLUSX_BINARYCOMPARE 0xBC
- #define GRUB_HFSPLUSX_CASEFOLDING 0xCF
- static grub_dl_t my_mod;
- grub_err_t (*grub_hfsplus_open_compressed) (struct grub_fshelp_node *node);
- grub_ssize_t (*grub_hfsplus_read_compressed) (struct grub_hfsplus_file *node,
- grub_off_t pos,
- grub_size_t len,
- char *buf);
- /* Find the extent that points to FILEBLOCK. If it is not in one of
- the 8 extents described by EXTENT, return -1. In that case set
- FILEBLOCK to the next block. */
- static grub_disk_addr_t
- grub_hfsplus_find_block (struct grub_hfsplus_extent *extent,
- grub_disk_addr_t *fileblock)
- {
- int i;
- grub_disk_addr_t blksleft = *fileblock;
- /* First lookup the file in the given extents. */
- for (i = 0; i < 8; i++)
- {
- if (blksleft < grub_be_to_cpu32 (extent[i].count))
- return grub_be_to_cpu32 (extent[i].start) + blksleft;
- blksleft -= grub_be_to_cpu32 (extent[i].count);
- }
- *fileblock = blksleft;
- return 0xffffffffffffffffULL;
- }
- static int grub_hfsplus_cmp_extkey (struct grub_hfsplus_key *keya,
- struct grub_hfsplus_key_internal *keyb);
- /* Search for the block FILEBLOCK inside the file NODE. Return the
- blocknumber of this block on disk. */
- static grub_disk_addr_t
- grub_hfsplus_read_block (grub_fshelp_node_t node, grub_disk_addr_t fileblock)
- {
- struct grub_hfsplus_btnode *nnode = 0;
- grub_disk_addr_t blksleft = fileblock;
- struct grub_hfsplus_extent *extents = node->compressed
- ? &node->resource_extents[0] : &node->extents[0];
- while (1)
- {
- struct grub_hfsplus_extkey *key;
- struct grub_hfsplus_key_internal extoverflow;
- grub_disk_addr_t blk;
- grub_off_t ptr;
- /* Try to find this block in the current set of extents. */
- blk = grub_hfsplus_find_block (extents, &blksleft);
- /* The previous iteration of this loop allocated memory. The
- code above used this memory, it can be freed now. */
- grub_free (nnode);
- nnode = 0;
- if (blk != 0xffffffffffffffffULL)
- return blk;
- /* For the extent overflow file, extra extents can't be found in
- the extent overflow file. If this happens, you found a
- bug... */
- if (node->fileid == GRUB_HFSPLUS_FILEID_OVERFLOW)
- {
- grub_error (GRUB_ERR_READ_ERROR,
- "extra extents found in an extend overflow file");
- break;
- }
- /* Set up the key to look for in the extent overflow file. */
- extoverflow.extkey.fileid = node->fileid;
- extoverflow.extkey.type = 0;
- extoverflow.extkey.start = fileblock - blksleft;
- extoverflow.extkey.type = node->compressed ? 0xff : 0;
- if (grub_hfsplus_btree_search (&node->data->extoverflow_tree,
- &extoverflow,
- grub_hfsplus_cmp_extkey, &nnode, &ptr)
- || !nnode)
- {
- grub_error (GRUB_ERR_READ_ERROR,
- "no block found for the file id 0x%x and the block offset 0x%x",
- node->fileid, fileblock);
- break;
- }
- /* The extent overflow file has 8 extents right after the key. */
- key = (struct grub_hfsplus_extkey *)
- grub_hfsplus_btree_recptr (&node->data->extoverflow_tree, nnode, ptr);
- extents = (struct grub_hfsplus_extent *) (key + 1);
- /* The block wasn't found. Perhaps the next iteration will find
- it. The last block we found is stored in BLKSLEFT now. */
- }
- grub_free (nnode);
- /* Too bad, you lose. */
- return -1;
- }
- /* Read LEN bytes from the file described by DATA starting with byte
- POS. Return the amount of read bytes in READ. */
- grub_ssize_t
- grub_hfsplus_read_file (grub_fshelp_node_t node,
- grub_disk_read_hook_t read_hook, void *read_hook_data,
- grub_off_t pos, grub_size_t len, char *buf)
- {
- return grub_fshelp_read_file (node->data->disk, node,
- read_hook, read_hook_data,
- pos, len, buf, grub_hfsplus_read_block,
- node->size,
- node->data->log2blksize - GRUB_DISK_SECTOR_BITS,
- node->data->embedded_offset);
- }
- static struct grub_hfsplus_data *
- grub_hfsplus_mount (grub_disk_t disk)
- {
- struct grub_hfsplus_data *data;
- struct grub_hfsplus_btheader header;
- struct grub_hfsplus_btnode node;
- grub_uint16_t magic;
- union {
- struct grub_hfs_sblock hfs;
- struct grub_hfsplus_volheader hfsplus;
- } volheader;
- data = grub_malloc (sizeof (*data));
- if (!data)
- return 0;
- data->disk = disk;
- /* Read the bootblock. */
- grub_disk_read (disk, GRUB_HFSPLUS_SBLOCK, 0, sizeof (volheader),
- &volheader);
- if (grub_errno)
- goto fail;
- data->embedded_offset = 0;
- if (grub_be_to_cpu16 (volheader.hfs.magic) == GRUB_HFS_MAGIC)
- {
- grub_disk_addr_t extent_start;
- grub_disk_addr_t ablk_size;
- grub_disk_addr_t ablk_start;
- /* See if there's an embedded HFS+ filesystem. */
- if (grub_be_to_cpu16 (volheader.hfs.embed_sig) != GRUB_HFSPLUS_MAGIC)
- {
- grub_error (GRUB_ERR_BAD_FS, "not a HFS+ filesystem");
- goto fail;
- }
- /* Calculate the offset needed to translate HFS+ sector numbers. */
- extent_start = grub_be_to_cpu16 (volheader.hfs.embed_extent.first_block);
- ablk_size = grub_be_to_cpu32 (volheader.hfs.blksz);
- ablk_start = grub_be_to_cpu16 (volheader.hfs.first_block);
- data->embedded_offset = (ablk_start
- + extent_start
- * (ablk_size >> GRUB_DISK_SECTOR_BITS));
- grub_disk_read (disk, data->embedded_offset + GRUB_HFSPLUS_SBLOCK, 0,
- sizeof (volheader), &volheader);
- if (grub_errno)
- goto fail;
- }
- /* Make sure this is an HFS+ filesystem. XXX: Do we really support
- HFX? */
- magic = grub_be_to_cpu16 (volheader.hfsplus.magic);
- if (((magic != GRUB_HFSPLUS_MAGIC) && (magic != GRUB_HFSPLUSX_MAGIC))
- || volheader.hfsplus.blksize == 0
- || ((volheader.hfsplus.blksize & (volheader.hfsplus.blksize - 1)) != 0)
- || grub_be_to_cpu32 (volheader.hfsplus.blksize) < GRUB_DISK_SECTOR_SIZE)
- {
- grub_error (GRUB_ERR_BAD_FS, "not a HFS+ filesystem");
- goto fail;
- }
- grub_memcpy (&data->volheader, &volheader.hfsplus,
- sizeof (volheader.hfsplus));
- for (data->log2blksize = 0;
- (1U << data->log2blksize) < grub_be_to_cpu32 (data->volheader.blksize);
- data->log2blksize++);
- /* Make a new node for the catalog tree. */
- data->catalog_tree.file.data = data;
- data->catalog_tree.file.fileid = GRUB_HFSPLUS_FILEID_CATALOG;
- data->catalog_tree.file.compressed = 0;
- grub_memcpy (&data->catalog_tree.file.extents,
- data->volheader.catalog_file.extents,
- sizeof data->volheader.catalog_file.extents);
- data->catalog_tree.file.size =
- grub_be_to_cpu64 (data->volheader.catalog_file.size);
- data->attr_tree.file.data = data;
- data->attr_tree.file.fileid = GRUB_HFSPLUS_FILEID_ATTR;
- grub_memcpy (&data->attr_tree.file.extents,
- data->volheader.attr_file.extents,
- sizeof data->volheader.attr_file.extents);
- data->attr_tree.file.size =
- grub_be_to_cpu64 (data->volheader.attr_file.size);
- data->attr_tree.file.compressed = 0;
- /* Make a new node for the extent overflow file. */
- data->extoverflow_tree.file.data = data;
- data->extoverflow_tree.file.fileid = GRUB_HFSPLUS_FILEID_OVERFLOW;
- data->extoverflow_tree.file.compressed = 0;
- grub_memcpy (&data->extoverflow_tree.file.extents,
- data->volheader.extents_file.extents,
- sizeof data->volheader.catalog_file.extents);
- data->extoverflow_tree.file.size =
- grub_be_to_cpu64 (data->volheader.extents_file.size);
- /* Read the essential information about the trees. */
- if (grub_hfsplus_read_file (&data->catalog_tree.file, 0, 0,
- sizeof (struct grub_hfsplus_btnode),
- sizeof (header), (char *) &header) <= 0)
- goto fail;
- data->catalog_tree.root = grub_be_to_cpu32 (header.root);
- data->catalog_tree.nodesize = grub_be_to_cpu16 (header.nodesize);
- data->case_sensitive = ((magic == GRUB_HFSPLUSX_MAGIC) &&
- (header.key_compare == GRUB_HFSPLUSX_BINARYCOMPARE));
- if (data->catalog_tree.nodesize < 2)
- goto fail;
- if (grub_hfsplus_read_file (&data->extoverflow_tree.file, 0, 0,
- sizeof (struct grub_hfsplus_btnode),
- sizeof (header), (char *) &header) <= 0)
- goto fail;
- data->extoverflow_tree.root = grub_be_to_cpu32 (header.root);
- if (grub_hfsplus_read_file (&data->extoverflow_tree.file, 0, 0, 0,
- sizeof (node), (char *) &node) <= 0)
- goto fail;
- data->extoverflow_tree.root = grub_be_to_cpu32 (header.root);
- data->extoverflow_tree.nodesize = grub_be_to_cpu16 (header.nodesize);
- if (data->extoverflow_tree.nodesize < 2)
- goto fail;
- if (grub_hfsplus_read_file (&data->attr_tree.file, 0, 0,
- sizeof (struct grub_hfsplus_btnode),
- sizeof (header), (char *) &header) <= 0)
- {
- grub_errno = 0;
- data->attr_tree.root = 0;
- data->attr_tree.nodesize = 0;
- }
- else
- {
- data->attr_tree.root = grub_be_to_cpu32 (header.root);
- data->attr_tree.nodesize = grub_be_to_cpu16 (header.nodesize);
- }
- data->dirroot.data = data;
- data->dirroot.fileid = GRUB_HFSPLUS_FILEID_ROOTDIR;
- return data;
- fail:
- if (grub_errno == GRUB_ERR_OUT_OF_RANGE)
- grub_error (GRUB_ERR_BAD_FS, "not a HFS+ filesystem");
- grub_free (data);
- return 0;
- }
- /* Compare the on disk catalog key KEYA with the catalog key we are
- looking for (KEYB). */
- static int
- grub_hfsplus_cmp_catkey (struct grub_hfsplus_key *keya,
- struct grub_hfsplus_key_internal *keyb)
- {
- struct grub_hfsplus_catkey *catkey_a = &keya->catkey;
- struct grub_hfsplus_catkey_internal *catkey_b = &keyb->catkey;
- int diff;
- grub_size_t len;
- /* Safe unsigned comparison */
- grub_uint32_t aparent = grub_be_to_cpu32 (catkey_a->parent);
- if (aparent > catkey_b->parent)
- return 1;
- if (aparent < catkey_b->parent)
- return -1;
- len = grub_be_to_cpu16 (catkey_a->namelen);
- if (len > catkey_b->namelen)
- len = catkey_b->namelen;
- /* Since it's big-endian memcmp gives the same result as manually comparing
- uint16_t but may be faster. */
- diff = grub_memcmp (catkey_a->name, catkey_b->name,
- len * sizeof (catkey_a->name[0]));
- if (diff == 0)
- diff = grub_be_to_cpu16 (catkey_a->namelen) - catkey_b->namelen;
- return diff;
- }
- /* Compare the on disk catalog key KEYA with the catalog key we are
- looking for (KEYB). */
- static int
- grub_hfsplus_cmp_catkey_id (struct grub_hfsplus_key *keya,
- struct grub_hfsplus_key_internal *keyb)
- {
- struct grub_hfsplus_catkey *catkey_a = &keya->catkey;
- struct grub_hfsplus_catkey_internal *catkey_b = &keyb->catkey;
- /* Safe unsigned comparison */
- grub_uint32_t aparent = grub_be_to_cpu32 (catkey_a->parent);
- if (aparent > catkey_b->parent)
- return 1;
- if (aparent < catkey_b->parent)
- return -1;
- return 0;
- }
- /* Compare the on disk extent overflow key KEYA with the extent
- overflow key we are looking for (KEYB). */
- static int
- grub_hfsplus_cmp_extkey (struct grub_hfsplus_key *keya,
- struct grub_hfsplus_key_internal *keyb)
- {
- struct grub_hfsplus_extkey *extkey_a = &keya->extkey;
- struct grub_hfsplus_extkey_internal *extkey_b = &keyb->extkey;
- grub_uint32_t akey;
- /* Safe unsigned comparison */
- akey = grub_be_to_cpu32 (extkey_a->fileid);
- if (akey > extkey_b->fileid)
- return 1;
- if (akey < extkey_b->fileid)
- return -1;
- if (extkey_a->type > extkey_b->type)
- return 1;
- if (extkey_a->type < extkey_b->type)
- return -1;
- if (extkey_a->type > extkey_b->type)
- return +1;
- if (extkey_a->type < extkey_b->type)
- return -1;
-
- akey = grub_be_to_cpu32 (extkey_a->start);
- if (akey > extkey_b->start)
- return 1;
- if (akey < extkey_b->start)
- return -1;
- return 0;
- }
- static char *
- grub_hfsplus_read_symlink (grub_fshelp_node_t node)
- {
- char *symlink;
- grub_ssize_t numread;
- symlink = grub_malloc (node->size + 1);
- if (!symlink)
- return 0;
- numread = grub_hfsplus_read_file (node, 0, 0, 0, node->size, symlink);
- if (numread != (grub_ssize_t) node->size)
- {
- grub_free (symlink);
- return 0;
- }
- symlink[node->size] = '\0';
- return symlink;
- }
- static int
- grub_hfsplus_btree_iterate_node (struct grub_hfsplus_btree *btree,
- struct grub_hfsplus_btnode *first_node,
- grub_disk_addr_t first_rec,
- int (*hook) (void *record, void *hook_arg),
- void *hook_arg)
- {
- grub_disk_addr_t rec;
- grub_uint64_t saved_node = -1;
- grub_uint64_t node_count = 0;
- for (;;)
- {
- char *cnode = (char *) first_node;
- /* Iterate over all records in this node. */
- for (rec = first_rec; rec < grub_be_to_cpu16 (first_node->count); rec++)
- {
- if (hook (grub_hfsplus_btree_recptr (btree, first_node, rec), hook_arg))
- return 1;
- }
- if (! first_node->next)
- break;
- if (node_count && first_node->next == saved_node)
- {
- grub_error (GRUB_ERR_BAD_FS, "HFS+ btree loop");
- return 0;
- }
- if (!(node_count & (node_count - 1)))
- saved_node = first_node->next;
- node_count++;
- if (grub_hfsplus_read_file (&btree->file, 0, 0,
- (((grub_disk_addr_t)
- grub_be_to_cpu32 (first_node->next))
- * btree->nodesize),
- btree->nodesize, cnode) <= 0)
- return 1;
- /* Don't skip any record in the next iteration. */
- first_rec = 0;
- }
- return 0;
- }
- /* Lookup the node described by KEY in the B+ Tree BTREE. Compare
- keys using the function COMPARE_KEYS. When a match is found,
- return the node in MATCHNODE and a pointer to the data in this node
- in KEYOFFSET. MATCHNODE should be freed by the caller. */
- grub_err_t
- grub_hfsplus_btree_search (struct grub_hfsplus_btree *btree,
- struct grub_hfsplus_key_internal *key,
- int (*compare_keys) (struct grub_hfsplus_key *keya,
- struct grub_hfsplus_key_internal *keyb),
- struct grub_hfsplus_btnode **matchnode,
- grub_off_t *keyoffset)
- {
- grub_uint64_t currnode;
- char *node;
- struct grub_hfsplus_btnode *nodedesc;
- grub_disk_addr_t rec;
- grub_uint64_t save_node;
- grub_uint64_t node_count = 0;
- if (!btree->nodesize)
- {
- *matchnode = 0;
- return 0;
- }
- node = grub_malloc (btree->nodesize);
- if (! node)
- return grub_errno;
- currnode = btree->root;
- save_node = currnode - 1;
- while (1)
- {
- int match = 0;
- if (save_node == currnode)
- {
- grub_free (node);
- return grub_error (GRUB_ERR_BAD_FS, "HFS+ btree loop");
- }
- if (!(node_count & (node_count - 1)))
- save_node = currnode;
- node_count++;
- /* Read a node. */
- if (grub_hfsplus_read_file (&btree->file, 0, 0,
- (grub_disk_addr_t) currnode
- * (grub_disk_addr_t) btree->nodesize,
- btree->nodesize, (char *) node) <= 0)
- {
- grub_free (node);
- return grub_error (GRUB_ERR_BAD_FS, "couldn't read i-node");
- }
- nodedesc = (struct grub_hfsplus_btnode *) node;
- /* Find the record in this tree. */
- for (rec = 0; rec < grub_be_to_cpu16 (nodedesc->count); rec++)
- {
- struct grub_hfsplus_key *currkey;
- currkey = grub_hfsplus_btree_recptr (btree, nodedesc, rec);
- /* The action that has to be taken depend on the type of
- record. */
- if (nodedesc->type == GRUB_HFSPLUS_BTNODE_TYPE_LEAF
- && compare_keys (currkey, key) == 0)
- {
- /* An exact match was found! */
- *matchnode = nodedesc;
- *keyoffset = rec;
- return 0;
- }
- else if (nodedesc->type == GRUB_HFSPLUS_BTNODE_TYPE_INDEX)
- {
- void *pointer;
- /* The place where the key could have been found didn't
- contain the key. This means that the previous match
- is the one that should be followed. */
- if (compare_keys (currkey, key) > 0)
- break;
- /* Mark the last key which is lower or equal to the key
- that we are looking for. The last match that is
- found will be used to locate the child which can
- contain the record. */
- pointer = ((char *) currkey
- + grub_be_to_cpu16 (currkey->keylen)
- + 2);
- currnode = grub_be_to_cpu32 (grub_get_unaligned32 (pointer));
- match = 1;
- }
- }
- /* No match is found, no record with this key exists in the
- tree. */
- if (! match)
- {
- *matchnode = 0;
- grub_free (node);
- return 0;
- }
- }
- }
- struct list_nodes_ctx
- {
- int ret;
- grub_fshelp_node_t dir;
- grub_fshelp_iterate_dir_hook_t hook;
- void *hook_data;
- };
- static int
- list_nodes (void *record, void *hook_arg)
- {
- struct grub_hfsplus_catkey *catkey;
- char *filename;
- int i;
- struct grub_fshelp_node *node;
- grub_uint16_t *keyname;
- struct grub_hfsplus_catfile *fileinfo;
- enum grub_fshelp_filetype type = GRUB_FSHELP_UNKNOWN;
- struct list_nodes_ctx *ctx = hook_arg;
- catkey = (struct grub_hfsplus_catkey *) record;
- fileinfo =
- (struct grub_hfsplus_catfile *) ((char *) record
- + grub_be_to_cpu16 (catkey->keylen)
- + 2 + (grub_be_to_cpu16(catkey->keylen)
- % 2));
- /* Stop iterating when the last directory entry is found. */
- if (grub_be_to_cpu32 (catkey->parent) != ctx->dir->fileid)
- return 1;
- /* Determine the type of the node that is found. */
- switch (fileinfo->type)
- {
- case grub_cpu_to_be16_compile_time (GRUB_HFSPLUS_FILETYPE_REG):
- {
- int mode = (grub_be_to_cpu16 (fileinfo->mode)
- & GRUB_HFSPLUS_FILEMODE_MASK);
- if (mode == GRUB_HFSPLUS_FILEMODE_REG)
- type = GRUB_FSHELP_REG;
- else if (mode == GRUB_HFSPLUS_FILEMODE_SYMLINK)
- type = GRUB_FSHELP_SYMLINK;
- else
- type = GRUB_FSHELP_UNKNOWN;
- break;
- }
- case grub_cpu_to_be16_compile_time (GRUB_HFSPLUS_FILETYPE_DIR):
- type = GRUB_FSHELP_DIR;
- break;
- case grub_cpu_to_be16_compile_time (GRUB_HFSPLUS_FILETYPE_DIR_THREAD):
- if (ctx->dir->fileid == 2)
- return 0;
- node = grub_malloc (sizeof (*node));
- if (!node)
- return 1;
- node->data = ctx->dir->data;
- node->mtime = 0;
- node->size = 0;
- node->fileid = grub_be_to_cpu32 (fileinfo->parentid);
- ctx->ret = ctx->hook ("..", GRUB_FSHELP_DIR, node, ctx->hook_data);
- return ctx->ret;
- }
- if (type == GRUB_FSHELP_UNKNOWN)
- return 0;
- filename = grub_malloc (grub_be_to_cpu16 (catkey->namelen)
- * GRUB_MAX_UTF8_PER_UTF16 + 1);
- if (! filename)
- return 0;
- keyname = grub_malloc (grub_be_to_cpu16 (catkey->namelen) * sizeof (*keyname));
- if (!keyname)
- {
- grub_free (filename);
- return 0;
- }
- /* Make sure the byte order of the UTF16 string is correct. */
- for (i = 0; i < grub_be_to_cpu16 (catkey->namelen); i++)
- {
- keyname[i] = grub_be_to_cpu16 (catkey->name[i]);
- if (keyname[i] == '/')
- keyname[i] = ':';
- /* If the name is obviously invalid, skip this node. */
- if (keyname[i] == 0)
- {
- grub_free (keyname);
- grub_free (filename);
- return 0;
- }
- }
- *grub_utf16_to_utf8 ((grub_uint8_t *) filename, keyname,
- grub_be_to_cpu16 (catkey->namelen)) = '\0';
- grub_free (keyname);
- /* hfs+ is case insensitive. */
- if (! ctx->dir->data->case_sensitive)
- type |= GRUB_FSHELP_CASE_INSENSITIVE;
- /* A valid node is found; setup the node and call the
- callback function. */
- node = grub_malloc (sizeof (*node));
- if (!node)
- {
- grub_free (filename);
- return 1;
- }
- node->data = ctx->dir->data;
- node->compressed = 0;
- node->cbuf = 0;
- node->compress_index = 0;
- grub_memcpy (node->extents, fileinfo->data.extents,
- sizeof (node->extents));
- grub_memcpy (node->resource_extents, fileinfo->resource.extents,
- sizeof (node->resource_extents));
- node->mtime = grub_be_to_cpu32 (fileinfo->mtime) - 2082844800;
- node->size = grub_be_to_cpu64 (fileinfo->data.size);
- node->resource_size = grub_be_to_cpu64 (fileinfo->resource.size);
- node->fileid = grub_be_to_cpu32 (fileinfo->fileid);
- ctx->ret = ctx->hook (filename, type, node, ctx->hook_data);
- grub_free (filename);
- return ctx->ret;
- }
- static int
- grub_hfsplus_iterate_dir (grub_fshelp_node_t dir,
- grub_fshelp_iterate_dir_hook_t hook, void *hook_data)
- {
- struct list_nodes_ctx ctx =
- {
- .ret = 0,
- .dir = dir,
- .hook = hook,
- .hook_data = hook_data
- };
- struct grub_hfsplus_key_internal intern;
- struct grub_hfsplus_btnode *node = NULL;
- grub_disk_addr_t ptr = 0;
- {
- struct grub_fshelp_node *fsnode;
- fsnode = grub_malloc (sizeof (*fsnode));
- if (!fsnode)
- return 1;
- *fsnode = *dir;
- if (hook (".", GRUB_FSHELP_DIR, fsnode, hook_data))
- return 1;
- }
- /* Create a key that points to the first entry in the directory. */
- intern.catkey.parent = dir->fileid;
- intern.catkey.name = 0;
- intern.catkey.namelen = 0;
- /* First lookup the first entry. */
- if (grub_hfsplus_btree_search (&dir->data->catalog_tree, &intern,
- grub_hfsplus_cmp_catkey, &node, &ptr)
- || !node)
- return 0;
- /* Iterate over all entries in this directory. */
- grub_hfsplus_btree_iterate_node (&dir->data->catalog_tree, node, ptr,
- list_nodes, &ctx);
- grub_free (node);
- return ctx.ret;
- }
- /* Open a file named NAME and initialize FILE. */
- static grub_err_t
- grub_hfsplus_open (struct grub_file *file, const char *name)
- {
- struct grub_hfsplus_data *data;
- struct grub_fshelp_node *fdiro = 0;
- grub_dl_ref (my_mod);
- data = grub_hfsplus_mount (file->device->disk);
- if (!data)
- goto fail;
- grub_fshelp_find_file (name, &data->dirroot, &fdiro,
- grub_hfsplus_iterate_dir,
- grub_hfsplus_read_symlink, GRUB_FSHELP_REG);
- if (grub_errno)
- goto fail;
- if (grub_hfsplus_open_compressed)
- {
- grub_err_t err;
- err = grub_hfsplus_open_compressed (fdiro);
- if (err)
- goto fail;
- }
- file->size = fdiro->size;
- data->opened_file = *fdiro;
- grub_free (fdiro);
- file->data = data;
- file->offset = 0;
- return 0;
- fail:
- if (data && fdiro != &data->dirroot)
- grub_free (fdiro);
- grub_free (data);
- grub_dl_unref (my_mod);
- return grub_errno;
- }
- static grub_err_t
- grub_hfsplus_close (grub_file_t file)
- {
- struct grub_hfsplus_data *data =
- (struct grub_hfsplus_data *) file->data;
- grub_free (data->opened_file.cbuf);
- grub_free (data->opened_file.compress_index);
- grub_free (data);
- grub_dl_unref (my_mod);
- return GRUB_ERR_NONE;
- }
- /* Read LEN bytes data from FILE into BUF. */
- static grub_ssize_t
- grub_hfsplus_read (grub_file_t file, char *buf, grub_size_t len)
- {
- struct grub_hfsplus_data *data =
- (struct grub_hfsplus_data *) file->data;
- data->opened_file.file = file;
- if (grub_hfsplus_read_compressed && data->opened_file.compressed)
- return grub_hfsplus_read_compressed (&data->opened_file,
- file->offset, len, buf);
- return grub_hfsplus_read_file (&data->opened_file,
- file->read_hook, file->read_hook_data,
- file->offset, len, buf);
- }
- /* Context for grub_hfsplus_dir. */
- struct grub_hfsplus_dir_ctx
- {
- grub_fs_dir_hook_t hook;
- void *hook_data;
- };
- /* Helper for grub_hfsplus_dir. */
- static int
- grub_hfsplus_dir_iter (const char *filename,
- enum grub_fshelp_filetype filetype,
- grub_fshelp_node_t node, void *data)
- {
- struct grub_hfsplus_dir_ctx *ctx = data;
- struct grub_dirhook_info info;
- grub_memset (&info, 0, sizeof (info));
- info.dir = ((filetype & GRUB_FSHELP_TYPE_MASK) == GRUB_FSHELP_DIR);
- info.mtimeset = 1;
- info.mtime = node->mtime;
- info.inodeset = 1;
- info.inode = node->fileid;
- info.case_insensitive = !! (filetype & GRUB_FSHELP_CASE_INSENSITIVE);
- grub_free (node);
- return ctx->hook (filename, &info, ctx->hook_data);
- }
- static grub_err_t
- grub_hfsplus_dir (grub_device_t device, const char *path,
- grub_fs_dir_hook_t hook, void *hook_data)
- {
- struct grub_hfsplus_dir_ctx ctx = { hook, hook_data };
- struct grub_hfsplus_data *data = 0;
- struct grub_fshelp_node *fdiro = 0;
- grub_dl_ref (my_mod);
- data = grub_hfsplus_mount (device->disk);
- if (!data)
- goto fail;
- /* Find the directory that should be opened. */
- grub_fshelp_find_file (path, &data->dirroot, &fdiro,
- grub_hfsplus_iterate_dir,
- grub_hfsplus_read_symlink, GRUB_FSHELP_DIR);
- if (grub_errno)
- goto fail;
- /* Iterate over all entries in this directory. */
- grub_hfsplus_iterate_dir (fdiro, grub_hfsplus_dir_iter, &ctx);
- fail:
- if (data && fdiro != &data->dirroot)
- grub_free (fdiro);
- grub_free (data);
- grub_dl_unref (my_mod);
- return grub_errno;
- }
- static grub_err_t
- grub_hfsplus_label (grub_device_t device, char **label)
- {
- struct grub_hfsplus_data *data;
- grub_disk_t disk = device->disk;
- struct grub_hfsplus_catkey *catkey;
- int i, label_len;
- grub_uint16_t *label_name;
- struct grub_hfsplus_key_internal intern;
- struct grub_hfsplus_btnode *node = NULL;
- grub_disk_addr_t ptr = 0;
- *label = 0;
- data = grub_hfsplus_mount (disk);
- if (!data)
- return grub_errno;
- /* Create a key that points to the label. */
- intern.catkey.parent = 1;
- intern.catkey.name = 0;
- intern.catkey.namelen = 0;
- /* First lookup the first entry. */
- if (grub_hfsplus_btree_search (&data->catalog_tree, &intern,
- grub_hfsplus_cmp_catkey_id, &node, &ptr)
- || !node)
- {
- grub_free (data);
- return 0;
- }
- catkey = (struct grub_hfsplus_catkey *)
- grub_hfsplus_btree_recptr (&data->catalog_tree, node, ptr);
- label_len = grub_be_to_cpu16 (catkey->namelen);
- label_name = grub_malloc (label_len * sizeof (*label_name));
- if (!label_name)
- {
- grub_free (node);
- grub_free (data);
- return grub_errno;
- }
- for (i = 0; i < label_len; i++)
- {
- label_name[i] = grub_be_to_cpu16 (catkey->name[i]);
- /* If the name is obviously invalid, skip this node. */
- if (label_name[i] == 0)
- {
- grub_free (label_name);
- grub_free (node);
- grub_free (data);
- return 0;
- }
- }
- *label = grub_malloc (label_len * GRUB_MAX_UTF8_PER_UTF16 + 1);
- if (! *label)
- {
- grub_free (label_name);
- grub_free (node);
- grub_free (data);
- return grub_errno;
- }
- *grub_utf16_to_utf8 ((grub_uint8_t *) (*label), label_name,
- label_len) = '\0';
- grub_free (label_name);
- grub_free (node);
- grub_free (data);
- return GRUB_ERR_NONE;
- }
- /* Get mtime. */
- static grub_err_t
- grub_hfsplus_mtime (grub_device_t device, grub_int32_t *tm)
- {
- struct grub_hfsplus_data *data;
- grub_disk_t disk = device->disk;
- grub_dl_ref (my_mod);
- data = grub_hfsplus_mount (disk);
- if (!data)
- *tm = 0;
- else
- *tm = grub_be_to_cpu32 (data->volheader.utime) - 2082844800;
- grub_dl_unref (my_mod);
- grub_free (data);
- return grub_errno;
- }
- static grub_err_t
- grub_hfsplus_uuid (grub_device_t device, char **uuid)
- {
- struct grub_hfsplus_data *data;
- grub_disk_t disk = device->disk;
- grub_dl_ref (my_mod);
- data = grub_hfsplus_mount (disk);
- if (data)
- {
- *uuid = grub_xasprintf ("%016llx",
- (unsigned long long)
- grub_be_to_cpu64 (data->volheader.num_serial));
- }
- else
- *uuid = NULL;
- grub_dl_unref (my_mod);
- grub_free (data);
- return grub_errno;
- }
- static struct grub_fs grub_hfsplus_fs =
- {
- .name = "hfsplus",
- .fs_dir = grub_hfsplus_dir,
- .fs_open = grub_hfsplus_open,
- .fs_read = grub_hfsplus_read,
- .fs_close = grub_hfsplus_close,
- .fs_label = grub_hfsplus_label,
- .fs_mtime = grub_hfsplus_mtime,
- .fs_uuid = grub_hfsplus_uuid,
- #ifdef GRUB_UTIL
- .reserved_first_sector = 1,
- .blocklist_install = 1,
- #endif
- .next = 0
- };
- GRUB_MOD_INIT(hfsplus)
- {
- grub_fs_register (&grub_hfsplus_fs);
- my_mod = mod;
- }
- GRUB_MOD_FINI(hfsplus)
- {
- grub_fs_unregister (&grub_hfsplus_fs);
- }
|