Compressed HFS+ support.

This commit is contained in:
Vladimir 'phcoder' Serbinenko 2013-05-07 15:46:17 +02:00
parent 16940e077c
commit 8098f65556
6 changed files with 618 additions and 183 deletions

View file

@ -1,3 +1,7 @@
2013-05-07 Vladimir Serbinenko <phcoder@gmail.com>
Compressed HFS+ support.
2013-05-07 Vladimir Serbinenko <phcoder@gmail.com> 2013-05-07 Vladimir Serbinenko <phcoder@gmail.com>
* grub-core/commands/videoinfo.c: Use "paletted" rather than "packed * grub-core/commands/videoinfo.c: Use "paletted" rather than "packed

View file

@ -85,6 +85,7 @@ library = {
common = grub-core/fs/fshelp.c; common = grub-core/fs/fshelp.c;
common = grub-core/fs/hfs.c; common = grub-core/fs/hfs.c;
common = grub-core/fs/hfsplus.c; common = grub-core/fs/hfsplus.c;
common = grub-core/fs/hfspluscomp.c;
common = grub-core/fs/iso9660.c; common = grub-core/fs/iso9660.c;
common = grub-core/fs/jfs.c; common = grub-core/fs/jfs.c;
common = grub-core/fs/minix.c; common = grub-core/fs/minix.c;

View file

@ -1185,6 +1185,11 @@ module = {
common = fs/hfsplus.c; common = fs/hfsplus.c;
}; };
module = {
name = hfspluscomp;
common = fs/hfspluscomp.c;
};
module = { module = {
name = iso9660; name = iso9660;
common = fs/iso9660.c; common = fs/iso9660.c;

View file

@ -19,6 +19,7 @@
/* HFS+ is documented at http://developer.apple.com/technotes/tn/tn1150.html */ /* 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/err.h>
#include <grub/file.h> #include <grub/file.h>
#include <grub/mm.h> #include <grub/mm.h>
@ -29,6 +30,7 @@
#include <grub/fshelp.h> #include <grub/fshelp.h>
#include <grub/hfs.h> #include <grub/hfs.h>
#include <grub/charset.h> #include <grub/charset.h>
#include <grub/hfsplus.h>
GRUB_MOD_LICENSE ("GPLv3+"); GRUB_MOD_LICENSE ("GPLv3+");
@ -36,42 +38,6 @@ GRUB_MOD_LICENSE ("GPLv3+");
#define GRUB_HFSPLUSX_MAGIC 0x4858 #define GRUB_HFSPLUSX_MAGIC 0x4858
#define GRUB_HFSPLUS_SBLOCK 2 #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. */ /* The type of node. */
enum grub_hfsplus_btnode_type enum grub_hfsplus_btnode_type
@ -82,16 +48,6 @@ enum grub_hfsplus_btnode_type
GRUB_HFSPLUS_BTNODE_TYPE_MAP = 2, 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. */ /* The header of a HFS+ B+ Tree. */
struct grub_hfsplus_btheader struct grub_hfsplus_btheader
{ {
@ -111,35 +67,6 @@ struct grub_hfsplus_btheader
grub_uint32_t attributes; grub_uint32_t attributes;
} __attribute__ ((packed)); } __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 struct grub_hfsplus_catfile
{ {
grub_uint16_t type; grub_uint16_t type;
@ -162,9 +89,13 @@ struct grub_hfsplus_catfile
#define GRUB_HFSPLUS_FILEMODE_SYMLINK 0120000 #define GRUB_HFSPLUS_FILEMODE_SYMLINK 0120000
/* Some pre-defined file IDs. */ /* Some pre-defined file IDs. */
#define GRUB_HFSPLUS_FILEID_ROOTDIR 2 enum
#define GRUB_HFSPLUS_FILEID_OVERFLOW 3 {
#define GRUB_HFSPLUS_FILEID_CATALOG 4 GRUB_HFSPLUS_FILEID_ROOTDIR = 2,
GRUB_HFSPLUS_FILEID_OVERFLOW = 3,
GRUB_HFSPLUS_FILEID_CATALOG = 4,
GRUB_HFSPLUS_FILEID_ATTR = 8
};
enum grub_hfsplus_filetype enum grub_hfsplus_filetype
{ {
@ -177,98 +108,15 @@ enum grub_hfsplus_filetype
#define GRUB_HFSPLUSX_BINARYCOMPARE 0xBC #define GRUB_HFSPLUSX_BINARYCOMPARE 0xBC
#define GRUB_HFSPLUSX_CASEFOLDING 0xCF #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; 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 /* 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 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; 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, static int grub_hfsplus_cmp_extkey (struct grub_hfsplus_key *keya,
struct grub_hfsplus_key_internal *keyb); 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; struct grub_hfsplus_btnode *nnode = 0;
grub_disk_addr_t blksleft = fileblock; 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) 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.fileid = node->fileid;
extoverflow.extkey.type = 0; extoverflow.extkey.type = 0;
extoverflow.extkey.start = fileblock - blksleft; extoverflow.extkey.start = fileblock - blksleft;
extoverflow.extkey.type = node->compressed ? 0xff : 0;
if (grub_hfsplus_btree_search (&node->data->extoverflow_tree, if (grub_hfsplus_btree_search (&node->data->extoverflow_tree,
&extoverflow, &extoverflow,
grub_hfsplus_cmp_extkey, &nnode, &ptr)) grub_hfsplus_cmp_extkey, &nnode, &ptr)
|| !nnode)
{ {
grub_error (GRUB_ERR_READ_ERROR, grub_error (GRUB_ERR_READ_ERROR,
"no block found for the file id 0x%x and the block offset 0x%x", "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 /* Read LEN bytes from the file described by DATA starting with byte
POS. Return the amount of read bytes in READ. */ 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_hfsplus_read_file (grub_fshelp_node_t node,
grub_disk_read_hook_t read_hook, void *read_hook_data, grub_disk_read_hook_t read_hook, void *read_hook_data,
grub_off_t pos, grub_size_t len, char *buf) 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. */ /* Make a new node for the catalog tree. */
data->catalog_tree.file.data = data; data->catalog_tree.file.data = data;
data->catalog_tree.file.fileid = GRUB_HFSPLUS_FILEID_CATALOG; data->catalog_tree.file.fileid = GRUB_HFSPLUS_FILEID_CATALOG;
data->catalog_tree.file.compressed = 0;
grub_memcpy (&data->catalog_tree.file.extents, grub_memcpy (&data->catalog_tree.file.extents,
data->volheader.catalog_file.extents, data->volheader.catalog_file.extents,
sizeof data->volheader.catalog_file.extents); sizeof data->volheader.catalog_file.extents);
data->catalog_tree.file.size = data->catalog_tree.file.size =
grub_be_to_cpu64 (data->volheader.catalog_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. */ /* Make a new node for the extent overflow file. */
data->extoverflow_tree.file.data = data; data->extoverflow_tree.file.data = data;
data->extoverflow_tree.file.fileid = GRUB_HFSPLUS_FILEID_OVERFLOW; data->extoverflow_tree.file.fileid = GRUB_HFSPLUS_FILEID_OVERFLOW;
data->extoverflow_tree.file.compressed = 0;
grub_memcpy (&data->extoverflow_tree.file.extents, grub_memcpy (&data->extoverflow_tree.file.extents,
data->volheader.extents_file.extents, data->volheader.extents_file.extents,
sizeof data->volheader.catalog_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.root = grub_be_to_cpu32 (header.root);
data->extoverflow_tree.nodesize = grub_be_to_cpu16 (header.nodesize); 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.data = data;
data->dirroot.fileid = GRUB_HFSPLUS_FILEID_ROOTDIR; data->dirroot.fileid = GRUB_HFSPLUS_FILEID_ROOTDIR;
@ -586,6 +454,12 @@ grub_hfsplus_cmp_extkey (struct grub_hfsplus_key *keya,
return 1; return 1;
if (extkey_a->type < extkey_b->type) if (extkey_a->type < extkey_b->type)
return -1; 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); akey = grub_be_to_cpu32 (extkey_a->start);
if (akey > extkey_b->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, 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 return the node in MATCHNODE and a pointer to the data in this node
in KEYOFFSET. MATCHNODE should be freed by the caller. */ 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, grub_hfsplus_btree_search (struct grub_hfsplus_btree *btree,
struct grub_hfsplus_key_internal *key, struct grub_hfsplus_key_internal *key,
int (*compare_keys) (struct grub_hfsplus_key *keya, 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 save_node;
grub_uint64_t node_count = 0; grub_uint64_t node_count = 0;
if (!btree->nodesize)
{
*matchnode = 0;
return 0;
}
node = grub_malloc (btree->nodesize); node = grub_malloc (btree->nodesize);
if (! node) if (! node)
return grub_errno; return grub_errno;
@ -760,7 +640,7 @@ grub_hfsplus_btree_search (struct grub_hfsplus_btree *btree,
{ {
*matchnode = 0; *matchnode = 0;
grub_free (node); grub_free (node);
return 1; return 0;
} }
} }
} }
@ -872,11 +752,17 @@ list_nodes (void *record, void *hook_arg)
if (!node) if (!node)
return 1; return 1;
node->data = ctx->dir->data; node->data = ctx->dir->data;
node->compressed = 0;
node->cbuf = 0;
node->compress_index = 0;
grub_memcpy (node->extents, fileinfo->data.extents, grub_memcpy (node->extents, fileinfo->data.extents,
sizeof (node->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->mtime = grub_be_to_cpu32 (fileinfo->mtime) - 2082844800;
node->size = grub_be_to_cpu64 (fileinfo->data.size); 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); node->fileid = grub_be_to_cpu32 (fileinfo->fileid);
ctx->ret = ctx->hook (filename, type, node, ctx->hook_data); 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. */ /* First lookup the first entry. */
if (grub_hfsplus_btree_search (&dir->data->catalog_tree, &intern, 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; return 0;
/* Iterate over all entries in this directory. */ /* Iterate over all entries in this directory. */
@ -950,6 +837,14 @@ grub_hfsplus_open (struct grub_file *file, const char *name)
if (grub_errno) if (grub_errno)
goto fail; 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; file->size = fdiro->size;
data->opened_file = *fdiro; data->opened_file = *fdiro;
grub_free (fdiro); grub_free (fdiro);
@ -973,7 +868,13 @@ grub_hfsplus_open (struct grub_file *file, const char *name)
static grub_err_t static grub_err_t
grub_hfsplus_close (grub_file_t file) 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); 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 *data =
(struct grub_hfsplus_data *) file->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, return grub_hfsplus_read_file (&data->opened_file,
file->read_hook, file->read_hook_data, file->read_hook, file->read_hook_data,
file->offset, len, buf); file->offset, len, buf);
@ -1076,7 +981,8 @@ grub_hfsplus_label (grub_device_t device, char **label)
/* First lookup the first entry. */ /* First lookup the first entry. */
if (grub_hfsplus_btree_search (&data->catalog_tree, &intern, 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); grub_free (data);
return 0; return 0;

302
grub-core/fs/hfspluscomp.c Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
*/
/* HFS+ is documented at http://developer.apple.com/technotes/tn/tn1150.html */
#include <grub/hfsplus.h>
#include <grub/dl.h>
#include <grub/misc.h>
#include <grub/mm.h>
#include <grub/deflate.h>
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;
}

217
include/grub/hfsplus.h Normal file
View file

@ -0,0 +1,217 @@
#include <grub/types.h>
#include <grub/disk.h>
/* 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);