Upgraded GRUB2 to 2.00 release.
[AROS.git] / arch / all-pc / boot / grub2-aros / grub-core / io / xzio.c
blobae30e6f80182dbad0eedaef8d7e1f7b95df223dc
1 /* xzio.c - decompression support for xz */
2 /*
3 * GRUB -- GRand Unified Bootloader
4 * Copyright (C) 2010 Free Software Foundation, Inc.
6 * GRUB is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * GRUB is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
20 #include <grub/err.h>
21 #include <grub/mm.h>
22 #include <grub/misc.h>
23 #include <grub/file.h>
24 #include <grub/fs.h>
25 #include <grub/dl.h>
27 GRUB_MOD_LICENSE ("GPLv3+");
29 #include "xz.h"
30 #include "xz_stream.h"
32 #define XZBUFSIZ 0x2000
33 #define VLI_MAX_DIGITS 9
34 #define XZ_STREAM_FOOTER_SIZE 12
36 struct grub_xzio
38 grub_file_t file;
39 struct xz_buf buf;
40 struct xz_dec *dec;
41 grub_uint8_t inbuf[XZBUFSIZ];
42 grub_uint8_t outbuf[XZBUFSIZ];
43 grub_off_t saved_offset;
46 typedef struct grub_xzio *grub_xzio_t;
47 static struct grub_fs grub_xzio_fs;
49 static grub_size_t
50 decode_vli (const grub_uint8_t buf[], grub_size_t size_max,
51 grub_uint64_t *num)
53 if (size_max == 0)
54 return 0;
56 if (size_max > VLI_MAX_DIGITS)
57 size_max = VLI_MAX_DIGITS;
59 *num = buf[0] & 0x7F;
60 grub_size_t i = 0;
62 while (buf[i++] & 0x80)
64 if (i >= size_max || buf[i] == 0x00)
65 return 0;
67 *num |= (uint64_t) (buf[i] & 0x7F) << (i * 7);
70 return i;
73 static grub_ssize_t
74 read_vli (grub_file_t file, grub_uint64_t *num)
76 grub_uint8_t buf[VLI_MAX_DIGITS];
77 grub_ssize_t read;
78 grub_size_t dec;
80 read = grub_file_read (file, buf, VLI_MAX_DIGITS);
81 if (read < 0)
82 return -1;
84 dec = decode_vli (buf, read, num);
85 grub_file_seek (file, file->offset - (read - dec));
86 return dec;
89 /* Function xz_dec_run() should consume header and ask for more (XZ_OK)
90 * else file is corrupted (or options not supported) or not xz. */
91 static int
92 test_header (grub_file_t file)
94 grub_xzio_t xzio = file->data;
95 enum xz_ret ret;
97 xzio->buf.in_size = grub_file_read (xzio->file, xzio->inbuf,
98 STREAM_HEADER_SIZE);
100 if (xzio->buf.in_size != STREAM_HEADER_SIZE)
101 return 0;
103 ret = xz_dec_run (xzio->dec, &xzio->buf);
105 if (ret == XZ_FORMAT_ERROR)
106 return 0;
108 if (ret != XZ_OK)
109 return 0;
111 return 1;
114 /* Try to find out size of uncompressed data,
115 * also do some footer sanity checks. */
116 static int
117 test_footer (grub_file_t file)
119 grub_xzio_t xzio = file->data;
120 grub_uint8_t footer[FOOTER_MAGIC_SIZE];
121 grub_uint32_t backsize;
122 grub_uint8_t imarker;
123 grub_uint64_t uncompressed_size_total = 0;
124 grub_uint64_t uncompressed_size;
125 grub_uint64_t records;
127 grub_file_seek (xzio->file, xzio->file->size - FOOTER_MAGIC_SIZE);
128 if (grub_file_read (xzio->file, footer, FOOTER_MAGIC_SIZE)
129 != FOOTER_MAGIC_SIZE
130 || grub_memcmp (footer, FOOTER_MAGIC, FOOTER_MAGIC_SIZE) != 0)
131 goto ERROR;
133 grub_file_seek (xzio->file, xzio->file->size - 8);
134 if (grub_file_read (xzio->file, &backsize, sizeof (backsize))
135 != sizeof (backsize))
136 goto ERROR;
138 /* Calculate real backward size. */
139 backsize = (grub_le_to_cpu32 (backsize) + 1) * 4;
141 /* Set file to the beginning of stream index. */
142 grub_file_seek (xzio->file,
143 xzio->file->size - XZ_STREAM_FOOTER_SIZE - backsize);
145 /* Test index marker. */
146 if (grub_file_read (xzio->file, &imarker, sizeof (imarker))
147 != sizeof (imarker) && imarker != 0x00)
148 goto ERROR;
150 if (read_vli (xzio->file, &records) <= 0)
151 goto ERROR;
153 for (; records != 0; records--)
155 if (read_vli (xzio->file, &uncompressed_size) <= 0) /* Ignore unpadded. */
156 goto ERROR;
157 if (read_vli (xzio->file, &uncompressed_size) <= 0) /* Uncompressed. */
158 goto ERROR;
160 uncompressed_size_total += uncompressed_size;
163 file->size = uncompressed_size_total;
164 grub_file_seek (xzio->file, STREAM_HEADER_SIZE);
165 return 1;
167 ERROR:
168 return 0;
171 static grub_file_t
172 grub_xzio_open (grub_file_t io)
174 grub_file_t file;
175 grub_xzio_t xzio;
177 file = (grub_file_t) grub_zalloc (sizeof (*file));
178 if (!file)
179 return 0;
181 xzio = grub_zalloc (sizeof (*xzio));
182 if (!xzio)
184 grub_free (file);
185 return 0;
188 xzio->file = io;
189 xzio->saved_offset = 0;
191 file->device = io->device;
192 file->offset = 0;
193 file->data = xzio;
194 file->read_hook = 0;
195 file->fs = &grub_xzio_fs;
196 file->size = GRUB_FILE_SIZE_UNKNOWN;
197 file->not_easily_seekable = 1;
199 if (grub_file_tell (xzio->file) != 0)
200 grub_file_seek (xzio->file, 0);
202 /* Allocated 64KiB for dictionary.
203 * Decoder will relocate if bigger is needed. */
204 xzio->dec = xz_dec_init (1 << 16);
205 if (!xzio->dec)
207 grub_free (file);
208 grub_free (xzio);
209 return 0;
212 xzio->buf.in = xzio->inbuf;
213 xzio->buf.in_pos = 0;
214 xzio->buf.in_size = 0;
215 xzio->buf.out = xzio->outbuf;
216 xzio->buf.out_pos = 0;
217 xzio->buf.out_size = XZBUFSIZ;
219 /* FIXME: don't test footer on not easily seekable files. */
220 if (!test_header (file) || !test_footer (file))
222 grub_errno = GRUB_ERR_NONE;
223 grub_file_seek (io, 0);
224 xz_dec_end (xzio->dec);
225 grub_free (xzio);
226 grub_free (file);
228 return io;
231 return file;
234 static grub_ssize_t
235 grub_xzio_read (grub_file_t file, char *buf, grub_size_t len)
237 grub_ssize_t ret = 0;
238 grub_ssize_t readret;
239 enum xz_ret xzret;
240 grub_xzio_t xzio = file->data;
241 grub_off_t current_offset;
243 /* If seek backward need to reset decoder and start from beginning of file.
244 TODO Possible improvement by jumping blocks. */
245 if (file->offset < xzio->saved_offset)
247 xz_dec_reset (xzio->dec);
248 xzio->saved_offset = 0;
249 xzio->buf.out_pos = 0;
250 xzio->buf.in_pos = 0;
251 xzio->buf.in_size = 0;
252 grub_file_seek (xzio->file, 0);
255 current_offset = xzio->saved_offset;
257 while (len > 0)
259 xzio->buf.out_size = file->offset + ret + len - current_offset;
260 if (xzio->buf.out_size > XZBUFSIZ)
261 xzio->buf.out_size = XZBUFSIZ;
262 /* Feed input. */
263 if (xzio->buf.in_pos == xzio->buf.in_size)
265 readret = grub_file_read (xzio->file, xzio->inbuf, XZBUFSIZ);
266 if (readret < 0)
267 return -1;
268 xzio->buf.in_size = readret;
269 xzio->buf.in_pos = 0;
272 xzret = xz_dec_run (xzio->dec, &xzio->buf);
273 switch (xzret)
275 case XZ_MEMLIMIT_ERROR:
276 case XZ_FORMAT_ERROR:
277 case XZ_OPTIONS_ERROR:
278 case XZ_DATA_ERROR:
279 case XZ_BUF_ERROR:
280 grub_error (GRUB_ERR_BAD_COMPRESSED_DATA,
281 N_("xz file corrupted or unsupported block options"));
282 return -1;
283 default:
284 break;
288 grub_off_t new_offset = current_offset + xzio->buf.out_pos;
290 if (file->offset <= new_offset)
291 /* Store first chunk of data in buffer. */
293 grub_size_t delta = new_offset - (file->offset + ret);
294 grub_memmove (buf, xzio->buf.out + (xzio->buf.out_pos - delta),
295 delta);
296 len -= delta;
297 buf += delta;
298 ret += delta;
300 current_offset = new_offset;
302 xzio->buf.out_pos = 0;
304 if (xzret == XZ_STREAM_END) /* Stream end, EOF. */
305 break;
308 if (ret >= 0)
309 xzio->saved_offset = file->offset + ret;
311 return ret;
314 /* Release everything, including the underlying file object. */
315 static grub_err_t
316 grub_xzio_close (grub_file_t file)
318 grub_xzio_t xzio = file->data;
320 xz_dec_end (xzio->dec);
322 grub_file_close (xzio->file);
323 grub_free (xzio);
325 /* Device must not be closed twice. */
326 file->device = 0;
327 return grub_errno;
330 static struct grub_fs grub_xzio_fs = {
331 .name = "xzio",
332 .dir = 0,
333 .open = 0,
334 .read = grub_xzio_read,
335 .close = grub_xzio_close,
336 .label = 0,
337 .next = 0
340 GRUB_MOD_INIT (xzio)
342 grub_file_filter_register (GRUB_FILE_FILTER_XZIO, grub_xzio_open);
345 GRUB_MOD_FINI (xzio)
347 grub_file_filter_unregister (GRUB_FILE_FILTER_XZIO);