aboutsummaryrefslogblamecommitdiff
path: root/src/xz/list.c
blob: ba298e43c63e9b4936332554c8a258a421b1bc6e (plain) (tree)
1
2
3
4
5
6
7
8
9




                                                                               
                            
  

                                                  











































































































































































































































































































                                                                               
                                                                     












                                                            


                                                                 












































                                                                          
                                                           
                                                                         
                                                              































































































                                                                              
///////////////////////////////////////////////////////////////////////////////
//
/// \file       list.c
/// \brief      Listing information about .lzma files
//
//  Author:     Lasse Collin
//
//  This file has been put into the public domain.
//  You can do whatever you want with this file.
//
///////////////////////////////////////////////////////////////////////////////

#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;
}