12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187 |
- /* 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>
- #include <grub/safemath.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
- #define HFSPLUS_BTNODE_MINSZ (1 << 9)
- #define HFSPLUS_BTNODE_MAXSZ (1 << 15)
- #define HFSPLUS_CATKEY_MIN_LEN 6
- #define HFSPLUS_CATKEY_MAX_LEN 516
- /* 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;
- }
- /*
- * If the extent overflow tree isn't ready yet, we can't look
- * in it. This can happen where the catalog file is corrupted.
- */
- if (!node->data->extoverflow_tree_ready)
- {
- grub_error (GRUB_ERR_BAD_FS,
- "attempted to read extent overflow tree before loading");
- 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%" PRIuGRUB_UINT64_T,
- 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;
- data->extoverflow_tree_ready = 0;
- /* 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)
- {
- grub_error (GRUB_ERR_BAD_FS, "invalid catalog node size");
- 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)
- {
- grub_error (GRUB_ERR_BAD_FS, "invalid extents overflow node size");
- goto fail;
- }
- data->extoverflow_tree_ready = 1;
- 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;
- grub_size_t sz = node->size;
- if (grub_add (sz, 1, &sz))
- return NULL;
- symlink = grub_malloc (sz);
- 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;
- }
- if (btree->nodesize < HFSPLUS_BTNODE_MINSZ ||
- btree->nodesize > HFSPLUS_BTNODE_MAXSZ)
- return grub_error (GRUB_ERR_BAD_FS, "invalid HFS+ btree node size");
- 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);
- if ((char *) pointer > node + btree->nodesize - 2)
- {
- grub_free (node);
- return grub_error (GRUB_ERR_BAD_FS, "HFS+ key beyond end of node");
- }
- 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;
- if (grub_be_to_cpu16 (catkey->keylen) < HFSPLUS_CATKEY_MIN_LEN ||
- grub_be_to_cpu16 (catkey->keylen) > HFSPLUS_CATKEY_MAX_LEN)
- {
- grub_error (GRUB_ERR_BAD_FS, "catalog key length is out of range");
- return 1;
- }
- 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_calloc (grub_be_to_cpu16 (catkey->namelen),
- GRUB_MAX_UTF8_PER_UTF16 + 1);
- if (! filename)
- return 0;
- keyname = grub_calloc (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);
- /* Ensure that the length is >= 0. */
- if (label_len < 0)
- label_len = 0;
- /* Ensure label length is at most 255 Unicode characters. */
- if (label_len > 255)
- label_len = 255;
- label_name = grub_calloc (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_calloc (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_int64_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);
- }
|