grub2/0441-Compressed-HFS-support.patch
Peter Jones f74b50e380 Rebase to upstream, fix a pile of bugs. The usual.
Signed-off-by: Peter Jones <pjones@redhat.com>
2013-06-12 15:37:08 -04:00

1049 lines
30 KiB
Diff
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

From 14aa7e045965ce95779e17e20503191f8f6bc160 Mon Sep 17 00:00:00 2001
From: Vladimir 'phcoder' Serbinenko <phcoder@gmail.com>
Date: Tue, 7 May 2013 15:46:17 +0200
Subject: [PATCH 441/482] Compressed HFS+ support.
---
ChangeLog | 4 +
Makefile.util.def | 1 +
grub-core/Makefile.core.def | 5 +
grub-core/fs/hfsplus.c | 272 +++++++++++++--------------------------
grub-core/fs/hfspluscomp.c | 302 ++++++++++++++++++++++++++++++++++++++++++++
include/grub/hfsplus.h | 217 +++++++++++++++++++++++++++++++
6 files changed, 618 insertions(+), 183 deletions(-)
create mode 100644 grub-core/fs/hfspluscomp.c
create mode 100644 include/grub/hfsplus.h
diff --git a/ChangeLog b/ChangeLog
index 8b77759..0430249 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,9 @@
2013-05-07 Vladimir Serbinenko <phcoder@gmail.com>
+ Compressed HFS+ support.
+
+2013-05-07 Vladimir Serbinenko <phcoder@gmail.com>
+
* grub-core/commands/videoinfo.c: Use "paletted" rather than "packed
pixel".
diff --git a/Makefile.util.def b/Makefile.util.def
index 5888314..dc621db 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 7dc106e..bb62dce 100644
--- a/grub-core/Makefile.core.def
+++ b/grub-core/Makefile.core.def
@@ -1186,6 +1186,11 @@ module = {
};
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 a507c0f..7905624 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 <grub/err.h>
#include <grub/file.h>
#include <grub/mm.h>
@@ -29,6 +30,7 @@
#include <grub/fshelp.h>
#include <grub/hfs.h>
#include <grub/charset.h>
+#include <grub/hfsplus.h>
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 0000000..b343441
--- /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 <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;
+}
diff --git a/include/grub/hfsplus.h b/include/grub/hfsplus.h
new file mode 100644
index 0000000..0defd35
--- /dev/null
+++ b/include/grub/hfsplus.h
@@ -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);
--
1.8.2.1