1 /* xzio.c - decompression support for xz */
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/>.
22 #include <grub/misc.h>
23 #include <grub/file.h>
27 GRUB_MOD_LICENSE ("GPLv3+");
30 #include "xz_stream.h"
32 #define XZBUFSIZ 0x2000
33 #define VLI_MAX_DIGITS 9
34 #define XZ_STREAM_FOOTER_SIZE 12
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
;
50 decode_vli (const grub_uint8_t buf
[], grub_size_t size_max
,
56 if (size_max
> VLI_MAX_DIGITS
)
57 size_max
= VLI_MAX_DIGITS
;
62 while (buf
[i
++] & 0x80)
64 if (i
>= size_max
|| buf
[i
] == 0x00)
67 *num
|= (uint64_t) (buf
[i
] & 0x7F) << (i
* 7);
74 read_vli (grub_file_t file
, grub_uint64_t
*num
)
76 grub_uint8_t buf
[VLI_MAX_DIGITS
];
80 read
= grub_file_read (file
, buf
, VLI_MAX_DIGITS
);
84 dec
= decode_vli (buf
, read
, num
);
85 grub_file_seek (file
, file
->offset
- (read
- 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. */
92 test_header (grub_file_t file
)
94 grub_xzio_t xzio
= file
->data
;
97 xzio
->buf
.in_size
= grub_file_read (xzio
->file
, xzio
->inbuf
,
100 if (xzio
->buf
.in_size
!= STREAM_HEADER_SIZE
)
103 ret
= xz_dec_run (xzio
->dec
, &xzio
->buf
);
105 if (ret
== XZ_FORMAT_ERROR
)
114 /* Try to find out size of uncompressed data,
115 * also do some footer sanity checks. */
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
)
130 || grub_memcmp (footer
, FOOTER_MAGIC
, FOOTER_MAGIC_SIZE
) != 0)
133 grub_file_seek (xzio
->file
, xzio
->file
->size
- 8);
134 if (grub_file_read (xzio
->file
, &backsize
, sizeof (backsize
))
135 != sizeof (backsize
))
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)
150 if (read_vli (xzio
->file
, &records
) <= 0)
153 for (; records
!= 0; records
--)
155 if (read_vli (xzio
->file
, &uncompressed_size
) <= 0) /* Ignore unpadded. */
157 if (read_vli (xzio
->file
, &uncompressed_size
) <= 0) /* Uncompressed. */
160 uncompressed_size_total
+= uncompressed_size
;
163 file
->size
= uncompressed_size_total
;
164 grub_file_seek (xzio
->file
, STREAM_HEADER_SIZE
);
172 grub_xzio_open (grub_file_t io
)
177 file
= (grub_file_t
) grub_zalloc (sizeof (*file
));
181 xzio
= grub_zalloc (sizeof (*xzio
));
189 xzio
->saved_offset
= 0;
191 file
->device
= io
->device
;
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);
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
);
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
;
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
;
259 xzio
->buf
.out_size
= file
->offset
+ ret
+ len
- current_offset
;
260 if (xzio
->buf
.out_size
> XZBUFSIZ
)
261 xzio
->buf
.out_size
= XZBUFSIZ
;
263 if (xzio
->buf
.in_pos
== xzio
->buf
.in_size
)
265 readret
= grub_file_read (xzio
->file
, xzio
->inbuf
, XZBUFSIZ
);
268 xzio
->buf
.in_size
= readret
;
269 xzio
->buf
.in_pos
= 0;
272 xzret
= xz_dec_run (xzio
->dec
, &xzio
->buf
);
275 case XZ_MEMLIMIT_ERROR
:
276 case XZ_FORMAT_ERROR
:
277 case XZ_OPTIONS_ERROR
:
280 grub_error (GRUB_ERR_BAD_COMPRESSED_DATA
,
281 N_("xz file corrupted or unsupported block options"));
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
),
300 current_offset
= new_offset
;
302 xzio
->buf
.out_pos
= 0;
304 if (xzret
== XZ_STREAM_END
) /* Stream end, EOF. */
309 xzio
->saved_offset
= file
->offset
+ ret
;
314 /* Release everything, including the underlying file object. */
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
);
325 /* Device must not be closed twice. */
330 static struct grub_fs grub_xzio_fs
= {
334 .read
= grub_xzio_read
,
335 .close
= grub_xzio_close
,
342 grub_file_filter_register (GRUB_FILE_FILTER_XZIO
, grub_xzio_open
);
347 grub_file_filter_unregister (GRUB_FILE_FILTER_XZIO
);