mirror of
				https://git.proxmox.com/git/grub2
				synced 2025-10-31 15:44:20 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			318 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			318 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  *  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>
 | |
| #include <grub/file.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];
 | |
| } 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;
 | |
| }
 | 
