Compressed HFS+ support.
This commit is contained in:
parent
16940e077c
commit
8098f65556
6 changed files with 618 additions and 183 deletions
|
@ -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;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue