| /////////////////////////////////////////////////////////////////////////////// |
| // |
| /// \file list.c |
| /// \brief Listing information about .lzma files |
| // |
| // Copyright (C) 2007 Lasse Collin |
| // |
| // This program is free software; you can redistribute it and/or |
| // modify it under the terms of the GNU Lesser General Public |
| // License as published by the Free Software Foundation; either |
| // version 2.1 of the License, or (at your option) any later version. |
| // |
| // This program 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 |
| // Lesser General Public License for more details. |
| // |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| #include "private.h" |
| |
| |
| /* |
| |
| 1. Check the file type: native, alone, unknown |
| |
| Alone: |
| 1. Show info about header. Don't look for concatenated parts. |
| |
| Native: |
| 1. Check that Stream Header is valid. |
| 2. Seek to the end of the file. |
| 3. Skip padding. |
| 4. Reverse decode Stream Footer. |
| 5. Seek Backward Size bytes. |
| 6. |
| |
| */ |
| |
| |
| static void |
| unsupported_file(file_handle *handle) |
| { |
| errmsg(V_ERROR, "%s: Unsupported file type", handle->name); |
| set_exit_status(ERROR); |
| (void)io_close(handle); |
| return; |
| } |
| |
| |
| /// Primitive escaping function, that escapes only ASCII control characters. |
| static void |
| print_escaped(const uint8_t *str) |
| { |
| while (*str != '\0') { |
| if (*str <= 0x1F || *str == 0x7F) |
| printf("\\x%02X", *str); |
| else |
| putchar(*str); |
| |
| ++str; |
| } |
| |
| return; |
| } |
| |
| |
| static void |
| list_native(file_handle *handle) |
| { |
| lzma_stream strm = LZMA_STREAM_INIT; |
| lzma_stream_flags flags; |
| lzma_ret ret = lzma_stream_header_decoder(&strm, &flags); |
| |
| } |
| |
| |
| static void |
| list_alone(const listing_handle *handle) |
| { |
| if (handle->buffer[0] > (4 * 5 + 4) * 9 + 8) { |
| unsupported_file(handle); |
| return; |
| } |
| |
| const unsigned int pb = handle->buffer[0] / (9 * 5); |
| handle->buffer[0] -= pb * 9 * 5; |
| const unsigned int lp = handle->buffer[0] / 9; |
| const unsigned int lc = handle->buffer[0] - lp * 9; |
| |
| uint32_t dict = 0; |
| for (size_t i = 1; i < 5; ++i) { |
| dict <<= 8; |
| dict |= header[i]; |
| } |
| |
| if (dict > LZMA_DICTIONARY_SIZE_MAX) { |
| unsupported_file(handle); |
| return; |
| } |
| |
| uint64_t uncompressed_size = 0; |
| for (size_t i = 5; i < 13; ++i) { |
| uncompressed_size <<= 8; |
| uncompressed_size |= header[i]; |
| } |
| |
| // Reject files with uncompressed size of 256 GiB or more. It's |
| // an arbitrary limit trying to avoid at least some false positives. |
| if (uncompressed_size != UINT64_MAX |
| && uncompressed_size >= (UINT64_C(1) << 38)) { |
| unsupported_file(handle); |
| return; |
| } |
| |
| if (verbosity < V_WARNING) { |
| printf("name="); |
| print_escaped(handle->name); |
| printf("\nformat=alone\n"); |
| |
| if (uncompressed_size == UINT64_MAX) |
| printf("uncompressed_size=unknown\n"); |
| else |
| printf("uncompressed_size=%" PRIu64 "\n", |
| uncompressed_size); |
| |
| printf("dict=%" PRIu32 "\n", dict); |
| |
| printf("lc=%u\nlp=%u\npb=%u\n\n", lc, lp, pb); |
| |
| } else { |
| printf("File name: "); |
| print_escaped(handle->name); |
| printf("\nFile format: LZMA_Alone\n") |
| |
| printf("Uncompressed size: "); |
| if (uncompressed_size == UINT64_MAX) |
| printf("unknown\n"); |
| else |
| printf("%," PRIu64 " bytes (%" PRIu64 " MiB)\n", |
| uncompressed_size, |
| (uncompressed_size + 1024 * 512) |
| / (1024 * 1024)); |
| |
| printf("Dictionary size: %," PRIu32 " bytes " |
| "(%" PRIu32 " MiB)\n", |
| dict, (dict + 1024 * 512) / (1024 * 1024)); |
| |
| printf("Literal context bits (lc): %u\n", lc); |
| printf("Literal position bits (lc): %u\n", lp); |
| printf("Position bits (pb): %u\n", pb); |
| } |
| |
| return; |
| } |
| |
| |
| |
| |
| typedef struct { |
| const char *filename; |
| struct stat st; |
| int fd; |
| |
| lzma_stream strm; |
| lzma_stream_flags stream_flags; |
| lzma_info *info; |
| |
| lzma_vli backward_size; |
| lzma_vli uncompressed_size; |
| |
| size_t buffer_size; |
| uint8_t buffer[IO_BUFFER_SIZE]; |
| } listing_handle; |
| |
| |
| static bool |
| listing_pread(listing_handle *handle, uint64_t offset) |
| { |
| if (offset >= (uint64_t)(handle->st.st_size)) { |
| errmsg(V_ERROR, "%s: Trying to read past the end of " |
| "the file.", handle->filename); |
| return true; |
| } |
| |
| #ifdef HAVE_PREAD |
| const ssize_t ret = pread(handle->fd, handle->buffer, IO_BUFFER_SIZE, |
| (off_t)(offset)); |
| #else |
| // Use lseek() + read() since we don't have pread(). We don't care |
| // to which offset the reading position is left. |
| if (lseek(handle->fd, (off_t)(offset), SEEK_SET) == -1) { |
| errmsg(V_ERROR, "%s: %s", handle->filename, strerror(errno)); |
| return true; |
| } |
| |
| const ssize_t ret = read(handle->fd, handle->buffer, IO_BUFFER_SIZE); |
| #endif |
| |
| if (ret == -1) { |
| errmsg(V_ERROR, "%s: %s", handle->filename, strerror(errno)); |
| return true; |
| } |
| |
| if (ret == 0) { |
| errmsg(V_ERROR, "%s: Trying to read past the end of " |
| "the file.", handle->filename); |
| return true; |
| } |
| |
| handle->buffer_size = (size_t)(ret); |
| return false; |
| } |
| |
| |
| |
| static bool |
| parse_stream_header(listing_handle *handle) |
| { |
| if (listing_pread(handle, 0)) |
| return true; |
| |
| // TODO Got enough input? |
| |
| lzma_ret ret = lzma_stream_header_decoder( |
| &handle->strm, &handle->stream_flags); |
| if (ret != LZMA_OK) { |
| errmsg(V_ERROR, "%s: %s", handle->name, str_strm_error(ret)); |
| return true; |
| } |
| |
| handle->strm.next_in = handle->buffer; |
| handle->strm.avail_in = handle->buffer_size; |
| ret = lzma_code(&handle->strm, LZMA_RUN); |
| if (ret != LZMA_STREAM_END) { |
| assert(ret != LZMA_OK); |
| errmsg(V_ERROR, "%s: %s", handle->name, str_strm_error(ret)); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| |
| static bool |
| parse_stream_tail(listing_handle *handle) |
| { |
| uint64_t offset = (uint64_t)(handle->st.st_size); |
| |
| // Skip padding |
| do { |
| if (offset == 0) { |
| errmsg(V_ERROR, "%s: %s", handle->name, |
| str_strm_error(LZMA_DATA_ERROR)); |
| return true; |
| } |
| |
| if (offset < IO_BUFFER_SIZE) |
| offset = 0; |
| else |
| offset -= IO_BUFFER_SIZE; |
| |
| if (listing_pread(handle, offset)) |
| return true; |
| |
| while (handle->buffer_size > 0 |
| && handle->buffer[handle->buffer_size - 1] |
| == '\0') |
| --handle->buffer_size; |
| |
| } while (handle->buffer_size == 0); |
| |
| if (handle->buffer_size < LZMA_STREAM_TAIL_SIZE) { |
| // TODO |
| } |
| |
| lzma_stream_flags stream_flags; |
| lzma_ret ret = lzma_stream_tail_decoder(&handle->strm, &stream_flags); |
| if (ret != LZMA_OK) { |
| errmsg(V_ERROR, "%s: %s", handle->name, str_strm_error(ret)); |
| return true; |
| } |
| |
| handle->strm.next_in = handle->buffer + handle->buffer_size |
| - LZMA_STREAM_TAIL_SIZE; |
| handle->strm.avail_in = LZMA_STREAM_TAIL_SIZE; |
| handle->buffer_size -= LZMA_STREAM_TAIL_SIZE; |
| ret = lzma_code(&handle->strm, LZMA_RUN); |
| if (ret != LZMA_OK) { |
| assert(ret != LZMA_OK); |
| errmsg(V_ERROR, "%s: %s", handle->name, str_strm_error(ret)); |
| return true; |
| } |
| |
| if (!lzma_stream_flags_is_equal(handle->stream_flags, stream_flags)) { |
| // TODO |
| // Possibly corrupt, possibly concatenated file. |
| } |
| |
| handle->backward_size = 0; |
| ret = lzma_vli_reverse_decode(&handle->backward_size, handle->buffer, |
| &handle->buffer_size); |
| if (ret != LZMA_OK) { |
| // It may be LZMA_BUF_ERROR too, but it doesn't make sense |
| // as an error message displayed to the user. |
| errmsg(V_ERROR, "%s: %s", handle->name, |
| str_strm_error(LZMA_DATA_ERROR)); |
| return true; |
| } |
| |
| if (!stream_flags.is_multi) { |
| handle->uncompressed_size = 0; |
| size_t tmp = handle->buffer_size; |
| ret = lzma_vli_reverse_decode(&handle->uncompressed_size, |
| handle->buffer, &tmp); |
| if (ret != LZMA_OK) |
| handle->uncompressed_size = LZMA_VLI_VALUE_UNKNOWN; |
| } |
| |
| // Calculate the Header Metadata Block start offset. |
| |
| |
| return false; |
| } |
| |
| |
| |
| static void |
| list_native(listing_handle *handle) |
| { |
| lzma_memory_limitter *limitter |
| = lzma_memory_limitter_create(opt_memory); |
| if (limitter == NULL) { |
| errmsg(V_ERROR, |
| } |
| lzma_info *info = |
| |
| |
| // Parse Stream Header |
| // |
| // Single-Block Stream: |
| // - Parse Block Header |
| // - Parse Stream Footer |
| // - If Backward Size doesn't match, error out |
| // |
| // Multi-Block Stream: |
| // - Parse Header Metadata Block, if any |
| // - Parse Footer Metadata Block |
| // - Parse Stream Footer |
| // - If Footer Metadata Block doesn't match the Stream, error out |
| // |
| // In other words, we don't support concatened files. |
| if (parse_stream_header(handle)) |
| return; |
| |
| if (parse_block_header(handle)) |
| return; |
| |
| if (handle->stream_flags.is_multi) { |
| if (handle->block_options.is_metadata) { |
| if (parse_metadata(handle) |
| return; |
| } |
| |
| if (my_seek(handle, |
| |
| } else { |
| if (handle->block_options.is_metadata) { |
| FILE_IS_CORRUPT(); |
| return; |
| } |
| |
| if (parse_stream_footer(handle)) |
| return; |
| |
| // If Uncompressed Size isn't present in Block Header, |
| // it must be present in Stream Footer. |
| if (handle->block_options.uncompressed_size |
| == LZMA_VLI_VALUE_UNKNOWN |
| && handle->stream_flags.uncompressed_size |
| == LZMA_VLI_VALUE_UNKNOWN) { |
| FILE_IS_CORRUPT(); |
| return; |
| } |
| |
| // Construct a single-Record Index. |
| lzma_index *index = malloc(sizeof(lzma_index)); |
| if (index == NULL) { |
| out_of_memory(); |
| return; |
| } |
| |
| // Pohdintaa: |
| // Jos Block coder hoitaisi Uncompressed ja Backward Sizet, |
| // voisi index->total_sizeksi laittaa suoraan Backward Sizen. |
| index->total_size = |
| |
| if () { |
| |
| } |
| } |
| |
| |
| if (handle->block_options.is_metadata) { |
| if (!handle->stream_flags.is_multi) { |
| FILE_IS_CORRUPT(); |
| return; |
| } |
| |
| if (parse_metadata(handle)) |
| return; |
| |
| } |
| } |
| |
| |
| |
| extern void |
| list(const char *filename) |
| { |
| if (strcmp(filename, "-") == 0) { |
| errmsg(V_ERROR, "%s: --list does not support reading from " |
| "standard input", filename); |
| return; |
| } |
| |
| if (is_empty_filename(filename)) |
| return; |
| |
| listing_handle handle; |
| handle.filename = filename; |
| |
| handle.fd = open(filename, O_RDONLY | O_NOCTTY); |
| if (handle.fd == -1) { |
| errmsg(V_ERROR, "%s: %s", filename, strerror(errno)); |
| return; |
| } |
| |
| if (fstat(handle.fd, &handle.st)) { |
| errmsg(V_ERROR, "%s: %s", filename, strerror(errno)); |
| goto out; |
| } |
| |
| if (!S_ISREG(handle.st.st_mode)) { |
| errmsg(V_WARNING, _("%s: Not a regular file, skipping"), |
| filename); |
| goto out; |
| } |
| |
| if (handle.st.st_size <= 0) { |
| errmsg(V_ERROR, _("%s: File is empty"), filename); |
| goto out; |
| } |
| |
| if (listing_pread(&handle, 0)) |
| goto out; |
| |
| if (handle.buffer[0] == 0xFF) { |
| if (opt_header == HEADER_ALONE) { |
| errmsg(V_ERROR, "%s: FIXME", filename); // FIXME |
| goto out; |
| } |
| |
| list_native(&handle); |
| } else { |
| if (opt_header != HEADER_AUTO && opt_header != HEADER_ALONE) { |
| errmsg(V_ERROR, "%s: FIXME", filename); // FIXME |
| goto out; |
| } |
| |
| list_alone(&handle); |
| } |
| |
| out: |
| (void)close(fd); |
| return; |
| } |