Compressed HFS+ support.
This commit is contained in:
parent
16940e077c
commit
8098f65556
6 changed files with 618 additions and 183 deletions
302
grub-core/fs/hfspluscomp.c
Normal file
302
grub-core/fs/hfspluscomp.c
Normal 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;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue