diff --git a/ChangeLog b/ChangeLog index 8b7775977..04302497f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +2013-05-07 Vladimir Serbinenko + + Compressed HFS+ support. + 2013-05-07 Vladimir Serbinenko * grub-core/commands/videoinfo.c: Use "paletted" rather than "packed diff --git a/Makefile.util.def b/Makefile.util.def index 588831421..dc621db20 100644 --- a/Makefile.util.def +++ b/Makefile.util.def @@ -85,6 +85,7 @@ library = { common = grub-core/fs/fshelp.c; common = grub-core/fs/hfs.c; common = grub-core/fs/hfsplus.c; + common = grub-core/fs/hfspluscomp.c; common = grub-core/fs/iso9660.c; common = grub-core/fs/jfs.c; common = grub-core/fs/minix.c; diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def index 7dc106ee1..bb62dce50 100644 --- a/grub-core/Makefile.core.def +++ b/grub-core/Makefile.core.def @@ -1185,6 +1185,11 @@ module = { common = fs/hfsplus.c; }; +module = { + name = hfspluscomp; + common = fs/hfspluscomp.c; +}; + module = { name = iso9660; common = fs/iso9660.c; diff --git a/grub-core/fs/hfsplus.c b/grub-core/fs/hfsplus.c index a507c0f1a..790562450 100644 --- a/grub-core/fs/hfsplus.c +++ b/grub-core/fs/hfsplus.c @@ -19,6 +19,7 @@ /* HFS+ is documented at http://developer.apple.com/technotes/tn/tn1150.html */ +#define grub_fshelp_node grub_hfsplus_file #include #include #include @@ -29,6 +30,7 @@ #include #include #include +#include GRUB_MOD_LICENSE ("GPLv3+"); @@ -36,42 +38,6 @@ GRUB_MOD_LICENSE ("GPLv3+"); #define GRUB_HFSPLUSX_MAGIC 0x4858 #define GRUB_HFSPLUS_SBLOCK 2 -/* A HFS+ extent. */ -struct grub_hfsplus_extent -{ - /* The first block of a file on disk. */ - grub_uint32_t start; - /* The amount of blocks described by this extent. */ - grub_uint32_t count; -} __attribute__ ((packed)); - -/* The descriptor of a fork. */ -struct grub_hfsplus_forkdata -{ - grub_uint64_t size; - grub_uint32_t clumpsize; - grub_uint32_t blocks; - struct grub_hfsplus_extent extents[8]; -} __attribute__ ((packed)); - -/* The HFS+ Volume Header. */ -struct grub_hfsplus_volheader -{ - grub_uint16_t magic; - grub_uint16_t version; - grub_uint32_t attributes; - grub_uint8_t unused1[12]; - grub_uint32_t utime; - grub_uint8_t unused2[16]; - grub_uint32_t blksize; - grub_uint8_t unused3[60]; - grub_uint64_t num_serial; - struct grub_hfsplus_forkdata allocations_file; - struct grub_hfsplus_forkdata extents_file; - struct grub_hfsplus_forkdata catalog_file; - struct grub_hfsplus_forkdata attrib_file; - struct grub_hfsplus_forkdata startup_file; -} __attribute__ ((packed)); /* The type of node. */ enum grub_hfsplus_btnode_type @@ -82,16 +48,6 @@ enum grub_hfsplus_btnode_type GRUB_HFSPLUS_BTNODE_TYPE_MAP = 2, }; -struct grub_hfsplus_btnode -{ - grub_uint32_t next; - grub_uint32_t prev; - grub_int8_t type; - grub_uint8_t height; - grub_uint16_t count; - grub_uint16_t unused; -} __attribute__ ((packed)); - /* The header of a HFS+ B+ Tree. */ struct grub_hfsplus_btheader { @@ -111,35 +67,6 @@ struct grub_hfsplus_btheader grub_uint32_t attributes; } __attribute__ ((packed)); -/* The on disk layout of a catalog key. */ -struct grub_hfsplus_catkey -{ - grub_uint16_t keylen; - grub_uint32_t parent; - grub_uint16_t namelen; - grub_uint16_t name[30]; -} __attribute__ ((packed)); - -/* The on disk layout of an extent overflow file key. */ -struct grub_hfsplus_extkey -{ - grub_uint16_t keylen; - grub_uint8_t type; - grub_uint8_t unused; - grub_uint32_t fileid; - grub_uint32_t start; -} __attribute__ ((packed)); - -struct grub_hfsplus_key -{ - union - { - struct grub_hfsplus_extkey extkey; - struct grub_hfsplus_catkey catkey; - grub_uint16_t keylen; - }; -} __attribute__ ((packed)); - struct grub_hfsplus_catfile { grub_uint16_t type; @@ -162,9 +89,13 @@ struct grub_hfsplus_catfile #define GRUB_HFSPLUS_FILEMODE_SYMLINK 0120000 /* Some pre-defined file IDs. */ -#define GRUB_HFSPLUS_FILEID_ROOTDIR 2 -#define GRUB_HFSPLUS_FILEID_OVERFLOW 3 -#define GRUB_HFSPLUS_FILEID_CATALOG 4 +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 { @@ -177,98 +108,15 @@ enum grub_hfsplus_filetype #define GRUB_HFSPLUSX_BINARYCOMPARE 0xBC #define GRUB_HFSPLUSX_CASEFOLDING 0xCF -/* Internal representation of a catalog key. */ -struct grub_hfsplus_catkey_internal -{ - grub_uint32_t parent; - const grub_uint16_t *name; - grub_size_t namelen; -}; - -/* Internal representation of an extent overflow key. */ -struct grub_hfsplus_extkey_internal -{ - grub_uint32_t fileid; - grub_uint8_t type; - grub_uint32_t start; -}; - -struct grub_hfsplus_key_internal -{ - union - { - struct grub_hfsplus_extkey_internal extkey; - struct grub_hfsplus_catkey_internal catkey; - }; -}; - - - -struct grub_fshelp_node -{ - struct grub_hfsplus_data *data; - struct grub_hfsplus_extent extents[8]; - grub_uint64_t size; - grub_uint32_t fileid; - grub_int32_t mtime; -}; - -struct grub_hfsplus_btree -{ - grub_uint32_t root; - grub_size_t nodesize; - - /* Catalog file node. */ - struct grub_fshelp_node file; -}; - -/* Information about a "mounted" HFS+ filesystem. */ -struct grub_hfsplus_data -{ - struct grub_hfsplus_volheader volheader; - grub_disk_t disk; - - unsigned int log2blksize; - - struct grub_hfsplus_btree catalog_tree; - struct grub_hfsplus_btree extoverflow_tree; - - struct grub_fshelp_node dirroot; - struct grub_fshelp_node opened_file; - - /* This is the offset into the physical disk for an embedded HFS+ - filesystem (one inside a plain HFS wrapper). */ - grub_disk_addr_t embedded_offset; - int case_sensitive; -}; - static grub_dl_t my_mod; -/* Return the offset of the record with the index INDEX, in the node - NODE which is part of the B+ tree BTREE. */ -static inline grub_off_t -grub_hfsplus_btree_recoffset (struct grub_hfsplus_btree *btree, - struct grub_hfsplus_btnode *node, int index) -{ - char *cnode = (char *) node; - void *recptr; - recptr = (&cnode[btree->nodesize - index * sizeof (grub_uint16_t) - 2]); - return grub_be_to_cpu16 (grub_get_unaligned16 (recptr)); -} - -/* Return a pointer to the record with the index INDEX, in the node - NODE which is part of the B+ tree BTREE. */ -static inline struct grub_hfsplus_key * -grub_hfsplus_btree_recptr (struct grub_hfsplus_btree *btree, - struct grub_hfsplus_btnode *node, int index) -{ - char *cnode = (char *) node; - grub_off_t offset; - offset = grub_hfsplus_btree_recoffset (btree, node, index); - return (struct grub_hfsplus_key *) &cnode[offset]; -} +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 @@ -292,14 +140,6 @@ grub_hfsplus_find_block (struct grub_hfsplus_extent *extent, return 0xffffffffffffffffULL; } -static 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); - static int grub_hfsplus_cmp_extkey (struct grub_hfsplus_key *keya, struct grub_hfsplus_key_internal *keyb); @@ -310,7 +150,8 @@ 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->extents[0]; + struct grub_hfsplus_extent *extents = node->compressed + ? &node->resource_extents[0] : &node->extents[0]; while (1) { @@ -344,10 +185,11 @@ grub_hfsplus_read_block (grub_fshelp_node_t node, grub_disk_addr_t fileblock) 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)) + 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", @@ -373,7 +215,7 @@ grub_hfsplus_read_block (grub_fshelp_node_t node, grub_disk_addr_t fileblock) /* Read LEN bytes from the file described by DATA starting with byte POS. Return the amount of read bytes in READ. */ -static grub_ssize_t +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) @@ -460,15 +302,27 @@ grub_hfsplus_mount (grub_disk_t disk) /* 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); @@ -501,6 +355,20 @@ grub_hfsplus_mount (grub_disk_t disk) data->extoverflow_tree.root = grub_be_to_cpu32 (header.root); data->extoverflow_tree.nodesize = grub_be_to_cpu16 (header.nodesize); + 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; @@ -586,6 +454,12 @@ grub_hfsplus_cmp_extkey (struct grub_hfsplus_key *keya, 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) @@ -668,7 +542,7 @@ grub_hfsplus_btree_iterate_node (struct grub_hfsplus_btree *btree, 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. */ -static grub_err_t +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, @@ -683,6 +557,12 @@ grub_hfsplus_btree_search (struct grub_hfsplus_btree *btree, 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; @@ -760,7 +640,7 @@ grub_hfsplus_btree_search (struct grub_hfsplus_btree *btree, { *matchnode = 0; grub_free (node); - return 1; + return 0; } } } @@ -872,11 +752,17 @@ list_nodes (void *record, void *hook_arg) if (!node) 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); @@ -919,7 +805,8 @@ grub_hfsplus_iterate_dir (grub_fshelp_node_t dir, /* First lookup the first entry. */ if (grub_hfsplus_btree_search (&dir->data->catalog_tree, &intern, - grub_hfsplus_cmp_catkey, &node, &ptr)) + grub_hfsplus_cmp_catkey, &node, &ptr) + || !node) return 0; /* Iterate over all entries in this directory. */ @@ -950,6 +837,14 @@ grub_hfsplus_open (struct grub_file *file, const char *name) 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); @@ -973,7 +868,13 @@ grub_hfsplus_open (struct grub_file *file, const char *name) static grub_err_t grub_hfsplus_close (grub_file_t file) { - grub_free (file->data); + 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); @@ -987,6 +888,10 @@ grub_hfsplus_read (grub_file_t file, char *buf, grub_size_t len) struct grub_hfsplus_data *data = (struct grub_hfsplus_data *) file->data; + 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); @@ -1076,7 +981,8 @@ grub_hfsplus_label (grub_device_t device, char **label) /* First lookup the first entry. */ if (grub_hfsplus_btree_search (&data->catalog_tree, &intern, - grub_hfsplus_cmp_catkey_id, &node, &ptr)) + grub_hfsplus_cmp_catkey_id, &node, &ptr) + || !node) { grub_free (data); return 0; diff --git a/grub-core/fs/hfspluscomp.c b/grub-core/fs/hfspluscomp.c new file mode 100644 index 000000000..b3434418c --- /dev/null +++ b/grub-core/fs/hfspluscomp.c @@ -0,0 +1,302 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2012 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 . + */ + +/* HFS+ is documented at http://developer.apple.com/technotes/tn/tn1150.html */ + +#include +#include +#include +#include +#include + +GRUB_MOD_LICENSE ("GPLv3+"); + +/* big-endian. */ +struct grub_hfsplus_compress_header1 +{ + grub_uint32_t header_size; + grub_uint32_t end_descriptor_offset; + grub_uint32_t total_compressed_size_including_seek_blocks_and_header2; + grub_uint32_t value_0x32; + grub_uint8_t unused[0xf0]; +} __attribute__ ((packed)); + +/* big-endian. */ +struct grub_hfsplus_compress_header2 +{ + grub_uint32_t total_compressed_size_including_seek_blocks; +} __attribute__ ((packed)); + +/* little-endian. */ +struct grub_hfsplus_compress_header3 +{ + grub_uint32_t num_chunks; +} __attribute__ ((packed)); + +/* little-endian. */ +struct grub_hfsplus_compress_block_descriptor +{ + grub_uint32_t offset; + grub_uint32_t size; +}; + +struct grub_hfsplus_compress_end_descriptor +{ + grub_uint8_t always_the_same[50]; +} __attribute__ ((packed)); + +struct grub_hfsplus_attr_header +{ + grub_uint8_t unused[3]; + grub_uint8_t type; + grub_uint32_t unknown[1]; + grub_uint64_t size; +} __attribute__ ((packed)); + +struct grub_hfsplus_compress_attr +{ + grub_uint32_t magic; + grub_uint32_t type; + grub_uint32_t uncompressed_inline_size; + grub_uint32_t always_0; +}; + +enum + { + HFSPLUS_COMPRESSION_INLINE = 3, + HFSPLUS_COMPRESSION_RESOURCE = 4 + }; + +static int +grub_hfsplus_cmp_attrkey (struct grub_hfsplus_key *keya, + struct grub_hfsplus_key_internal *keyb) +{ + struct grub_hfsplus_attrkey *attrkey_a = &keya->attrkey; + struct grub_hfsplus_attrkey_internal *attrkey_b = &keyb->attrkey; + grub_uint32_t aparent = grub_be_to_cpu32 (attrkey_a->cnid); + grub_size_t len; + int diff; + + if (aparent > attrkey_b->cnid) + return 1; + if (aparent < attrkey_b->cnid) + return -1; + + len = grub_be_to_cpu16 (attrkey_a->namelen); + if (len > attrkey_b->namelen) + len = attrkey_b->namelen; + /* Since it's big-endian memcmp gives the same result as manually comparing + uint16_t but may be faster. */ + diff = grub_memcmp (attrkey_a->name, attrkey_b->name, + len * sizeof (attrkey_a->name[0])); + if (diff == 0) + diff = grub_be_to_cpu16 (attrkey_a->namelen) - attrkey_b->namelen; + return diff; +} + +#define HFSPLUS_COMPRESS_BLOCK_SIZE 65536 + +static grub_ssize_t +hfsplus_read_compressed_real (struct grub_hfsplus_file *node, + grub_off_t pos, grub_size_t len, char *buf) +{ + char *tmp_buf = 0; + grub_size_t len0 = len; + + if (node->compressed == 1) + { + grub_memcpy (buf, node->cbuf + pos, len); + return len; + } + + while (len) + { + grub_uint32_t block = pos / HFSPLUS_COMPRESS_BLOCK_SIZE; + grub_size_t curlen = HFSPLUS_COMPRESS_BLOCK_SIZE + - (pos % HFSPLUS_COMPRESS_BLOCK_SIZE); + + if (curlen > len) + curlen = len; + + if (node->cbuf_block != block) + { + grub_uint32_t sz = grub_le_to_cpu32 (node->compress_index[block].size); + grub_size_t ts; + if (!tmp_buf) + tmp_buf = grub_malloc (HFSPLUS_COMPRESS_BLOCK_SIZE); + if (!tmp_buf) + return -1; + if (grub_hfsplus_read_file (node, 0, 0, + grub_le_to_cpu32 (node->compress_index[block].start) + 0x104, + sz, tmp_buf) + != (grub_ssize_t) sz) + { + grub_free (tmp_buf); + return -1; + } + ts = HFSPLUS_COMPRESS_BLOCK_SIZE; + if (ts > node->size - (pos & ~(HFSPLUS_COMPRESS_BLOCK_SIZE))) + ts = node->size - (pos & ~(HFSPLUS_COMPRESS_BLOCK_SIZE)); + if (grub_zlib_decompress (tmp_buf, sz, 0, + node->cbuf, ts) < 0) + { + grub_free (tmp_buf); + return -1; + } + node->cbuf_block = block; + } + grub_memcpy (buf, node->cbuf + (pos % HFSPLUS_COMPRESS_BLOCK_SIZE), + curlen); + buf += curlen; + pos += curlen; + len -= curlen; + } + grub_free (tmp_buf); + return len0; +} + +static grub_err_t +hfsplus_open_compressed_real (struct grub_hfsplus_file *node) +{ + grub_err_t err; + struct grub_hfsplus_btnode *attr_node; + grub_off_t attr_off; + struct grub_hfsplus_key_internal key; + struct grub_hfsplus_attr_header *attr_head; + struct grub_hfsplus_compress_attr *cmp_head; +#define c grub_cpu_to_be16_compile_time + const grub_uint16_t compress_attr_name[] = + { + c('c'), c('o'), c('m'), c('.'), c('a'), c('p'), c('p'), c('l'), c('e'), + c('.'), c('d'), c('e'), c('c'), c('m'), c('p'), c('f'), c('s') }; +#undef c + if (node->size) + return 0; + + key.attrkey.cnid = node->fileid; + key.attrkey.namelen = sizeof (compress_attr_name) / sizeof (compress_attr_name[0]); + key.attrkey.name = compress_attr_name; + + err = grub_hfsplus_btree_search (&node->data->attr_tree, &key, + grub_hfsplus_cmp_attrkey, + &attr_node, &attr_off); + if (err || !attr_node) + { + grub_errno = 0; + return 0; + } + + attr_head = (struct grub_hfsplus_attr_header *) + ((char *) grub_hfsplus_btree_recptr (&node->data->attr_tree, + attr_node, attr_off) + + sizeof (struct grub_hfsplus_attrkey) + sizeof (compress_attr_name)); + if (attr_head->type != 0x10 + || !(attr_head->size & grub_cpu_to_be64_compile_time(~0xfULL))) + { + grub_free (attr_node); + return 0; + } + cmp_head = (struct grub_hfsplus_compress_attr *) (attr_head + 1); + if (cmp_head->magic != grub_cpu_to_be32_compile_time (0x66706d63)) + { + grub_free (attr_node); + return 0; + } + node->size = grub_le_to_cpu32 (cmp_head->uncompressed_inline_size); + + if (cmp_head->type == grub_cpu_to_le32_compile_time (HFSPLUS_COMPRESSION_RESOURCE)) + { + grub_uint32_t index_size; + node->compressed = 2; + + if (grub_hfsplus_read_file (node, 0, 0, + 0x104, sizeof (index_size), + (char *) &index_size) + != 4) + { + node->compressed = 0; + grub_free (attr_node); + grub_errno = 0; + return 0; + } + node->compress_index_size = grub_le_to_cpu32 (index_size); + node->compress_index = grub_malloc (node->compress_index_size + * sizeof (node->compress_index[0])); + if (!node->compress_index) + { + node->compressed = 0; + grub_free (attr_node); + return grub_errno; + } + if (grub_hfsplus_read_file (node, 0, 0, + 0x104 + sizeof (index_size), + node->compress_index_size + * sizeof (node->compress_index[0]), + (char *) node->compress_index) + != (grub_ssize_t) (node->compress_index_size + * sizeof (node->compress_index[0]))) + { + node->compressed = 0; + grub_free (attr_node); + grub_free (node->compress_index); + grub_errno = 0; + return 0; + } + + node->cbuf_block = -1; + + node->cbuf = grub_malloc (HFSPLUS_COMPRESS_BLOCK_SIZE); + grub_free (attr_node); + if (!node->cbuf) + { + node->compressed = 0; + grub_free (node->compress_index); + return grub_errno; + } + return 0; + } + if (cmp_head->type != HFSPLUS_COMPRESSION_INLINE) + { + grub_free (attr_node); + return 0; + } + + node->cbuf = grub_malloc (node->size); + if (!node->cbuf) + return grub_errno; + + if (grub_zlib_decompress ((char *) (cmp_head + 1), + grub_cpu_to_be64 (attr_head->size) + - sizeof (*cmp_head), 0, + node->cbuf, node->size) < 0) + return grub_errno; + node->compressed = 1; + return 0; +} + +GRUB_MOD_INIT(hfspluscomp) +{ + grub_hfsplus_open_compressed = hfsplus_open_compressed_real; + grub_hfsplus_read_compressed = hfsplus_read_compressed_real; +} + +GRUB_MOD_FINI(hfspluscomp) +{ + grub_hfsplus_open_compressed = 0; + grub_hfsplus_read_compressed = 0; +} diff --git a/include/grub/hfsplus.h b/include/grub/hfsplus.h new file mode 100644 index 000000000..0defd35b3 --- /dev/null +++ b/include/grub/hfsplus.h @@ -0,0 +1,217 @@ + +#include +#include + +/* A HFS+ extent. */ +struct grub_hfsplus_extent +{ + /* The first block of a file on disk. */ + grub_uint32_t start; + /* The amount of blocks described by this extent. */ + grub_uint32_t count; +} __attribute__ ((packed)); + +/* The descriptor of a fork. */ +struct grub_hfsplus_forkdata +{ + grub_uint64_t size; + grub_uint32_t clumpsize; + grub_uint32_t blocks; + struct grub_hfsplus_extent extents[8]; +} __attribute__ ((packed)); + +/* The HFS+ Volume Header. */ +struct grub_hfsplus_volheader +{ + grub_uint16_t magic; + grub_uint16_t version; + grub_uint32_t attributes; + grub_uint8_t unused1[12]; + grub_uint32_t utime; + grub_uint8_t unused2[16]; + grub_uint32_t blksize; + grub_uint8_t unused3[60]; + grub_uint64_t num_serial; + struct grub_hfsplus_forkdata allocations_file; + struct grub_hfsplus_forkdata extents_file; + struct grub_hfsplus_forkdata catalog_file; + struct grub_hfsplus_forkdata attr_file; + struct grub_hfsplus_forkdata startup_file; +} __attribute__ ((packed)); + +struct grub_hfsplus_compress_index +{ + grub_uint32_t start; + grub_uint32_t size; +}; + +struct grub_hfsplus_file +{ + struct grub_hfsplus_data *data; + struct grub_hfsplus_extent extents[8]; + struct grub_hfsplus_extent resource_extents[8]; + grub_uint64_t size; + grub_uint64_t resource_size; + grub_uint32_t fileid; + grub_int32_t mtime; + int compressed; + char *cbuf; + struct grub_hfsplus_compress_index *compress_index; + grub_uint32_t cbuf_block; + grub_uint32_t compress_index_size; +}; + +struct grub_hfsplus_btree +{ + grub_uint32_t root; + grub_size_t nodesize; + + /* Catalog file node. */ + struct grub_hfsplus_file file; +}; + +/* Information about a "mounted" HFS+ filesystem. */ +struct grub_hfsplus_data +{ + struct grub_hfsplus_volheader volheader; + grub_disk_t disk; + + unsigned int log2blksize; + + struct grub_hfsplus_btree catalog_tree; + struct grub_hfsplus_btree extoverflow_tree; + struct grub_hfsplus_btree attr_tree; + + struct grub_hfsplus_file dirroot; + struct grub_hfsplus_file opened_file; + + /* This is the offset into the physical disk for an embedded HFS+ + filesystem (one inside a plain HFS wrapper). */ + grub_disk_addr_t embedded_offset; + int case_sensitive; +}; + +/* Internal representation of a catalog key. */ +struct grub_hfsplus_catkey_internal +{ + grub_uint32_t parent; + const grub_uint16_t *name; + grub_size_t namelen; +}; + +/* Internal representation of an extent overflow key. */ +struct grub_hfsplus_extkey_internal +{ + grub_uint32_t fileid; + grub_uint32_t start; + grub_uint8_t type; +}; + +struct grub_hfsplus_attrkey +{ + grub_uint16_t keylen; + grub_uint16_t unknown1[1]; + grub_uint32_t cnid; + grub_uint16_t unknown2[2]; + grub_uint16_t namelen; + grub_uint16_t name[0]; +} __attribute__ ((packed)); + +struct grub_hfsplus_attrkey_internal +{ + grub_uint32_t cnid; + const grub_uint16_t *name; + grub_size_t namelen; +}; + +struct grub_hfsplus_key_internal +{ + union + { + struct grub_hfsplus_extkey_internal extkey; + struct grub_hfsplus_catkey_internal catkey; + struct grub_hfsplus_attrkey_internal attrkey; + }; +}; + +/* The on disk layout of a catalog key. */ +struct grub_hfsplus_catkey +{ + grub_uint16_t keylen; + grub_uint32_t parent; + grub_uint16_t namelen; + grub_uint16_t name[30]; +} __attribute__ ((packed)); + +/* The on disk layout of an extent overflow file key. */ +struct grub_hfsplus_extkey +{ + grub_uint16_t keylen; + grub_uint8_t type; + grub_uint8_t unused; + grub_uint32_t fileid; + grub_uint32_t start; +} __attribute__ ((packed)); + +struct grub_hfsplus_key +{ + union + { + struct grub_hfsplus_extkey extkey; + struct grub_hfsplus_catkey catkey; + struct grub_hfsplus_attrkey attrkey; + grub_uint16_t keylen; + }; +} __attribute__ ((packed)); + +struct grub_hfsplus_btnode +{ + grub_uint32_t next; + grub_uint32_t prev; + grub_int8_t type; + grub_uint8_t height; + grub_uint16_t count; + grub_uint16_t unused; +} __attribute__ ((packed)); + +/* Return the offset of the record with the index INDEX, in the node + NODE which is part of the B+ tree BTREE. */ +static inline grub_off_t +grub_hfsplus_btree_recoffset (struct grub_hfsplus_btree *btree, + struct grub_hfsplus_btnode *node, int index) +{ + char *cnode = (char *) node; + void *recptr; + recptr = (&cnode[btree->nodesize - index * sizeof (grub_uint16_t) - 2]); + return grub_be_to_cpu16 (grub_get_unaligned16 (recptr)); +} + +/* Return a pointer to the record with the index INDEX, in the node + NODE which is part of the B+ tree BTREE. */ +static inline struct grub_hfsplus_key * +grub_hfsplus_btree_recptr (struct grub_hfsplus_btree *btree, + struct grub_hfsplus_btnode *node, int index) +{ + char *cnode = (char *) node; + grub_off_t offset; + offset = grub_hfsplus_btree_recoffset (btree, node, index); + return (struct grub_hfsplus_key *) &cnode[offset]; +} + +extern grub_err_t (*grub_hfsplus_open_compressed) (struct grub_hfsplus_file *node); +extern grub_ssize_t (*grub_hfsplus_read_compressed) (struct grub_hfsplus_file *node, + grub_off_t pos, + grub_size_t len, + char *buf); + +grub_ssize_t +grub_hfsplus_read_file (struct grub_hfsplus_file *node, + grub_disk_read_hook_t read_hook, void *read_hook_data, + grub_off_t pos, grub_size_t len, char *buf); +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);