/* * 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 #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]; } GRUB_PACKED; /* big-endian. */ struct grub_hfsplus_compress_header2 { grub_uint32_t total_compressed_size_including_seek_blocks; } GRUB_PACKED; /* little-endian. */ struct grub_hfsplus_compress_header3 { grub_uint32_t num_chunks; } GRUB_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]; } GRUB_PACKED; struct grub_hfsplus_attr_header { grub_uint8_t unused[3]; grub_uint8_t type; grub_uint32_t unknown[1]; grub_uint64_t size; } GRUB_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; } GRUB_PACKED; 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); if (grub_file_progress_hook && node->file) grub_file_progress_hook (0, 0, len, node->file); 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) != (grub_ssize_t) ts) { if (!grub_errno) grub_error (GRUB_ERR_BAD_COMPRESSED_DATA, "premature end of compressed"); grub_free (tmp_buf); return -1; } node->cbuf_block = block; } grub_memcpy (buf, node->cbuf + (pos % HFSPLUS_COMPRESS_BLOCK_SIZE), curlen); if (grub_file_progress_hook && node->file) grub_file_progress_hook (0, 0, curlen, node->file); 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) != (grub_ssize_t) node->size) { if (!grub_errno) grub_error (GRUB_ERR_BAD_COMPRESSED_DATA, "premature end of compressed"); 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; }