/////////////////////////////////////////////////////////////////////////////// // /// \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_UNKNOWN; } // Calculate the Header Metadata Block start offset. return false; } static void list_native(listing_handle *handle) { lzma_memory_limiter *limiter = lzma_memory_limiter_create(opt_memory); if (limiter == 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_UNKNOWN && handle->stream_flags.uncompressed_size == LZMA_VLI_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; }