1 ///////////////////////////////////////////////////////////////////////////////
4 /// \brief Listing information about .lzma files
6 // Author: Lasse Collin
8 // This file has been put into the public domain.
9 // You can do whatever you want with this file.
11 ///////////////////////////////////////////////////////////////////////////////
18 1. Check the file type: native, alone, unknown
21 1. Show info about header. Don't look for concatenated parts.
24 1. Check that Stream Header is valid.
25 2. Seek to the end of the file.
27 4. Reverse decode Stream Footer.
28 5. Seek Backward Size bytes.
35 unsupported_file(file_handle *handle)
37 errmsg(V_ERROR, "%s: Unsupported file type", handle->name);
38 set_exit_status(ERROR);
39 (void)io_close(handle);
44 /// Primitive escaping function, that escapes only ASCII control characters.
46 print_escaped(const uint8_t *str)
48 while (*str != '\0') {
49 if (*str <= 0x1F || *str == 0x7F)
50 printf("\\x%02X", *str);
62 list_native(file_handle *handle)
64 lzma_stream strm = LZMA_STREAM_INIT;
65 lzma_stream_flags flags;
66 lzma_ret ret = lzma_stream_header_decoder(&strm, &flags);
72 list_alone(const listing_handle *handle)
74 if (handle->buffer[0] > (4 * 5 + 4) * 9 + 8) {
75 unsupported_file(handle);
79 const unsigned int pb = handle->buffer[0] / (9 * 5);
80 handle->buffer[0] -= pb * 9 * 5;
81 const unsigned int lp = handle->buffer[0] / 9;
82 const unsigned int lc = handle->buffer[0] - lp * 9;
85 for (size_t i = 1; i < 5; ++i) {
90 if (dict > LZMA_DICTIONARY_SIZE_MAX) {
91 unsupported_file(handle);
95 uint64_t uncompressed_size = 0;
96 for (size_t i = 5; i < 13; ++i) {
97 uncompressed_size <<= 8;
98 uncompressed_size |= header[i];
101 // Reject files with uncompressed size of 256 GiB or more. It's
102 // an arbitrary limit trying to avoid at least some false positives.
103 if (uncompressed_size != UINT64_MAX
104 && uncompressed_size >= (UINT64_C(1) << 38)) {
105 unsupported_file(handle);
109 if (verbosity < V_WARNING) {
111 print_escaped(handle->name);
112 printf("\nformat=alone\n");
114 if (uncompressed_size == UINT64_MAX)
115 printf("uncompressed_size=unknown\n");
117 printf("uncompressed_size=%" PRIu64 "\n",
120 printf("dict=%" PRIu32 "\n", dict);
122 printf("lc=%u\nlp=%u\npb=%u\n\n", lc, lp, pb);
125 printf("File name: ");
126 print_escaped(handle->name);
127 printf("\nFile format: LZMA_Alone\n")
129 printf("Uncompressed size: ");
130 if (uncompressed_size == UINT64_MAX)
133 printf("%," PRIu64 " bytes (%" PRIu64 " MiB)\n",
135 (uncompressed_size + 1024 * 512)
138 printf("Dictionary size: %," PRIu32 " bytes "
139 "(%" PRIu32 " MiB)\n",
140 dict, (dict + 1024 * 512) / (1024 * 1024));
142 printf("Literal context bits (lc): %u\n", lc);
143 printf("Literal position bits (lc): %u\n", lp);
144 printf("Position bits (pb): %u\n", pb);
154 const char *filename;
159 lzma_stream_flags stream_flags;
162 lzma_vli backward_size;
163 lzma_vli uncompressed_size;
166 uint8_t buffer[IO_BUFFER_SIZE];
171 listing_pread(listing_handle *handle, uint64_t offset)
173 if (offset >= (uint64_t)(handle->st.st_size)) {
174 errmsg(V_ERROR, "%s: Trying to read past the end of "
175 "the file.", handle->filename);
180 const ssize_t ret = pread(handle->fd, handle->buffer, IO_BUFFER_SIZE,
183 // Use lseek() + read() since we don't have pread(). We don't care
184 // to which offset the reading position is left.
185 if (lseek(handle->fd, (off_t)(offset), SEEK_SET) == -1) {
186 errmsg(V_ERROR, "%s: %s", handle->filename, strerror(errno));
190 const ssize_t ret = read(handle->fd, handle->buffer, IO_BUFFER_SIZE);
194 errmsg(V_ERROR, "%s: %s", handle->filename, strerror(errno));
199 errmsg(V_ERROR, "%s: Trying to read past the end of "
200 "the file.", handle->filename);
204 handle->buffer_size = (size_t)(ret);
211 parse_stream_header(listing_handle *handle)
213 if (listing_pread(handle, 0))
216 // TODO Got enough input?
218 lzma_ret ret = lzma_stream_header_decoder(
219 &handle->strm, &handle->stream_flags);
220 if (ret != LZMA_OK) {
221 errmsg(V_ERROR, "%s: %s", handle->name, str_strm_error(ret));
225 handle->strm.next_in = handle->buffer;
226 handle->strm.avail_in = handle->buffer_size;
227 ret = lzma_code(&handle->strm, LZMA_RUN);
228 if (ret != LZMA_STREAM_END) {
229 assert(ret != LZMA_OK);
230 errmsg(V_ERROR, "%s: %s", handle->name, str_strm_error(ret));
239 parse_stream_tail(listing_handle *handle)
241 uint64_t offset = (uint64_t)(handle->st.st_size);
246 errmsg(V_ERROR, "%s: %s", handle->name,
247 str_strm_error(LZMA_DATA_ERROR));
251 if (offset < IO_BUFFER_SIZE)
254 offset -= IO_BUFFER_SIZE;
256 if (listing_pread(handle, offset))
259 while (handle->buffer_size > 0
260 && handle->buffer[handle->buffer_size - 1]
262 --handle->buffer_size;
264 } while (handle->buffer_size == 0);
266 if (handle->buffer_size < LZMA_STREAM_TAIL_SIZE) {
270 lzma_stream_flags stream_flags;
271 lzma_ret ret = lzma_stream_tail_decoder(&handle->strm, &stream_flags);
272 if (ret != LZMA_OK) {
273 errmsg(V_ERROR, "%s: %s", handle->name, str_strm_error(ret));
277 handle->strm.next_in = handle->buffer + handle->buffer_size
278 - LZMA_STREAM_TAIL_SIZE;
279 handle->strm.avail_in = LZMA_STREAM_TAIL_SIZE;
280 handle->buffer_size -= LZMA_STREAM_TAIL_SIZE;
281 ret = lzma_code(&handle->strm, LZMA_RUN);
282 if (ret != LZMA_OK) {
283 assert(ret != LZMA_OK);
284 errmsg(V_ERROR, "%s: %s", handle->name, str_strm_error(ret));
288 if (!lzma_stream_flags_is_equal(handle->stream_flags, stream_flags)) {
290 // Possibly corrupt, possibly concatenated file.
293 handle->backward_size = 0;
294 ret = lzma_vli_reverse_decode(&handle->backward_size, handle->buffer,
295 &handle->buffer_size);
296 if (ret != LZMA_OK) {
297 // It may be LZMA_BUF_ERROR too, but it doesn't make sense
298 // as an error message displayed to the user.
299 errmsg(V_ERROR, "%s: %s", handle->name,
300 str_strm_error(LZMA_DATA_ERROR));
304 if (!stream_flags.is_multi) {
305 handle->uncompressed_size = 0;
306 size_t tmp = handle->buffer_size;
307 ret = lzma_vli_reverse_decode(&handle->uncompressed_size,
308 handle->buffer, &tmp);
310 handle->uncompressed_size = LZMA_VLI_UNKNOWN;
313 // Calculate the Header Metadata Block start offset.
322 list_native(listing_handle *handle)
324 lzma_memory_limiter *limiter
325 = lzma_memory_limiter_create(opt_memory);
326 if (limiter == NULL) {
332 // Parse Stream Header
334 // Single-Block Stream:
335 // - Parse Block Header
336 // - Parse Stream Footer
337 // - If Backward Size doesn't match, error out
339 // Multi-Block Stream:
340 // - Parse Header Metadata Block, if any
341 // - Parse Footer Metadata Block
342 // - Parse Stream Footer
343 // - If Footer Metadata Block doesn't match the Stream, error out
345 // In other words, we don't support concatened files.
346 if (parse_stream_header(handle))
349 if (parse_block_header(handle))
352 if (handle->stream_flags.is_multi) {
353 if (handle->block_options.is_metadata) {
354 if (parse_metadata(handle)
361 if (handle->block_options.is_metadata) {
366 if (parse_stream_footer(handle))
369 // If Uncompressed Size isn't present in Block Header,
370 // it must be present in Stream Footer.
371 if (handle->block_options.uncompressed_size
373 && handle->stream_flags.uncompressed_size
374 == LZMA_VLI_UNKNOWN) {
379 // Construct a single-Record Index.
380 lzma_index *index = malloc(sizeof(lzma_index));
387 // Jos Block coder hoitaisi Uncompressed ja Backward Sizet,
388 // voisi index->total_sizeksi laittaa suoraan Backward Sizen.
397 if (handle->block_options.is_metadata) {
398 if (!handle->stream_flags.is_multi) {
403 if (parse_metadata(handle))
412 list(const char *filename)
414 if (strcmp(filename, "-") == 0) {
415 errmsg(V_ERROR, "%s: --list does not support reading from "
416 "standard input", filename);
420 if (is_empty_filename(filename))
423 listing_handle handle;
424 handle.filename = filename;
426 handle.fd = open(filename, O_RDONLY | O_NOCTTY);
427 if (handle.fd == -1) {
428 errmsg(V_ERROR, "%s: %s", filename, strerror(errno));
432 if (fstat(handle.fd, &handle.st)) {
433 errmsg(V_ERROR, "%s: %s", filename, strerror(errno));
437 if (!S_ISREG(handle.st.st_mode)) {
438 errmsg(V_WARNING, _("%s: Not a regular file, skipping"),
443 if (handle.st.st_size <= 0) {
444 errmsg(V_ERROR, _("%s: File is empty"), filename);
448 if (listing_pread(&handle, 0))
451 if (handle.buffer[0] == 0xFF) {
452 if (opt_header == HEADER_ALONE) {
453 errmsg(V_ERROR, "%s: FIXME", filename); // FIXME
457 list_native(&handle);
459 if (opt_header != HEADER_AUTO && opt_header != HEADER_ALONE) {
460 errmsg(V_ERROR, "%s: FIXME", filename); // FIXME