]> git.proxmox.com Git - grub2.git/blob - grub-core/fs/hfspluscomp.c
Import grub2_2.02+dfsg1.orig.tar.xz
[grub2.git] / grub-core / fs / hfspluscomp.c
1 /*
2 * GRUB -- GRand Unified Bootloader
3 * Copyright (C) 2012 Free Software Foundation, Inc.
4 *
5 * GRUB is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * GRUB is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 /* HFS+ is documented at http://developer.apple.com/technotes/tn/tn1150.html */
20
21 #include <grub/hfsplus.h>
22 #include <grub/dl.h>
23 #include <grub/misc.h>
24 #include <grub/mm.h>
25 #include <grub/deflate.h>
26 #include <grub/file.h>
27
28 GRUB_MOD_LICENSE ("GPLv3+");
29
30 /* big-endian. */
31 struct grub_hfsplus_compress_header1
32 {
33 grub_uint32_t header_size;
34 grub_uint32_t end_descriptor_offset;
35 grub_uint32_t total_compressed_size_including_seek_blocks_and_header2;
36 grub_uint32_t value_0x32;
37 grub_uint8_t unused[0xf0];
38 } GRUB_PACKED;
39
40 /* big-endian. */
41 struct grub_hfsplus_compress_header2
42 {
43 grub_uint32_t total_compressed_size_including_seek_blocks;
44 } GRUB_PACKED;
45
46 /* little-endian. */
47 struct grub_hfsplus_compress_header3
48 {
49 grub_uint32_t num_chunks;
50 } GRUB_PACKED;
51
52 /* little-endian. */
53 struct grub_hfsplus_compress_block_descriptor
54 {
55 grub_uint32_t offset;
56 grub_uint32_t size;
57 };
58
59 struct grub_hfsplus_compress_end_descriptor
60 {
61 grub_uint8_t always_the_same[50];
62 } GRUB_PACKED;
63
64 struct grub_hfsplus_attr_header
65 {
66 grub_uint8_t unused[3];
67 grub_uint8_t type;
68 grub_uint32_t unknown[1];
69 grub_uint64_t size;
70 } GRUB_PACKED;
71
72 struct grub_hfsplus_compress_attr
73 {
74 grub_uint32_t magic;
75 grub_uint32_t type;
76 grub_uint32_t uncompressed_inline_size;
77 grub_uint32_t always_0;
78 } GRUB_PACKED;
79
80 enum
81 {
82 HFSPLUS_COMPRESSION_INLINE = 3,
83 HFSPLUS_COMPRESSION_RESOURCE = 4
84 };
85
86 static int
87 grub_hfsplus_cmp_attrkey (struct grub_hfsplus_key *keya,
88 struct grub_hfsplus_key_internal *keyb)
89 {
90 struct grub_hfsplus_attrkey *attrkey_a = &keya->attrkey;
91 struct grub_hfsplus_attrkey_internal *attrkey_b = &keyb->attrkey;
92 grub_uint32_t aparent = grub_be_to_cpu32 (attrkey_a->cnid);
93 grub_size_t len;
94 int diff;
95
96 if (aparent > attrkey_b->cnid)
97 return 1;
98 if (aparent < attrkey_b->cnid)
99 return -1;
100
101 len = grub_be_to_cpu16 (attrkey_a->namelen);
102 if (len > attrkey_b->namelen)
103 len = attrkey_b->namelen;
104 /* Since it's big-endian memcmp gives the same result as manually comparing
105 uint16_t but may be faster. */
106 diff = grub_memcmp (attrkey_a->name, attrkey_b->name,
107 len * sizeof (attrkey_a->name[0]));
108 if (diff == 0)
109 diff = grub_be_to_cpu16 (attrkey_a->namelen) - attrkey_b->namelen;
110 return diff;
111 }
112
113 #define HFSPLUS_COMPRESS_BLOCK_SIZE 65536
114
115 static grub_ssize_t
116 hfsplus_read_compressed_real (struct grub_hfsplus_file *node,
117 grub_off_t pos, grub_size_t len, char *buf)
118 {
119 char *tmp_buf = 0;
120 grub_size_t len0 = len;
121
122 if (node->compressed == 1)
123 {
124 grub_memcpy (buf, node->cbuf + pos, len);
125 if (grub_file_progress_hook && node->file)
126 grub_file_progress_hook (0, 0, len, node->file);
127 return len;
128 }
129
130 while (len)
131 {
132 grub_uint32_t block = pos / HFSPLUS_COMPRESS_BLOCK_SIZE;
133 grub_size_t curlen = HFSPLUS_COMPRESS_BLOCK_SIZE
134 - (pos % HFSPLUS_COMPRESS_BLOCK_SIZE);
135
136 if (curlen > len)
137 curlen = len;
138
139 if (node->cbuf_block != block)
140 {
141 grub_uint32_t sz = grub_le_to_cpu32 (node->compress_index[block].size);
142 grub_size_t ts;
143 if (!tmp_buf)
144 tmp_buf = grub_malloc (HFSPLUS_COMPRESS_BLOCK_SIZE);
145 if (!tmp_buf)
146 return -1;
147 if (grub_hfsplus_read_file (node, 0, 0,
148 grub_le_to_cpu32 (node->compress_index[block].start) + 0x104,
149 sz, tmp_buf)
150 != (grub_ssize_t) sz)
151 {
152 grub_free (tmp_buf);
153 return -1;
154 }
155 ts = HFSPLUS_COMPRESS_BLOCK_SIZE;
156 if (ts > node->size - (pos & ~(HFSPLUS_COMPRESS_BLOCK_SIZE)))
157 ts = node->size - (pos & ~(HFSPLUS_COMPRESS_BLOCK_SIZE));
158 if (grub_zlib_decompress (tmp_buf, sz, 0,
159 node->cbuf, ts) != (grub_ssize_t) ts)
160 {
161 if (!grub_errno)
162 grub_error (GRUB_ERR_BAD_COMPRESSED_DATA,
163 "premature end of compressed");
164
165 grub_free (tmp_buf);
166 return -1;
167 }
168 node->cbuf_block = block;
169 }
170 grub_memcpy (buf, node->cbuf + (pos % HFSPLUS_COMPRESS_BLOCK_SIZE),
171 curlen);
172 if (grub_file_progress_hook && node->file)
173 grub_file_progress_hook (0, 0, curlen, node->file);
174 buf += curlen;
175 pos += curlen;
176 len -= curlen;
177 }
178 grub_free (tmp_buf);
179 return len0;
180 }
181
182 static grub_err_t
183 hfsplus_open_compressed_real (struct grub_hfsplus_file *node)
184 {
185 grub_err_t err;
186 struct grub_hfsplus_btnode *attr_node;
187 grub_off_t attr_off;
188 struct grub_hfsplus_key_internal key;
189 struct grub_hfsplus_attr_header *attr_head;
190 struct grub_hfsplus_compress_attr *cmp_head;
191 #define c grub_cpu_to_be16_compile_time
192 const grub_uint16_t compress_attr_name[] =
193 {
194 c('c'), c('o'), c('m'), c('.'), c('a'), c('p'), c('p'), c('l'), c('e'),
195 c('.'), c('d'), c('e'), c('c'), c('m'), c('p'), c('f'), c('s') };
196 #undef c
197 if (node->size)
198 return 0;
199
200 key.attrkey.cnid = node->fileid;
201 key.attrkey.namelen = sizeof (compress_attr_name) / sizeof (compress_attr_name[0]);
202 key.attrkey.name = compress_attr_name;
203
204 err = grub_hfsplus_btree_search (&node->data->attr_tree, &key,
205 grub_hfsplus_cmp_attrkey,
206 &attr_node, &attr_off);
207 if (err || !attr_node)
208 {
209 grub_errno = 0;
210 return 0;
211 }
212
213 attr_head = (struct grub_hfsplus_attr_header *)
214 ((char *) grub_hfsplus_btree_recptr (&node->data->attr_tree,
215 attr_node, attr_off)
216 + sizeof (struct grub_hfsplus_attrkey) + sizeof (compress_attr_name));
217 if (attr_head->type != 0x10
218 || !(attr_head->size & grub_cpu_to_be64_compile_time(~0xfULL)))
219 {
220 grub_free (attr_node);
221 return 0;
222 }
223 cmp_head = (struct grub_hfsplus_compress_attr *) (attr_head + 1);
224 if (cmp_head->magic != grub_cpu_to_be32_compile_time (0x66706d63))
225 {
226 grub_free (attr_node);
227 return 0;
228 }
229 node->size = grub_le_to_cpu32 (cmp_head->uncompressed_inline_size);
230
231 if (cmp_head->type == grub_cpu_to_le32_compile_time (HFSPLUS_COMPRESSION_RESOURCE))
232 {
233 grub_uint32_t index_size;
234 node->compressed = 2;
235
236 if (grub_hfsplus_read_file (node, 0, 0,
237 0x104, sizeof (index_size),
238 (char *) &index_size)
239 != 4)
240 {
241 node->compressed = 0;
242 grub_free (attr_node);
243 grub_errno = 0;
244 return 0;
245 }
246 node->compress_index_size = grub_le_to_cpu32 (index_size);
247 node->compress_index = grub_malloc (node->compress_index_size
248 * sizeof (node->compress_index[0]));
249 if (!node->compress_index)
250 {
251 node->compressed = 0;
252 grub_free (attr_node);
253 return grub_errno;
254 }
255 if (grub_hfsplus_read_file (node, 0, 0,
256 0x104 + sizeof (index_size),
257 node->compress_index_size
258 * sizeof (node->compress_index[0]),
259 (char *) node->compress_index)
260 != (grub_ssize_t) (node->compress_index_size
261 * sizeof (node->compress_index[0])))
262 {
263 node->compressed = 0;
264 grub_free (attr_node);
265 grub_free (node->compress_index);
266 grub_errno = 0;
267 return 0;
268 }
269
270 node->cbuf_block = -1;
271
272 node->cbuf = grub_malloc (HFSPLUS_COMPRESS_BLOCK_SIZE);
273 grub_free (attr_node);
274 if (!node->cbuf)
275 {
276 node->compressed = 0;
277 grub_free (node->compress_index);
278 return grub_errno;
279 }
280 return 0;
281 }
282 if (cmp_head->type != HFSPLUS_COMPRESSION_INLINE)
283 {
284 grub_free (attr_node);
285 return 0;
286 }
287
288 node->cbuf = grub_malloc (node->size);
289 if (!node->cbuf)
290 return grub_errno;
291
292 if (grub_zlib_decompress ((char *) (cmp_head + 1),
293 grub_cpu_to_be64 (attr_head->size)
294 - sizeof (*cmp_head), 0,
295 node->cbuf, node->size)
296 != (grub_ssize_t) node->size)
297 {
298 if (!grub_errno)
299 grub_error (GRUB_ERR_BAD_COMPRESSED_DATA,
300 "premature end of compressed");
301 return grub_errno;
302 }
303 node->compressed = 1;
304 return 0;
305 }
306
307 GRUB_MOD_INIT(hfspluscomp)
308 {
309 grub_hfsplus_open_compressed = hfsplus_open_compressed_real;
310 grub_hfsplus_read_compressed = hfsplus_read_compressed_real;
311 }
312
313 GRUB_MOD_FINI(hfspluscomp)
314 {
315 grub_hfsplus_open_compressed = 0;
316 grub_hfsplus_read_compressed = 0;
317 }