xref: /minix/external/public-domain/xz/dist/src/xz/list.c (revision 0a6a1f1d)
15a645f22SBen Gras ///////////////////////////////////////////////////////////////////////////////
25a645f22SBen Gras //
35a645f22SBen Gras /// \file       list.c
45a645f22SBen Gras /// \brief      Listing information about .xz files
55a645f22SBen Gras //
65a645f22SBen Gras //  Author:     Lasse Collin
75a645f22SBen Gras //
85a645f22SBen Gras //  This file has been put into the public domain.
95a645f22SBen Gras //  You can do whatever you want with this file.
105a645f22SBen Gras //
115a645f22SBen Gras ///////////////////////////////////////////////////////////////////////////////
125a645f22SBen Gras 
135a645f22SBen Gras #include "private.h"
145a645f22SBen Gras #include "tuklib_integer.h"
155a645f22SBen Gras 
165a645f22SBen Gras 
175a645f22SBen Gras /// Information about a .xz file
185a645f22SBen Gras typedef struct {
195a645f22SBen Gras 	/// Combined Index of all Streams in the file
205a645f22SBen Gras 	lzma_index *idx;
215a645f22SBen Gras 
225a645f22SBen Gras 	/// Total amount of Stream Padding
235a645f22SBen Gras 	uint64_t stream_padding;
245a645f22SBen Gras 
255a645f22SBen Gras 	/// Highest memory usage so far
265a645f22SBen Gras 	uint64_t memusage_max;
275a645f22SBen Gras 
285a645f22SBen Gras 	/// True if all Blocks so far have Compressed Size and
295a645f22SBen Gras 	/// Uncompressed Size fields
305a645f22SBen Gras 	bool all_have_sizes;
315a645f22SBen Gras 
32*0a6a1f1dSLionel Sambuc 	/// Oldest XZ Utils version that will decompress the file
33*0a6a1f1dSLionel Sambuc 	uint32_t min_version;
34*0a6a1f1dSLionel Sambuc 
355a645f22SBen Gras } xz_file_info;
365a645f22SBen Gras 
37*0a6a1f1dSLionel Sambuc #define XZ_FILE_INFO_INIT { NULL, 0, 0, true, 50000002 }
385a645f22SBen Gras 
395a645f22SBen Gras 
405a645f22SBen Gras /// Information about a .xz Block
415a645f22SBen Gras typedef struct {
425a645f22SBen Gras 	/// Size of the Block Header
435a645f22SBen Gras 	uint32_t header_size;
445a645f22SBen Gras 
455a645f22SBen Gras 	/// A few of the Block Flags as a string
465a645f22SBen Gras 	char flags[3];
475a645f22SBen Gras 
485a645f22SBen Gras 	/// Size of the Compressed Data field in the Block
495a645f22SBen Gras 	lzma_vli compressed_size;
505a645f22SBen Gras 
515a645f22SBen Gras 	/// Decoder memory usage for this Block
525a645f22SBen Gras 	uint64_t memusage;
535a645f22SBen Gras 
545a645f22SBen Gras 	/// The filter chain of this Block in human-readable form
555a645f22SBen Gras 	char filter_chain[FILTERS_STR_SIZE];
565a645f22SBen Gras 
575a645f22SBen Gras } block_header_info;
585a645f22SBen Gras 
595a645f22SBen Gras 
605a645f22SBen Gras /// Check ID to string mapping
615a645f22SBen Gras static const char check_names[LZMA_CHECK_ID_MAX + 1][12] = {
625a645f22SBen Gras 	// TRANSLATORS: Indicates that there is no integrity check.
635a645f22SBen Gras 	// This string is used in tables, so the width must not
645a645f22SBen Gras 	// exceed ten columns with a fixed-width font.
655a645f22SBen Gras 	N_("None"),
665a645f22SBen Gras 	"CRC32",
675a645f22SBen Gras 	// TRANSLATORS: Indicates that integrity check name is not known,
685a645f22SBen Gras 	// but the Check ID is known (here 2). This and other "Unknown-N"
695a645f22SBen Gras 	// strings are used in tables, so the width must not exceed ten
705a645f22SBen Gras 	// columns with a fixed-width font. It's OK to omit the dash if
715a645f22SBen Gras 	// you need space for one extra letter, but don't use spaces.
725a645f22SBen Gras 	N_("Unknown-2"),
735a645f22SBen Gras 	N_("Unknown-3"),
745a645f22SBen Gras 	"CRC64",
755a645f22SBen Gras 	N_("Unknown-5"),
765a645f22SBen Gras 	N_("Unknown-6"),
775a645f22SBen Gras 	N_("Unknown-7"),
785a645f22SBen Gras 	N_("Unknown-8"),
795a645f22SBen Gras 	N_("Unknown-9"),
805a645f22SBen Gras 	"SHA-256",
815a645f22SBen Gras 	N_("Unknown-11"),
825a645f22SBen Gras 	N_("Unknown-12"),
835a645f22SBen Gras 	N_("Unknown-13"),
845a645f22SBen Gras 	N_("Unknown-14"),
855a645f22SBen Gras 	N_("Unknown-15"),
865a645f22SBen Gras };
875a645f22SBen Gras 
885a645f22SBen Gras /// Buffer size for get_check_names(). This may be a bit ridiculous,
895a645f22SBen Gras /// but at least it's enough if some language needs many multibyte chars.
905a645f22SBen Gras #define CHECKS_STR_SIZE 1024
915a645f22SBen Gras 
925a645f22SBen Gras 
935a645f22SBen Gras /// Value of the Check field as hexadecimal string.
945a645f22SBen Gras /// This is set by parse_check_value().
955a645f22SBen Gras static char check_value[2 * LZMA_CHECK_SIZE_MAX + 1];
965a645f22SBen Gras 
975a645f22SBen Gras 
985a645f22SBen Gras /// Totals that are displayed if there was more than one file.
995a645f22SBen Gras /// The "files" counter is also used in print_info_adv() to show
1005a645f22SBen Gras /// the file number.
1015a645f22SBen Gras static struct {
1025a645f22SBen Gras 	uint64_t files;
1035a645f22SBen Gras 	uint64_t streams;
1045a645f22SBen Gras 	uint64_t blocks;
1055a645f22SBen Gras 	uint64_t compressed_size;
1065a645f22SBen Gras 	uint64_t uncompressed_size;
1075a645f22SBen Gras 	uint64_t stream_padding;
1085a645f22SBen Gras 	uint64_t memusage_max;
1095a645f22SBen Gras 	uint32_t checks;
110*0a6a1f1dSLionel Sambuc 	uint32_t min_version;
1115a645f22SBen Gras 	bool all_have_sizes;
112*0a6a1f1dSLionel Sambuc } totals = { 0, 0, 0, 0, 0, 0, 0, 0, 0, true };
113*0a6a1f1dSLionel Sambuc 
114*0a6a1f1dSLionel Sambuc 
115*0a6a1f1dSLionel Sambuc /// Convert XZ Utils version number to a string.
116*0a6a1f1dSLionel Sambuc static const char *
xz_ver_to_str(uint32_t ver)117*0a6a1f1dSLionel Sambuc xz_ver_to_str(uint32_t ver)
118*0a6a1f1dSLionel Sambuc {
119*0a6a1f1dSLionel Sambuc 	static char buf[32];
120*0a6a1f1dSLionel Sambuc 
121*0a6a1f1dSLionel Sambuc 	unsigned int major = ver / 10000000U;
122*0a6a1f1dSLionel Sambuc 	ver -= major * 10000000U;
123*0a6a1f1dSLionel Sambuc 
124*0a6a1f1dSLionel Sambuc 	unsigned int minor = ver / 10000U;
125*0a6a1f1dSLionel Sambuc 	ver -= minor * 10000U;
126*0a6a1f1dSLionel Sambuc 
127*0a6a1f1dSLionel Sambuc 	unsigned int patch = ver / 10U;
128*0a6a1f1dSLionel Sambuc 	ver -= patch * 10U;
129*0a6a1f1dSLionel Sambuc 
130*0a6a1f1dSLionel Sambuc 	const char *stability = ver == 0 ? "alpha" : ver == 1 ? "beta" : "";
131*0a6a1f1dSLionel Sambuc 
132*0a6a1f1dSLionel Sambuc 	snprintf(buf, sizeof(buf), "%u.%u.%u%s",
133*0a6a1f1dSLionel Sambuc 			major, minor, patch, stability);
134*0a6a1f1dSLionel Sambuc 	return buf;
135*0a6a1f1dSLionel Sambuc }
1365a645f22SBen Gras 
1375a645f22SBen Gras 
1385a645f22SBen Gras /// \brief      Parse the Index(es) from the given .xz file
1395a645f22SBen Gras ///
1405a645f22SBen Gras /// \param      xfi     Pointer to structure where the decoded information
1415a645f22SBen Gras ///                     is stored.
1425a645f22SBen Gras /// \param      pair    Input file
1435a645f22SBen Gras ///
1445a645f22SBen Gras /// \return     On success, false is returned. On error, true is returned.
1455a645f22SBen Gras ///
1465a645f22SBen Gras // TODO: This function is pretty big. liblzma should have a function that
1475a645f22SBen Gras // takes a callback function to parse the Index(es) from a .xz file to make
1485a645f22SBen Gras // it easy for applications.
1495a645f22SBen Gras static bool
parse_indexes(xz_file_info * xfi,file_pair * pair)1505a645f22SBen Gras parse_indexes(xz_file_info *xfi, file_pair *pair)
1515a645f22SBen Gras {
1525a645f22SBen Gras 	if (pair->src_st.st_size <= 0) {
1535a645f22SBen Gras 		message_error(_("%s: File is empty"), pair->src_name);
1545a645f22SBen Gras 		return true;
1555a645f22SBen Gras 	}
1565a645f22SBen Gras 
1575a645f22SBen Gras 	if (pair->src_st.st_size < 2 * LZMA_STREAM_HEADER_SIZE) {
1585a645f22SBen Gras 		message_error(_("%s: Too small to be a valid .xz file"),
1595a645f22SBen Gras 				pair->src_name);
1605a645f22SBen Gras 		return true;
1615a645f22SBen Gras 	}
1625a645f22SBen Gras 
1635a645f22SBen Gras 	io_buf buf;
1645a645f22SBen Gras 	lzma_stream_flags header_flags;
1655a645f22SBen Gras 	lzma_stream_flags footer_flags;
1665a645f22SBen Gras 	lzma_ret ret;
1675a645f22SBen Gras 
1685a645f22SBen Gras 	// lzma_stream for the Index decoder
1695a645f22SBen Gras 	lzma_stream strm = LZMA_STREAM_INIT;
1705a645f22SBen Gras 
1715a645f22SBen Gras 	// All Indexes decoded so far
1725a645f22SBen Gras 	lzma_index *combined_index = NULL;
1735a645f22SBen Gras 
1745a645f22SBen Gras 	// The Index currently being decoded
1755a645f22SBen Gras 	lzma_index *this_index = NULL;
1765a645f22SBen Gras 
1775a645f22SBen Gras 	// Current position in the file. We parse the file backwards so
1785a645f22SBen Gras 	// initialize it to point to the end of the file.
1795a645f22SBen Gras 	off_t pos = pair->src_st.st_size;
1805a645f22SBen Gras 
1815a645f22SBen Gras 	// Each loop iteration decodes one Index.
1825a645f22SBen Gras 	do {
1835a645f22SBen Gras 		// Check that there is enough data left to contain at least
1845a645f22SBen Gras 		// the Stream Header and Stream Footer. This check cannot
1855a645f22SBen Gras 		// fail in the first pass of this loop.
1865a645f22SBen Gras 		if (pos < 2 * LZMA_STREAM_HEADER_SIZE) {
1875a645f22SBen Gras 			message_error("%s: %s", pair->src_name,
1885a645f22SBen Gras 					message_strm(LZMA_DATA_ERROR));
1895a645f22SBen Gras 			goto error;
1905a645f22SBen Gras 		}
1915a645f22SBen Gras 
1925a645f22SBen Gras 		pos -= LZMA_STREAM_HEADER_SIZE;
1935a645f22SBen Gras 		lzma_vli stream_padding = 0;
1945a645f22SBen Gras 
1955a645f22SBen Gras 		// Locate the Stream Footer. There may be Stream Padding which
1965a645f22SBen Gras 		// we must skip when reading backwards.
1975a645f22SBen Gras 		while (true) {
1985a645f22SBen Gras 			if (pos < LZMA_STREAM_HEADER_SIZE) {
1995a645f22SBen Gras 				message_error("%s: %s", pair->src_name,
2005a645f22SBen Gras 						message_strm(
2015a645f22SBen Gras 							LZMA_DATA_ERROR));
2025a645f22SBen Gras 				goto error;
2035a645f22SBen Gras 			}
2045a645f22SBen Gras 
2055a645f22SBen Gras 			if (io_pread(pair, &buf,
2065a645f22SBen Gras 					LZMA_STREAM_HEADER_SIZE, pos))
2075a645f22SBen Gras 				goto error;
2085a645f22SBen Gras 
2095a645f22SBen Gras 			// Stream Padding is always a multiple of four bytes.
2105a645f22SBen Gras 			int i = 2;
2115a645f22SBen Gras 			if (buf.u32[i] != 0)
2125a645f22SBen Gras 				break;
2135a645f22SBen Gras 
2145a645f22SBen Gras 			// To avoid calling io_pread() for every four bytes
2155a645f22SBen Gras 			// of Stream Padding, take advantage that we read
2165a645f22SBen Gras 			// 12 bytes (LZMA_STREAM_HEADER_SIZE) already and
2175a645f22SBen Gras 			// check them too before calling io_pread() again.
2185a645f22SBen Gras 			do {
2195a645f22SBen Gras 				stream_padding += 4;
2205a645f22SBen Gras 				pos -= 4;
2215a645f22SBen Gras 				--i;
2225a645f22SBen Gras 			} while (i >= 0 && buf.u32[i] == 0);
2235a645f22SBen Gras 		}
2245a645f22SBen Gras 
2255a645f22SBen Gras 		// Decode the Stream Footer.
2265a645f22SBen Gras 		ret = lzma_stream_footer_decode(&footer_flags, buf.u8);
2275a645f22SBen Gras 		if (ret != LZMA_OK) {
2285a645f22SBen Gras 			message_error("%s: %s", pair->src_name,
2295a645f22SBen Gras 					message_strm(ret));
2305a645f22SBen Gras 			goto error;
2315a645f22SBen Gras 		}
2325a645f22SBen Gras 
233*0a6a1f1dSLionel Sambuc 		// Check that the Stream Footer doesn't specify something
234*0a6a1f1dSLionel Sambuc 		// that we don't support. This can only happen if the xz
235*0a6a1f1dSLionel Sambuc 		// version is older than liblzma and liblzma supports
236*0a6a1f1dSLionel Sambuc 		// something new.
237*0a6a1f1dSLionel Sambuc 		//
238*0a6a1f1dSLionel Sambuc 		// It is enough to check Stream Footer. Stream Header must
239*0a6a1f1dSLionel Sambuc 		// match when it is compared against Stream Footer with
240*0a6a1f1dSLionel Sambuc 		// lzma_stream_flags_compare().
241*0a6a1f1dSLionel Sambuc 		if (footer_flags.version != 0) {
242*0a6a1f1dSLionel Sambuc 			message_error("%s: %s", pair->src_name,
243*0a6a1f1dSLionel Sambuc 					message_strm(LZMA_OPTIONS_ERROR));
244*0a6a1f1dSLionel Sambuc 			goto error;
245*0a6a1f1dSLionel Sambuc 		}
246*0a6a1f1dSLionel Sambuc 
2475a645f22SBen Gras 		// Check that the size of the Index field looks sane.
2485a645f22SBen Gras 		lzma_vli index_size = footer_flags.backward_size;
2495a645f22SBen Gras 		if ((lzma_vli)(pos) < index_size + LZMA_STREAM_HEADER_SIZE) {
2505a645f22SBen Gras 			message_error("%s: %s", pair->src_name,
2515a645f22SBen Gras 					message_strm(LZMA_DATA_ERROR));
2525a645f22SBen Gras 			goto error;
2535a645f22SBen Gras 		}
2545a645f22SBen Gras 
2555a645f22SBen Gras 		// Set pos to the beginning of the Index.
2565a645f22SBen Gras 		pos -= index_size;
2575a645f22SBen Gras 
2585a645f22SBen Gras 		// See how much memory we can use for decoding this Index.
2595a645f22SBen Gras 		uint64_t memlimit = hardware_memlimit_get(MODE_LIST);
2605a645f22SBen Gras 		uint64_t memused = 0;
2615a645f22SBen Gras 		if (combined_index != NULL) {
2625a645f22SBen Gras 			memused = lzma_index_memused(combined_index);
2635a645f22SBen Gras 			if (memused > memlimit)
2645a645f22SBen Gras 				message_bug();
2655a645f22SBen Gras 
2665a645f22SBen Gras 			memlimit -= memused;
2675a645f22SBen Gras 		}
2685a645f22SBen Gras 
2695a645f22SBen Gras 		// Decode the Index.
2705a645f22SBen Gras 		ret = lzma_index_decoder(&strm, &this_index, memlimit);
2715a645f22SBen Gras 		if (ret != LZMA_OK) {
2725a645f22SBen Gras 			message_error("%s: %s", pair->src_name,
2735a645f22SBen Gras 					message_strm(ret));
2745a645f22SBen Gras 			goto error;
2755a645f22SBen Gras 		}
2765a645f22SBen Gras 
2775a645f22SBen Gras 		do {
2785a645f22SBen Gras 			// Don't give the decoder more input than the
2795a645f22SBen Gras 			// Index size.
2805a645f22SBen Gras 			strm.avail_in = my_min(IO_BUFFER_SIZE, index_size);
2815a645f22SBen Gras 			if (io_pread(pair, &buf, strm.avail_in, pos))
2825a645f22SBen Gras 				goto error;
2835a645f22SBen Gras 
2845a645f22SBen Gras 			pos += strm.avail_in;
2855a645f22SBen Gras 			index_size -= strm.avail_in;
2865a645f22SBen Gras 
2875a645f22SBen Gras 			strm.next_in = buf.u8;
2885a645f22SBen Gras 			ret = lzma_code(&strm, LZMA_RUN);
2895a645f22SBen Gras 
2905a645f22SBen Gras 		} while (ret == LZMA_OK);
2915a645f22SBen Gras 
2925a645f22SBen Gras 		// If the decoding seems to be successful, check also that
2935a645f22SBen Gras 		// the Index decoder consumed as much input as indicated
2945a645f22SBen Gras 		// by the Backward Size field.
2955a645f22SBen Gras 		if (ret == LZMA_STREAM_END)
2965a645f22SBen Gras 			if (index_size != 0 || strm.avail_in != 0)
2975a645f22SBen Gras 				ret = LZMA_DATA_ERROR;
2985a645f22SBen Gras 
2995a645f22SBen Gras 		if (ret != LZMA_STREAM_END) {
3005a645f22SBen Gras 			// LZMA_BUFFER_ERROR means that the Index decoder
3015a645f22SBen Gras 			// would have liked more input than what the Index
3025a645f22SBen Gras 			// size should be according to Stream Footer.
3035a645f22SBen Gras 			// The message for LZMA_DATA_ERROR makes more
3045a645f22SBen Gras 			// sense in that case.
3055a645f22SBen Gras 			if (ret == LZMA_BUF_ERROR)
3065a645f22SBen Gras 				ret = LZMA_DATA_ERROR;
3075a645f22SBen Gras 
3085a645f22SBen Gras 			message_error("%s: %s", pair->src_name,
3095a645f22SBen Gras 					message_strm(ret));
3105a645f22SBen Gras 
3115a645f22SBen Gras 			// If the error was too low memory usage limit,
3125a645f22SBen Gras 			// show also how much memory would have been needed.
3135a645f22SBen Gras 			if (ret == LZMA_MEMLIMIT_ERROR) {
3145a645f22SBen Gras 				uint64_t needed = lzma_memusage(&strm);
3155a645f22SBen Gras 				if (UINT64_MAX - needed < memused)
3165a645f22SBen Gras 					needed = UINT64_MAX;
3175a645f22SBen Gras 				else
3185a645f22SBen Gras 					needed += memused;
3195a645f22SBen Gras 
3205a645f22SBen Gras 				message_mem_needed(V_ERROR, needed);
3215a645f22SBen Gras 			}
3225a645f22SBen Gras 
3235a645f22SBen Gras 			goto error;
3245a645f22SBen Gras 		}
3255a645f22SBen Gras 
3265a645f22SBen Gras 		// Decode the Stream Header and check that its Stream Flags
3275a645f22SBen Gras 		// match the Stream Footer.
3285a645f22SBen Gras 		pos -= footer_flags.backward_size + LZMA_STREAM_HEADER_SIZE;
3295a645f22SBen Gras 		if ((lzma_vli)(pos) < lzma_index_total_size(this_index)) {
3305a645f22SBen Gras 			message_error("%s: %s", pair->src_name,
3315a645f22SBen Gras 					message_strm(LZMA_DATA_ERROR));
3325a645f22SBen Gras 			goto error;
3335a645f22SBen Gras 		}
3345a645f22SBen Gras 
3355a645f22SBen Gras 		pos -= lzma_index_total_size(this_index);
3365a645f22SBen Gras 		if (io_pread(pair, &buf, LZMA_STREAM_HEADER_SIZE, pos))
3375a645f22SBen Gras 			goto error;
3385a645f22SBen Gras 
3395a645f22SBen Gras 		ret = lzma_stream_header_decode(&header_flags, buf.u8);
3405a645f22SBen Gras 		if (ret != LZMA_OK) {
3415a645f22SBen Gras 			message_error("%s: %s", pair->src_name,
3425a645f22SBen Gras 					message_strm(ret));
3435a645f22SBen Gras 			goto error;
3445a645f22SBen Gras 		}
3455a645f22SBen Gras 
3465a645f22SBen Gras 		ret = lzma_stream_flags_compare(&header_flags, &footer_flags);
3475a645f22SBen Gras 		if (ret != LZMA_OK) {
3485a645f22SBen Gras 			message_error("%s: %s", pair->src_name,
3495a645f22SBen Gras 					message_strm(ret));
3505a645f22SBen Gras 			goto error;
3515a645f22SBen Gras 		}
3525a645f22SBen Gras 
3535a645f22SBen Gras 		// Store the decoded Stream Flags into this_index. This is
3545a645f22SBen Gras 		// needed so that we can print which Check is used in each
3555a645f22SBen Gras 		// Stream.
3565a645f22SBen Gras 		ret = lzma_index_stream_flags(this_index, &footer_flags);
3575a645f22SBen Gras 		if (ret != LZMA_OK)
3585a645f22SBen Gras 			message_bug();
3595a645f22SBen Gras 
3605a645f22SBen Gras 		// Store also the size of the Stream Padding field. It is
3615a645f22SBen Gras 		// needed to show the offsets of the Streams correctly.
3625a645f22SBen Gras 		ret = lzma_index_stream_padding(this_index, stream_padding);
3635a645f22SBen Gras 		if (ret != LZMA_OK)
3645a645f22SBen Gras 			message_bug();
3655a645f22SBen Gras 
3665a645f22SBen Gras 		if (combined_index != NULL) {
3675a645f22SBen Gras 			// Append the earlier decoded Indexes
3685a645f22SBen Gras 			// after this_index.
3695a645f22SBen Gras 			ret = lzma_index_cat(
3705a645f22SBen Gras 					this_index, combined_index, NULL);
3715a645f22SBen Gras 			if (ret != LZMA_OK) {
3725a645f22SBen Gras 				message_error("%s: %s", pair->src_name,
3735a645f22SBen Gras 						message_strm(ret));
3745a645f22SBen Gras 				goto error;
3755a645f22SBen Gras 			}
3765a645f22SBen Gras 		}
3775a645f22SBen Gras 
3785a645f22SBen Gras 		combined_index = this_index;
3795a645f22SBen Gras 		this_index = NULL;
3805a645f22SBen Gras 
3815a645f22SBen Gras 		xfi->stream_padding += stream_padding;
3825a645f22SBen Gras 
3835a645f22SBen Gras 	} while (pos > 0);
3845a645f22SBen Gras 
3855a645f22SBen Gras 	lzma_end(&strm);
3865a645f22SBen Gras 
3875a645f22SBen Gras 	// All OK. Make combined_index available to the caller.
3885a645f22SBen Gras 	xfi->idx = combined_index;
3895a645f22SBen Gras 	return false;
3905a645f22SBen Gras 
3915a645f22SBen Gras error:
3925a645f22SBen Gras 	// Something went wrong, free the allocated memory.
3935a645f22SBen Gras 	lzma_end(&strm);
3945a645f22SBen Gras 	lzma_index_end(combined_index, NULL);
3955a645f22SBen Gras 	lzma_index_end(this_index, NULL);
3965a645f22SBen Gras 	return true;
3975a645f22SBen Gras }
3985a645f22SBen Gras 
3995a645f22SBen Gras 
4005a645f22SBen Gras /// \brief      Parse the Block Header
4015a645f22SBen Gras ///
4025a645f22SBen Gras /// The result is stored into *bhi. The caller takes care of initializing it.
4035a645f22SBen Gras ///
4045a645f22SBen Gras /// \return     False on success, true on error.
4055a645f22SBen Gras static bool
parse_block_header(file_pair * pair,const lzma_index_iter * iter,block_header_info * bhi,xz_file_info * xfi)4065a645f22SBen Gras parse_block_header(file_pair *pair, const lzma_index_iter *iter,
4075a645f22SBen Gras 		block_header_info *bhi, xz_file_info *xfi)
4085a645f22SBen Gras {
4095a645f22SBen Gras #if IO_BUFFER_SIZE < LZMA_BLOCK_HEADER_SIZE_MAX
4105a645f22SBen Gras #	error IO_BUFFER_SIZE < LZMA_BLOCK_HEADER_SIZE_MAX
4115a645f22SBen Gras #endif
4125a645f22SBen Gras 
4135a645f22SBen Gras 	// Get the whole Block Header with one read, but don't read past
4145a645f22SBen Gras 	// the end of the Block (or even its Check field).
4155a645f22SBen Gras 	const uint32_t size = my_min(iter->block.total_size
4165a645f22SBen Gras 				- lzma_check_size(iter->stream.flags->check),
4175a645f22SBen Gras 			LZMA_BLOCK_HEADER_SIZE_MAX);
4185a645f22SBen Gras 	io_buf buf;
4195a645f22SBen Gras 	if (io_pread(pair, &buf, size, iter->block.compressed_file_offset))
4205a645f22SBen Gras 		return true;
4215a645f22SBen Gras 
4225a645f22SBen Gras 	// Zero would mean Index Indicator and thus not a valid Block.
4235a645f22SBen Gras 	if (buf.u8[0] == 0)
4245a645f22SBen Gras 		goto data_error;
4255a645f22SBen Gras 
4265a645f22SBen Gras 	// Initialize the block structure and decode Block Header Size.
427*0a6a1f1dSLionel Sambuc 	lzma_filter filters[LZMA_FILTERS_MAX + 1];
428*0a6a1f1dSLionel Sambuc 	lzma_block block;
4295a645f22SBen Gras 	block.version = 0;
4305a645f22SBen Gras 	block.check = iter->stream.flags->check;
4315a645f22SBen Gras 	block.filters = filters;
4325a645f22SBen Gras 
4335a645f22SBen Gras 	block.header_size = lzma_block_header_size_decode(buf.u8[0]);
4345a645f22SBen Gras 	if (block.header_size > size)
4355a645f22SBen Gras 		goto data_error;
4365a645f22SBen Gras 
4375a645f22SBen Gras 	// Decode the Block Header.
4385a645f22SBen Gras 	switch (lzma_block_header_decode(&block, NULL, buf.u8)) {
4395a645f22SBen Gras 	case LZMA_OK:
4405a645f22SBen Gras 		break;
4415a645f22SBen Gras 
4425a645f22SBen Gras 	case LZMA_OPTIONS_ERROR:
4435a645f22SBen Gras 		message_error("%s: %s", pair->src_name,
4445a645f22SBen Gras 				message_strm(LZMA_OPTIONS_ERROR));
4455a645f22SBen Gras 		return true;
4465a645f22SBen Gras 
4475a645f22SBen Gras 	case LZMA_DATA_ERROR:
4485a645f22SBen Gras 		goto data_error;
4495a645f22SBen Gras 
4505a645f22SBen Gras 	default:
4515a645f22SBen Gras 		message_bug();
4525a645f22SBen Gras 	}
4535a645f22SBen Gras 
4545a645f22SBen Gras 	// Check the Block Flags. These must be done before calling
4555a645f22SBen Gras 	// lzma_block_compressed_size(), because it overwrites
4565a645f22SBen Gras 	// block.compressed_size.
4575a645f22SBen Gras 	bhi->flags[0] = block.compressed_size != LZMA_VLI_UNKNOWN
4585a645f22SBen Gras 			? 'c' : '-';
4595a645f22SBen Gras 	bhi->flags[1] = block.uncompressed_size != LZMA_VLI_UNKNOWN
4605a645f22SBen Gras 			? 'u' : '-';
4615a645f22SBen Gras 	bhi->flags[2] = '\0';
4625a645f22SBen Gras 
4635a645f22SBen Gras 	// Collect information if all Blocks have both Compressed Size
4645a645f22SBen Gras 	// and Uncompressed Size fields. They can be useful e.g. for
4655a645f22SBen Gras 	// multi-threaded decompression so it can be useful to know it.
4665a645f22SBen Gras 	xfi->all_have_sizes &= block.compressed_size != LZMA_VLI_UNKNOWN
4675a645f22SBen Gras 			&& block.uncompressed_size != LZMA_VLI_UNKNOWN;
4685a645f22SBen Gras 
4695a645f22SBen Gras 	// Validate or set block.compressed_size.
4705a645f22SBen Gras 	switch (lzma_block_compressed_size(&block,
4715a645f22SBen Gras 			iter->block.unpadded_size)) {
4725a645f22SBen Gras 	case LZMA_OK:
473*0a6a1f1dSLionel Sambuc 		// Validate also block.uncompressed_size if it is present.
474*0a6a1f1dSLionel Sambuc 		// If it isn't present, there's no need to set it since
475*0a6a1f1dSLionel Sambuc 		// we aren't going to actually decompress the Block; if
476*0a6a1f1dSLionel Sambuc 		// we were decompressing, then we should set it so that
477*0a6a1f1dSLionel Sambuc 		// the Block decoder could validate the Uncompressed Size
478*0a6a1f1dSLionel Sambuc 		// that was stored in the Index.
479*0a6a1f1dSLionel Sambuc 		if (block.uncompressed_size == LZMA_VLI_UNKNOWN
480*0a6a1f1dSLionel Sambuc 				|| block.uncompressed_size
481*0a6a1f1dSLionel Sambuc 					== iter->block.uncompressed_size)
4825a645f22SBen Gras 			break;
4835a645f22SBen Gras 
484*0a6a1f1dSLionel Sambuc 		// If the above fails, the file is corrupt so
485*0a6a1f1dSLionel Sambuc 		// LZMA_DATA_ERROR is a good error code.
486*0a6a1f1dSLionel Sambuc 
4875a645f22SBen Gras 	case LZMA_DATA_ERROR:
488*0a6a1f1dSLionel Sambuc 		// Free the memory allocated by lzma_block_header_decode().
489*0a6a1f1dSLionel Sambuc 		for (size_t i = 0; filters[i].id != LZMA_VLI_UNKNOWN; ++i)
490*0a6a1f1dSLionel Sambuc 			free(filters[i].options);
491*0a6a1f1dSLionel Sambuc 
4925a645f22SBen Gras 		goto data_error;
4935a645f22SBen Gras 
4945a645f22SBen Gras 	default:
4955a645f22SBen Gras 		message_bug();
4965a645f22SBen Gras 	}
4975a645f22SBen Gras 
4985a645f22SBen Gras 	// Copy the known sizes.
4995a645f22SBen Gras 	bhi->header_size = block.header_size;
5005a645f22SBen Gras 	bhi->compressed_size = block.compressed_size;
5015a645f22SBen Gras 
5025a645f22SBen Gras 	// Calculate the decoder memory usage and update the maximum
5035a645f22SBen Gras 	// memory usage of this Block.
5045a645f22SBen Gras 	bhi->memusage = lzma_raw_decoder_memusage(filters);
5055a645f22SBen Gras 	if (xfi->memusage_max < bhi->memusage)
5065a645f22SBen Gras 		xfi->memusage_max = bhi->memusage;
5075a645f22SBen Gras 
508*0a6a1f1dSLionel Sambuc 	// Determine the minimum XZ Utils version that supports this Block.
509*0a6a1f1dSLionel Sambuc 	//
510*0a6a1f1dSLionel Sambuc 	// Currently the only thing that 5.0.0 doesn't support is empty
511*0a6a1f1dSLionel Sambuc 	// LZMA2 Block. This decoder bug was fixed in 5.0.2.
512*0a6a1f1dSLionel Sambuc 	{
513*0a6a1f1dSLionel Sambuc 		size_t i = 0;
514*0a6a1f1dSLionel Sambuc 		while (filters[i + 1].id != LZMA_VLI_UNKNOWN)
515*0a6a1f1dSLionel Sambuc 			++i;
516*0a6a1f1dSLionel Sambuc 
517*0a6a1f1dSLionel Sambuc 		if (filters[i].id == LZMA_FILTER_LZMA2
518*0a6a1f1dSLionel Sambuc 				&& iter->block.uncompressed_size == 0
519*0a6a1f1dSLionel Sambuc 				&& xfi->min_version < 50000022U)
520*0a6a1f1dSLionel Sambuc 			xfi->min_version = 50000022U;
521*0a6a1f1dSLionel Sambuc 	}
522*0a6a1f1dSLionel Sambuc 
5235a645f22SBen Gras 	// Convert the filter chain to human readable form.
5245a645f22SBen Gras 	message_filters_to_str(bhi->filter_chain, filters, false);
5255a645f22SBen Gras 
5265a645f22SBen Gras 	// Free the memory allocated by lzma_block_header_decode().
5275a645f22SBen Gras 	for (size_t i = 0; filters[i].id != LZMA_VLI_UNKNOWN; ++i)
5285a645f22SBen Gras 		free(filters[i].options);
5295a645f22SBen Gras 
5305a645f22SBen Gras 	return false;
5315a645f22SBen Gras 
5325a645f22SBen Gras data_error:
5335a645f22SBen Gras 	// Show the error message.
5345a645f22SBen Gras 	message_error("%s: %s", pair->src_name,
5355a645f22SBen Gras 			message_strm(LZMA_DATA_ERROR));
5365a645f22SBen Gras 	return true;
5375a645f22SBen Gras }
5385a645f22SBen Gras 
5395a645f22SBen Gras 
5405a645f22SBen Gras /// \brief      Parse the Check field and put it into check_value[]
5415a645f22SBen Gras ///
5425a645f22SBen Gras /// \return     False on success, true on error.
5435a645f22SBen Gras static bool
parse_check_value(file_pair * pair,const lzma_index_iter * iter)5445a645f22SBen Gras parse_check_value(file_pair *pair, const lzma_index_iter *iter)
5455a645f22SBen Gras {
5465a645f22SBen Gras 	// Don't read anything from the file if there is no integrity Check.
5475a645f22SBen Gras 	if (iter->stream.flags->check == LZMA_CHECK_NONE) {
5485a645f22SBen Gras 		snprintf(check_value, sizeof(check_value), "---");
5495a645f22SBen Gras 		return false;
5505a645f22SBen Gras 	}
5515a645f22SBen Gras 
5525a645f22SBen Gras 	// Locate and read the Check field.
5535a645f22SBen Gras 	const uint32_t size = lzma_check_size(iter->stream.flags->check);
5545a645f22SBen Gras 	const off_t offset = iter->block.compressed_file_offset
5555a645f22SBen Gras 			+ iter->block.total_size - size;
5565a645f22SBen Gras 	io_buf buf;
5575a645f22SBen Gras 	if (io_pread(pair, &buf, size, offset))
5585a645f22SBen Gras 		return true;
5595a645f22SBen Gras 
5605a645f22SBen Gras 	// CRC32 and CRC64 are in little endian. Guess that all the future
5615a645f22SBen Gras 	// 32-bit and 64-bit Check values are little endian too. It shouldn't
5625a645f22SBen Gras 	// be a too big problem if this guess is wrong.
5635a645f22SBen Gras 	if (size == 4)
5645a645f22SBen Gras 		snprintf(check_value, sizeof(check_value),
5655a645f22SBen Gras 				"%08" PRIx32, conv32le(buf.u32[0]));
5665a645f22SBen Gras 	else if (size == 8)
5675a645f22SBen Gras 		snprintf(check_value, sizeof(check_value),
5685a645f22SBen Gras 				"%016" PRIx64, conv64le(buf.u64[0]));
5695a645f22SBen Gras 	else
5705a645f22SBen Gras 		for (size_t i = 0; i < size; ++i)
5715a645f22SBen Gras 			snprintf(check_value + i * 2, 3, "%02x", buf.u8[i]);
5725a645f22SBen Gras 
5735a645f22SBen Gras 	return false;
5745a645f22SBen Gras }
5755a645f22SBen Gras 
5765a645f22SBen Gras 
5775a645f22SBen Gras /// \brief      Parse detailed information about a Block
5785a645f22SBen Gras ///
5795a645f22SBen Gras /// Since this requires seek(s), listing information about all Blocks can
5805a645f22SBen Gras /// be slow.
5815a645f22SBen Gras ///
5825a645f22SBen Gras /// \param      pair    Input file
5835a645f22SBen Gras /// \param      iter    Location of the Block whose Check value should
5845a645f22SBen Gras ///                     be printed.
5855a645f22SBen Gras /// \param      bhi     Pointer to structure where to store the information
5865a645f22SBen Gras ///                     about the Block Header field.
5875a645f22SBen Gras ///
5885a645f22SBen Gras /// \return     False on success, true on error. If an error occurs,
5895a645f22SBen Gras ///             the error message is printed too so the caller doesn't
5905a645f22SBen Gras ///             need to worry about that.
5915a645f22SBen Gras static bool
parse_details(file_pair * pair,const lzma_index_iter * iter,block_header_info * bhi,xz_file_info * xfi)5925a645f22SBen Gras parse_details(file_pair *pair, const lzma_index_iter *iter,
5935a645f22SBen Gras 		block_header_info *bhi, xz_file_info *xfi)
5945a645f22SBen Gras {
5955a645f22SBen Gras 	if (parse_block_header(pair, iter, bhi, xfi))
5965a645f22SBen Gras 		return true;
5975a645f22SBen Gras 
5985a645f22SBen Gras 	if (parse_check_value(pair, iter))
5995a645f22SBen Gras 		return true;
6005a645f22SBen Gras 
6015a645f22SBen Gras 	return false;
6025a645f22SBen Gras }
6035a645f22SBen Gras 
6045a645f22SBen Gras 
6055a645f22SBen Gras /// \brief      Get the compression ratio
6065a645f22SBen Gras ///
6075a645f22SBen Gras /// This has slightly different format than that is used in message.c.
6085a645f22SBen Gras static const char *
get_ratio(uint64_t compressed_size,uint64_t uncompressed_size)6095a645f22SBen Gras get_ratio(uint64_t compressed_size, uint64_t uncompressed_size)
6105a645f22SBen Gras {
6115a645f22SBen Gras 	if (uncompressed_size == 0)
6125a645f22SBen Gras 		return "---";
6135a645f22SBen Gras 
6145a645f22SBen Gras 	const double ratio = (double)(compressed_size)
6155a645f22SBen Gras 			/ (double)(uncompressed_size);
6165a645f22SBen Gras 	if (ratio > 9.999)
6175a645f22SBen Gras 		return "---";
6185a645f22SBen Gras 
6195a645f22SBen Gras 	static char buf[16];
6205a645f22SBen Gras 	snprintf(buf, sizeof(buf), "%.3f", ratio);
6215a645f22SBen Gras 	return buf;
6225a645f22SBen Gras }
6235a645f22SBen Gras 
6245a645f22SBen Gras 
6255a645f22SBen Gras /// \brief      Get a comma-separated list of Check names
6265a645f22SBen Gras ///
6275a645f22SBen Gras /// The check names are translated with gettext except when in robot mode.
6285a645f22SBen Gras ///
6295a645f22SBen Gras /// \param      buf     Buffer to hold the resulting string
6305a645f22SBen Gras /// \param      checks  Bit mask of Checks to print
6315a645f22SBen Gras /// \param      space_after_comma
6325a645f22SBen Gras ///                     It's better to not use spaces in table-like listings,
6335a645f22SBen Gras ///                     but in more verbose formats a space after a comma
6345a645f22SBen Gras ///                     is good for readability.
6355a645f22SBen Gras static void
get_check_names(char buf[CHECKS_STR_SIZE],uint32_t checks,bool space_after_comma)6365a645f22SBen Gras get_check_names(char buf[CHECKS_STR_SIZE],
6375a645f22SBen Gras 		uint32_t checks, bool space_after_comma)
6385a645f22SBen Gras {
6395a645f22SBen Gras 	assert(checks != 0);
6405a645f22SBen Gras 
6415a645f22SBen Gras 	char *pos = buf;
6425a645f22SBen Gras 	size_t left = CHECKS_STR_SIZE;
6435a645f22SBen Gras 
6445a645f22SBen Gras 	const char *sep = space_after_comma ? ", " : ",";
6455a645f22SBen Gras 	bool comma = false;
6465a645f22SBen Gras 
6475a645f22SBen Gras 	for (size_t i = 0; i <= LZMA_CHECK_ID_MAX; ++i) {
6485a645f22SBen Gras 		if (checks & (UINT32_C(1) << i)) {
6495a645f22SBen Gras 			my_snprintf(&pos, &left, "%s%s",
6505a645f22SBen Gras 					comma ? sep : "",
6515a645f22SBen Gras 					opt_robot ? check_names[i]
6525a645f22SBen Gras 						: _(check_names[i]));
6535a645f22SBen Gras 			comma = true;
6545a645f22SBen Gras 		}
6555a645f22SBen Gras 	}
6565a645f22SBen Gras 
6575a645f22SBen Gras 	return;
6585a645f22SBen Gras }
6595a645f22SBen Gras 
6605a645f22SBen Gras 
6615a645f22SBen Gras static bool
print_info_basic(const xz_file_info * xfi,file_pair * pair)6625a645f22SBen Gras print_info_basic(const xz_file_info *xfi, file_pair *pair)
6635a645f22SBen Gras {
6645a645f22SBen Gras 	static bool headings_displayed = false;
6655a645f22SBen Gras 	if (!headings_displayed) {
6665a645f22SBen Gras 		headings_displayed = true;
6675a645f22SBen Gras 		// TRANSLATORS: These are column headings. From Strms (Streams)
6685a645f22SBen Gras 		// to Ratio, the columns are right aligned. Check and Filename
6695a645f22SBen Gras 		// are left aligned. If you need longer words, it's OK to
6705a645f22SBen Gras 		// use two lines here. Test with "xz -l foo.xz".
6715a645f22SBen Gras 		puts(_("Strms  Blocks   Compressed Uncompressed  Ratio  "
6725a645f22SBen Gras 				"Check   Filename"));
6735a645f22SBen Gras 	}
6745a645f22SBen Gras 
6755a645f22SBen Gras 	char checks[CHECKS_STR_SIZE];
6765a645f22SBen Gras 	get_check_names(checks, lzma_index_checks(xfi->idx), false);
6775a645f22SBen Gras 
6785a645f22SBen Gras 	const char *cols[7] = {
6795a645f22SBen Gras 		uint64_to_str(lzma_index_stream_count(xfi->idx), 0),
6805a645f22SBen Gras 		uint64_to_str(lzma_index_block_count(xfi->idx), 1),
6815a645f22SBen Gras 		uint64_to_nicestr(lzma_index_file_size(xfi->idx),
6825a645f22SBen Gras 			NICESTR_B, NICESTR_TIB, false, 2),
6835a645f22SBen Gras 		uint64_to_nicestr(lzma_index_uncompressed_size(xfi->idx),
6845a645f22SBen Gras 			NICESTR_B, NICESTR_TIB, false, 3),
6855a645f22SBen Gras 		get_ratio(lzma_index_file_size(xfi->idx),
6865a645f22SBen Gras 			lzma_index_uncompressed_size(xfi->idx)),
6875a645f22SBen Gras 		checks,
6885a645f22SBen Gras 		pair->src_name,
6895a645f22SBen Gras 	};
6905a645f22SBen Gras 	printf("%*s %*s  %*s  %*s  %*s  %-*s %s\n",
6915a645f22SBen Gras 			tuklib_mbstr_fw(cols[0], 5), cols[0],
6925a645f22SBen Gras 			tuklib_mbstr_fw(cols[1], 7), cols[1],
6935a645f22SBen Gras 			tuklib_mbstr_fw(cols[2], 11), cols[2],
6945a645f22SBen Gras 			tuklib_mbstr_fw(cols[3], 11), cols[3],
6955a645f22SBen Gras 			tuklib_mbstr_fw(cols[4], 5), cols[4],
6965a645f22SBen Gras 			tuklib_mbstr_fw(cols[5], 7), cols[5],
6975a645f22SBen Gras 			cols[6]);
6985a645f22SBen Gras 
6995a645f22SBen Gras 	return false;
7005a645f22SBen Gras }
7015a645f22SBen Gras 
7025a645f22SBen Gras 
7035a645f22SBen Gras static void
print_adv_helper(uint64_t stream_count,uint64_t block_count,uint64_t compressed_size,uint64_t uncompressed_size,uint32_t checks,uint64_t stream_padding)7045a645f22SBen Gras print_adv_helper(uint64_t stream_count, uint64_t block_count,
7055a645f22SBen Gras 		uint64_t compressed_size, uint64_t uncompressed_size,
7065a645f22SBen Gras 		uint32_t checks, uint64_t stream_padding)
7075a645f22SBen Gras {
7085a645f22SBen Gras 	char checks_str[CHECKS_STR_SIZE];
7095a645f22SBen Gras 	get_check_names(checks_str, checks, true);
7105a645f22SBen Gras 
7115a645f22SBen Gras 	printf(_("  Streams:            %s\n"),
7125a645f22SBen Gras 			uint64_to_str(stream_count, 0));
7135a645f22SBen Gras 	printf(_("  Blocks:             %s\n"),
7145a645f22SBen Gras 			uint64_to_str(block_count, 0));
7155a645f22SBen Gras 	printf(_("  Compressed size:    %s\n"),
7165a645f22SBen Gras 			uint64_to_nicestr(compressed_size,
7175a645f22SBen Gras 				NICESTR_B, NICESTR_TIB, true, 0));
7185a645f22SBen Gras 	printf(_("  Uncompressed size:  %s\n"),
7195a645f22SBen Gras 			uint64_to_nicestr(uncompressed_size,
7205a645f22SBen Gras 				NICESTR_B, NICESTR_TIB, true, 0));
7215a645f22SBen Gras 	printf(_("  Ratio:              %s\n"),
7225a645f22SBen Gras 			get_ratio(compressed_size, uncompressed_size));
7235a645f22SBen Gras 	printf(_("  Check:              %s\n"), checks_str);
7245a645f22SBen Gras 	printf(_("  Stream padding:     %s\n"),
7255a645f22SBen Gras 			uint64_to_nicestr(stream_padding,
7265a645f22SBen Gras 				NICESTR_B, NICESTR_TIB, true, 0));
7275a645f22SBen Gras 	return;
7285a645f22SBen Gras }
7295a645f22SBen Gras 
7305a645f22SBen Gras 
7315a645f22SBen Gras static bool
print_info_adv(xz_file_info * xfi,file_pair * pair)7325a645f22SBen Gras print_info_adv(xz_file_info *xfi, file_pair *pair)
7335a645f22SBen Gras {
7345a645f22SBen Gras 	// Print the overall information.
7355a645f22SBen Gras 	print_adv_helper(lzma_index_stream_count(xfi->idx),
7365a645f22SBen Gras 			lzma_index_block_count(xfi->idx),
7375a645f22SBen Gras 			lzma_index_file_size(xfi->idx),
7385a645f22SBen Gras 			lzma_index_uncompressed_size(xfi->idx),
7395a645f22SBen Gras 			lzma_index_checks(xfi->idx),
7405a645f22SBen Gras 			xfi->stream_padding);
7415a645f22SBen Gras 
7425a645f22SBen Gras 	// Size of the biggest Check. This is used to calculate the width
7435a645f22SBen Gras 	// of the CheckVal field. The table would get insanely wide if
7445a645f22SBen Gras 	// we always reserved space for 64-byte Check (128 chars as hex).
7455a645f22SBen Gras 	uint32_t check_max = 0;
7465a645f22SBen Gras 
7475a645f22SBen Gras 	// Print information about the Streams.
7485a645f22SBen Gras 	//
7495a645f22SBen Gras 	// TRANSLATORS: The second line is column headings. All except
7505a645f22SBen Gras 	// Check are right aligned; Check is left aligned. Test with
7515a645f22SBen Gras 	// "xz -lv foo.xz".
7525a645f22SBen Gras 	puts(_("  Streams:\n    Stream    Blocks"
7535a645f22SBen Gras 			"      CompOffset    UncompOffset"
7545a645f22SBen Gras 			"        CompSize      UncompSize  Ratio"
7555a645f22SBen Gras 			"  Check      Padding"));
7565a645f22SBen Gras 
7575a645f22SBen Gras 	lzma_index_iter iter;
7585a645f22SBen Gras 	lzma_index_iter_init(&iter, xfi->idx);
7595a645f22SBen Gras 
7605a645f22SBen Gras 	while (!lzma_index_iter_next(&iter, LZMA_INDEX_ITER_STREAM)) {
7615a645f22SBen Gras 		const char *cols1[4] = {
7625a645f22SBen Gras 			uint64_to_str(iter.stream.number, 0),
7635a645f22SBen Gras 			uint64_to_str(iter.stream.block_count, 1),
7645a645f22SBen Gras 			uint64_to_str(iter.stream.compressed_offset, 2),
7655a645f22SBen Gras 			uint64_to_str(iter.stream.uncompressed_offset, 3),
7665a645f22SBen Gras 		};
7675a645f22SBen Gras 		printf("    %*s %*s %*s %*s ",
7685a645f22SBen Gras 				tuklib_mbstr_fw(cols1[0], 6), cols1[0],
7695a645f22SBen Gras 				tuklib_mbstr_fw(cols1[1], 9), cols1[1],
7705a645f22SBen Gras 				tuklib_mbstr_fw(cols1[2], 15), cols1[2],
7715a645f22SBen Gras 				tuklib_mbstr_fw(cols1[3], 15), cols1[3]);
7725a645f22SBen Gras 
7735a645f22SBen Gras 		const char *cols2[5] = {
7745a645f22SBen Gras 			uint64_to_str(iter.stream.compressed_size, 0),
7755a645f22SBen Gras 			uint64_to_str(iter.stream.uncompressed_size, 1),
7765a645f22SBen Gras 			get_ratio(iter.stream.compressed_size,
7775a645f22SBen Gras 				iter.stream.uncompressed_size),
7785a645f22SBen Gras 			_(check_names[iter.stream.flags->check]),
7795a645f22SBen Gras 			uint64_to_str(iter.stream.padding, 2),
7805a645f22SBen Gras 		};
7815a645f22SBen Gras 		printf("%*s %*s  %*s  %-*s %*s\n",
7825a645f22SBen Gras 				tuklib_mbstr_fw(cols2[0], 15), cols2[0],
7835a645f22SBen Gras 				tuklib_mbstr_fw(cols2[1], 15), cols2[1],
7845a645f22SBen Gras 				tuklib_mbstr_fw(cols2[2], 5), cols2[2],
7855a645f22SBen Gras 				tuklib_mbstr_fw(cols2[3], 10), cols2[3],
7865a645f22SBen Gras 				tuklib_mbstr_fw(cols2[4], 7), cols2[4]);
7875a645f22SBen Gras 
7885a645f22SBen Gras 		// Update the maximum Check size.
7895a645f22SBen Gras 		if (lzma_check_size(iter.stream.flags->check) > check_max)
7905a645f22SBen Gras 			check_max = lzma_check_size(iter.stream.flags->check);
7915a645f22SBen Gras 	}
7925a645f22SBen Gras 
7935a645f22SBen Gras 	// Cache the verbosity level to a local variable.
7945a645f22SBen Gras 	const bool detailed = message_verbosity_get() >= V_DEBUG;
7955a645f22SBen Gras 
7965a645f22SBen Gras 	// Information collected from Block Headers
7975a645f22SBen Gras 	block_header_info bhi;
7985a645f22SBen Gras 
7995a645f22SBen Gras 	// Print information about the Blocks but only if there is
8005a645f22SBen Gras 	// at least one Block.
8015a645f22SBen Gras 	if (lzma_index_block_count(xfi->idx) > 0) {
8025a645f22SBen Gras 		// Calculate the width of the CheckVal field.
8035a645f22SBen Gras 		const int checkval_width = my_max(8, 2 * check_max);
8045a645f22SBen Gras 
8055a645f22SBen Gras 		// TRANSLATORS: The second line is column headings. All
8065a645f22SBen Gras 		// except Check are right aligned; Check is left aligned.
8075a645f22SBen Gras 		printf(_("  Blocks:\n    Stream     Block"
8085a645f22SBen Gras 			"      CompOffset    UncompOffset"
8095a645f22SBen Gras 			"       TotalSize      UncompSize  Ratio  Check"));
8105a645f22SBen Gras 
8115a645f22SBen Gras 		if (detailed) {
8125a645f22SBen Gras 			// TRANSLATORS: These are additional column headings
8135a645f22SBen Gras 			// for the most verbose listing mode. CheckVal
8145a645f22SBen Gras 			// (Check value), Flags, and Filters are left aligned.
8155a645f22SBen Gras 			// Header (Block Header Size), CompSize, and MemUsage
8165a645f22SBen Gras 			// are right aligned. %*s is replaced with 0-120
8175a645f22SBen Gras 			// spaces to make the CheckVal column wide enough.
8185a645f22SBen Gras 			// Test with "xz -lvv foo.xz".
8195a645f22SBen Gras 			printf(_("      CheckVal %*s Header  Flags        "
8205a645f22SBen Gras 					"CompSize    MemUsage  Filters"),
8215a645f22SBen Gras 					checkval_width - 8, "");
8225a645f22SBen Gras 		}
8235a645f22SBen Gras 
8245a645f22SBen Gras 		putchar('\n');
8255a645f22SBen Gras 
8265a645f22SBen Gras 		lzma_index_iter_init(&iter, xfi->idx);
8275a645f22SBen Gras 
8285a645f22SBen Gras 		// Iterate over the Blocks.
8295a645f22SBen Gras 		while (!lzma_index_iter_next(&iter, LZMA_INDEX_ITER_BLOCK)) {
8305a645f22SBen Gras 			if (detailed && parse_details(pair, &iter, &bhi, xfi))
8315a645f22SBen Gras 					return true;
8325a645f22SBen Gras 
8335a645f22SBen Gras 			const char *cols1[4] = {
8345a645f22SBen Gras 				uint64_to_str(iter.stream.number, 0),
8355a645f22SBen Gras 				uint64_to_str(
8365a645f22SBen Gras 					iter.block.number_in_stream, 1),
8375a645f22SBen Gras 				uint64_to_str(
8385a645f22SBen Gras 					iter.block.compressed_file_offset, 2),
8395a645f22SBen Gras 				uint64_to_str(
8405a645f22SBen Gras 					iter.block.uncompressed_file_offset, 3)
8415a645f22SBen Gras 			};
8425a645f22SBen Gras 			printf("    %*s %*s %*s %*s ",
8435a645f22SBen Gras 				tuklib_mbstr_fw(cols1[0], 6), cols1[0],
8445a645f22SBen Gras 				tuklib_mbstr_fw(cols1[1], 9), cols1[1],
8455a645f22SBen Gras 				tuklib_mbstr_fw(cols1[2], 15), cols1[2],
8465a645f22SBen Gras 				tuklib_mbstr_fw(cols1[3], 15), cols1[3]);
8475a645f22SBen Gras 
8485a645f22SBen Gras 			const char *cols2[4] = {
8495a645f22SBen Gras 				uint64_to_str(iter.block.total_size, 0),
8505a645f22SBen Gras 				uint64_to_str(iter.block.uncompressed_size,
8515a645f22SBen Gras 						1),
8525a645f22SBen Gras 				get_ratio(iter.block.total_size,
8535a645f22SBen Gras 					iter.block.uncompressed_size),
8545a645f22SBen Gras 				_(check_names[iter.stream.flags->check])
8555a645f22SBen Gras 			};
8565a645f22SBen Gras 			printf("%*s %*s  %*s  %-*s",
8575a645f22SBen Gras 				tuklib_mbstr_fw(cols2[0], 15), cols2[0],
8585a645f22SBen Gras 				tuklib_mbstr_fw(cols2[1], 15), cols2[1],
8595a645f22SBen Gras 				tuklib_mbstr_fw(cols2[2], 5), cols2[2],
8605a645f22SBen Gras 				tuklib_mbstr_fw(cols2[3], detailed ? 11 : 1),
8615a645f22SBen Gras 					cols2[3]);
8625a645f22SBen Gras 
8635a645f22SBen Gras 			if (detailed) {
8645a645f22SBen Gras 				const lzma_vli compressed_size
8655a645f22SBen Gras 						= iter.block.unpadded_size
8665a645f22SBen Gras 						- bhi.header_size
8675a645f22SBen Gras 						- lzma_check_size(
8685a645f22SBen Gras 						iter.stream.flags->check);
8695a645f22SBen Gras 
8705a645f22SBen Gras 				const char *cols3[6] = {
8715a645f22SBen Gras 					check_value,
8725a645f22SBen Gras 					uint64_to_str(bhi.header_size, 0),
8735a645f22SBen Gras 					bhi.flags,
8745a645f22SBen Gras 					uint64_to_str(compressed_size, 1),
8755a645f22SBen Gras 					uint64_to_str(
8765a645f22SBen Gras 						round_up_to_mib(bhi.memusage),
8775a645f22SBen Gras 						2),
8785a645f22SBen Gras 					bhi.filter_chain
8795a645f22SBen Gras 				};
8805a645f22SBen Gras 				// Show MiB for memory usage, because it
8815a645f22SBen Gras 				// is the only size which is not in bytes.
8825a645f22SBen Gras 				printf("%-*s  %*s  %-5s %*s %*s MiB  %s",
8835a645f22SBen Gras 					checkval_width, cols3[0],
8845a645f22SBen Gras 					tuklib_mbstr_fw(cols3[1], 6), cols3[1],
8855a645f22SBen Gras 					cols3[2],
8865a645f22SBen Gras 					tuklib_mbstr_fw(cols3[3], 15),
8875a645f22SBen Gras 						cols3[3],
8885a645f22SBen Gras 					tuklib_mbstr_fw(cols3[4], 7), cols3[4],
8895a645f22SBen Gras 					cols3[5]);
8905a645f22SBen Gras 			}
8915a645f22SBen Gras 
8925a645f22SBen Gras 			putchar('\n');
8935a645f22SBen Gras 		}
8945a645f22SBen Gras 	}
8955a645f22SBen Gras 
8965a645f22SBen Gras 	if (detailed) {
8975a645f22SBen Gras 		printf(_("  Memory needed:      %s MiB\n"), uint64_to_str(
8985a645f22SBen Gras 				round_up_to_mib(xfi->memusage_max), 0));
8995a645f22SBen Gras 		printf(_("  Sizes in headers:   %s\n"),
9005a645f22SBen Gras 				xfi->all_have_sizes ? _("Yes") : _("No"));
901*0a6a1f1dSLionel Sambuc 		printf(_("  Minimum XZ Utils version: %s\n"),
902*0a6a1f1dSLionel Sambuc 				xz_ver_to_str(xfi->min_version));
9035a645f22SBen Gras 	}
9045a645f22SBen Gras 
9055a645f22SBen Gras 	return false;
9065a645f22SBen Gras }
9075a645f22SBen Gras 
9085a645f22SBen Gras 
9095a645f22SBen Gras static bool
print_info_robot(xz_file_info * xfi,file_pair * pair)9105a645f22SBen Gras print_info_robot(xz_file_info *xfi, file_pair *pair)
9115a645f22SBen Gras {
9125a645f22SBen Gras 	char checks[CHECKS_STR_SIZE];
9135a645f22SBen Gras 	get_check_names(checks, lzma_index_checks(xfi->idx), false);
9145a645f22SBen Gras 
9155a645f22SBen Gras 	printf("name\t%s\n", pair->src_name);
9165a645f22SBen Gras 
9175a645f22SBen Gras 	printf("file\t%" PRIu64 "\t%" PRIu64 "\t%" PRIu64 "\t%" PRIu64
9185a645f22SBen Gras 			"\t%s\t%s\t%" PRIu64 "\n",
9195a645f22SBen Gras 			lzma_index_stream_count(xfi->idx),
9205a645f22SBen Gras 			lzma_index_block_count(xfi->idx),
9215a645f22SBen Gras 			lzma_index_file_size(xfi->idx),
9225a645f22SBen Gras 			lzma_index_uncompressed_size(xfi->idx),
9235a645f22SBen Gras 			get_ratio(lzma_index_file_size(xfi->idx),
9245a645f22SBen Gras 				lzma_index_uncompressed_size(xfi->idx)),
9255a645f22SBen Gras 			checks,
9265a645f22SBen Gras 			xfi->stream_padding);
9275a645f22SBen Gras 
9285a645f22SBen Gras 	if (message_verbosity_get() >= V_VERBOSE) {
9295a645f22SBen Gras 		lzma_index_iter iter;
9305a645f22SBen Gras 		lzma_index_iter_init(&iter, xfi->idx);
9315a645f22SBen Gras 
9325a645f22SBen Gras 		while (!lzma_index_iter_next(&iter, LZMA_INDEX_ITER_STREAM))
9335a645f22SBen Gras 			printf("stream\t%" PRIu64 "\t%" PRIu64 "\t%" PRIu64
9345a645f22SBen Gras 				"\t%" PRIu64 "\t%" PRIu64 "\t%" PRIu64
9355a645f22SBen Gras 				"\t%s\t%s\t%" PRIu64 "\n",
9365a645f22SBen Gras 				iter.stream.number,
9375a645f22SBen Gras 				iter.stream.block_count,
9385a645f22SBen Gras 				iter.stream.compressed_offset,
9395a645f22SBen Gras 				iter.stream.uncompressed_offset,
9405a645f22SBen Gras 				iter.stream.compressed_size,
9415a645f22SBen Gras 				iter.stream.uncompressed_size,
9425a645f22SBen Gras 				get_ratio(iter.stream.compressed_size,
9435a645f22SBen Gras 					iter.stream.uncompressed_size),
9445a645f22SBen Gras 				check_names[iter.stream.flags->check],
9455a645f22SBen Gras 				iter.stream.padding);
9465a645f22SBen Gras 
9475a645f22SBen Gras 		lzma_index_iter_rewind(&iter);
9485a645f22SBen Gras 		block_header_info bhi;
9495a645f22SBen Gras 
9505a645f22SBen Gras 		while (!lzma_index_iter_next(&iter, LZMA_INDEX_ITER_BLOCK)) {
9515a645f22SBen Gras 			if (message_verbosity_get() >= V_DEBUG
9525a645f22SBen Gras 					&& parse_details(
9535a645f22SBen Gras 						pair, &iter, &bhi, xfi))
9545a645f22SBen Gras 				return true;
9555a645f22SBen Gras 
9565a645f22SBen Gras 			printf("block\t%" PRIu64 "\t%" PRIu64 "\t%" PRIu64
9575a645f22SBen Gras 					"\t%" PRIu64 "\t%" PRIu64
9585a645f22SBen Gras 					"\t%" PRIu64 "\t%" PRIu64 "\t%s\t%s",
9595a645f22SBen Gras 					iter.stream.number,
9605a645f22SBen Gras 					iter.block.number_in_stream,
9615a645f22SBen Gras 					iter.block.number_in_file,
9625a645f22SBen Gras 					iter.block.compressed_file_offset,
9635a645f22SBen Gras 					iter.block.uncompressed_file_offset,
9645a645f22SBen Gras 					iter.block.total_size,
9655a645f22SBen Gras 					iter.block.uncompressed_size,
9665a645f22SBen Gras 					get_ratio(iter.block.total_size,
9675a645f22SBen Gras 						iter.block.uncompressed_size),
9685a645f22SBen Gras 					check_names[iter.stream.flags->check]);
9695a645f22SBen Gras 
9705a645f22SBen Gras 			if (message_verbosity_get() >= V_DEBUG)
9715a645f22SBen Gras 				printf("\t%s\t%" PRIu32 "\t%s\t%" PRIu64
9725a645f22SBen Gras 						"\t%" PRIu64 "\t%s",
9735a645f22SBen Gras 						check_value,
9745a645f22SBen Gras 						bhi.header_size,
9755a645f22SBen Gras 						bhi.flags,
9765a645f22SBen Gras 						bhi.compressed_size,
9775a645f22SBen Gras 						bhi.memusage,
9785a645f22SBen Gras 						bhi.filter_chain);
9795a645f22SBen Gras 
9805a645f22SBen Gras 			putchar('\n');
9815a645f22SBen Gras 		}
9825a645f22SBen Gras 	}
9835a645f22SBen Gras 
9845a645f22SBen Gras 	if (message_verbosity_get() >= V_DEBUG)
985*0a6a1f1dSLionel Sambuc 		printf("summary\t%" PRIu64 "\t%s\t%" PRIu32 "\n",
9865a645f22SBen Gras 				xfi->memusage_max,
987*0a6a1f1dSLionel Sambuc 				xfi->all_have_sizes ? "yes" : "no",
988*0a6a1f1dSLionel Sambuc 				xfi->min_version);
9895a645f22SBen Gras 
9905a645f22SBen Gras 	return false;
9915a645f22SBen Gras }
9925a645f22SBen Gras 
9935a645f22SBen Gras 
9945a645f22SBen Gras static void
update_totals(const xz_file_info * xfi)9955a645f22SBen Gras update_totals(const xz_file_info *xfi)
9965a645f22SBen Gras {
9975a645f22SBen Gras 	// TODO: Integer overflow checks
9985a645f22SBen Gras 	++totals.files;
9995a645f22SBen Gras 	totals.streams += lzma_index_stream_count(xfi->idx);
10005a645f22SBen Gras 	totals.blocks += lzma_index_block_count(xfi->idx);
10015a645f22SBen Gras 	totals.compressed_size += lzma_index_file_size(xfi->idx);
10025a645f22SBen Gras 	totals.uncompressed_size += lzma_index_uncompressed_size(xfi->idx);
10035a645f22SBen Gras 	totals.stream_padding += xfi->stream_padding;
10045a645f22SBen Gras 	totals.checks |= lzma_index_checks(xfi->idx);
10055a645f22SBen Gras 
10065a645f22SBen Gras 	if (totals.memusage_max < xfi->memusage_max)
10075a645f22SBen Gras 		totals.memusage_max = xfi->memusage_max;
10085a645f22SBen Gras 
1009*0a6a1f1dSLionel Sambuc 	if (totals.min_version < xfi->min_version)
1010*0a6a1f1dSLionel Sambuc 		totals.min_version = xfi->min_version;
1011*0a6a1f1dSLionel Sambuc 
10125a645f22SBen Gras 	totals.all_have_sizes &= xfi->all_have_sizes;
10135a645f22SBen Gras 
10145a645f22SBen Gras 	return;
10155a645f22SBen Gras }
10165a645f22SBen Gras 
10175a645f22SBen Gras 
10185a645f22SBen Gras static void
print_totals_basic(void)10195a645f22SBen Gras print_totals_basic(void)
10205a645f22SBen Gras {
10215a645f22SBen Gras 	// Print a separator line.
10225a645f22SBen Gras 	char line[80];
10235a645f22SBen Gras 	memset(line, '-', sizeof(line));
10245a645f22SBen Gras 	line[sizeof(line) - 1] = '\0';
10255a645f22SBen Gras 	puts(line);
10265a645f22SBen Gras 
10275a645f22SBen Gras 	// Get the check names.
10285a645f22SBen Gras 	char checks[CHECKS_STR_SIZE];
10295a645f22SBen Gras 	get_check_names(checks, totals.checks, false);
10305a645f22SBen Gras 
10315a645f22SBen Gras 	// Print the totals except the file count, which needs
10325a645f22SBen Gras 	// special handling.
10335a645f22SBen Gras 	printf("%5s %7s  %11s  %11s  %5s  %-7s ",
10345a645f22SBen Gras 			uint64_to_str(totals.streams, 0),
10355a645f22SBen Gras 			uint64_to_str(totals.blocks, 1),
10365a645f22SBen Gras 			uint64_to_nicestr(totals.compressed_size,
10375a645f22SBen Gras 				NICESTR_B, NICESTR_TIB, false, 2),
10385a645f22SBen Gras 			uint64_to_nicestr(totals.uncompressed_size,
10395a645f22SBen Gras 				NICESTR_B, NICESTR_TIB, false, 3),
10405a645f22SBen Gras 			get_ratio(totals.compressed_size,
10415a645f22SBen Gras 				totals.uncompressed_size),
10425a645f22SBen Gras 			checks);
10435a645f22SBen Gras 
10445a645f22SBen Gras 	// Since we print totals only when there are at least two files,
10455a645f22SBen Gras 	// the English message will always use "%s files". But some other
10465a645f22SBen Gras 	// languages need different forms for different plurals so we
10475a645f22SBen Gras 	// have to translate this with ngettext().
10485a645f22SBen Gras 	//
10495a645f22SBen Gras 	// TRANSLATORS: %s is an integer. Only the plural form of this
10505a645f22SBen Gras 	// message is used (e.g. "2 files"). Test with "xz -l foo.xz bar.xz".
10515a645f22SBen Gras 	printf(ngettext("%s file\n", "%s files\n",
10525a645f22SBen Gras 			totals.files <= ULONG_MAX ? totals.files
10535a645f22SBen Gras 				: (totals.files % 1000000) + 1000000),
10545a645f22SBen Gras 			uint64_to_str(totals.files, 0));
10555a645f22SBen Gras 
10565a645f22SBen Gras 	return;
10575a645f22SBen Gras }
10585a645f22SBen Gras 
10595a645f22SBen Gras 
10605a645f22SBen Gras static void
print_totals_adv(void)10615a645f22SBen Gras print_totals_adv(void)
10625a645f22SBen Gras {
10635a645f22SBen Gras 	putchar('\n');
10645a645f22SBen Gras 	puts(_("Totals:"));
10655a645f22SBen Gras 	printf(_("  Number of files:    %s\n"),
10665a645f22SBen Gras 			uint64_to_str(totals.files, 0));
10675a645f22SBen Gras 	print_adv_helper(totals.streams, totals.blocks,
10685a645f22SBen Gras 			totals.compressed_size, totals.uncompressed_size,
10695a645f22SBen Gras 			totals.checks, totals.stream_padding);
10705a645f22SBen Gras 
10715a645f22SBen Gras 	if (message_verbosity_get() >= V_DEBUG) {
10725a645f22SBen Gras 		printf(_("  Memory needed:      %s MiB\n"), uint64_to_str(
10735a645f22SBen Gras 				round_up_to_mib(totals.memusage_max), 0));
10745a645f22SBen Gras 		printf(_("  Sizes in headers:   %s\n"),
10755a645f22SBen Gras 				totals.all_have_sizes ? _("Yes") : _("No"));
1076*0a6a1f1dSLionel Sambuc 		printf(_("  Minimum XZ Utils version: %s\n"),
1077*0a6a1f1dSLionel Sambuc 				xz_ver_to_str(totals.min_version));
10785a645f22SBen Gras 	}
10795a645f22SBen Gras 
10805a645f22SBen Gras 	return;
10815a645f22SBen Gras }
10825a645f22SBen Gras 
10835a645f22SBen Gras 
10845a645f22SBen Gras static void
print_totals_robot(void)10855a645f22SBen Gras print_totals_robot(void)
10865a645f22SBen Gras {
10875a645f22SBen Gras 	char checks[CHECKS_STR_SIZE];
10885a645f22SBen Gras 	get_check_names(checks, totals.checks, false);
10895a645f22SBen Gras 
10905a645f22SBen Gras 	printf("totals\t%" PRIu64 "\t%" PRIu64 "\t%" PRIu64 "\t%" PRIu64
10915a645f22SBen Gras 			"\t%s\t%s\t%" PRIu64 "\t%" PRIu64,
10925a645f22SBen Gras 			totals.streams,
10935a645f22SBen Gras 			totals.blocks,
10945a645f22SBen Gras 			totals.compressed_size,
10955a645f22SBen Gras 			totals.uncompressed_size,
10965a645f22SBen Gras 			get_ratio(totals.compressed_size,
10975a645f22SBen Gras 				totals.uncompressed_size),
10985a645f22SBen Gras 			checks,
10995a645f22SBen Gras 			totals.stream_padding,
11005a645f22SBen Gras 			totals.files);
11015a645f22SBen Gras 
11025a645f22SBen Gras 	if (message_verbosity_get() >= V_DEBUG)
1103*0a6a1f1dSLionel Sambuc 		printf("\t%" PRIu64 "\t%s\t%" PRIu32,
11045a645f22SBen Gras 				totals.memusage_max,
1105*0a6a1f1dSLionel Sambuc 				totals.all_have_sizes ? "yes" : "no",
1106*0a6a1f1dSLionel Sambuc 				totals.min_version);
11075a645f22SBen Gras 
11085a645f22SBen Gras 	putchar('\n');
11095a645f22SBen Gras 
11105a645f22SBen Gras 	return;
11115a645f22SBen Gras }
11125a645f22SBen Gras 
11135a645f22SBen Gras 
11145a645f22SBen Gras extern void
list_totals(void)11155a645f22SBen Gras list_totals(void)
11165a645f22SBen Gras {
11175a645f22SBen Gras 	if (opt_robot) {
11185a645f22SBen Gras 		// Always print totals in --robot mode. It can be convenient
11195a645f22SBen Gras 		// in some cases and doesn't complicate usage of the
11205a645f22SBen Gras 		// single-file case much.
11215a645f22SBen Gras 		print_totals_robot();
11225a645f22SBen Gras 
11235a645f22SBen Gras 	} else if (totals.files > 1) {
11245a645f22SBen Gras 		// For non-robot mode, totals are printed only if there
11255a645f22SBen Gras 		// is more than one file.
11265a645f22SBen Gras 		if (message_verbosity_get() <= V_WARNING)
11275a645f22SBen Gras 			print_totals_basic();
11285a645f22SBen Gras 		else
11295a645f22SBen Gras 			print_totals_adv();
11305a645f22SBen Gras 	}
11315a645f22SBen Gras 
11325a645f22SBen Gras 	return;
11335a645f22SBen Gras }
11345a645f22SBen Gras 
11355a645f22SBen Gras 
11365a645f22SBen Gras extern void
list_file(const char * filename)11375a645f22SBen Gras list_file(const char *filename)
11385a645f22SBen Gras {
11395a645f22SBen Gras 	if (opt_format != FORMAT_XZ && opt_format != FORMAT_AUTO)
11405a645f22SBen Gras 		message_fatal(_("--list works only on .xz files "
11415a645f22SBen Gras 				"(--format=xz or --format=auto)"));
11425a645f22SBen Gras 
11435a645f22SBen Gras 	message_filename(filename);
11445a645f22SBen Gras 
11455a645f22SBen Gras 	if (filename == stdin_filename) {
11465a645f22SBen Gras 		message_error(_("--list does not support reading from "
11475a645f22SBen Gras 				"standard input"));
11485a645f22SBen Gras 		return;
11495a645f22SBen Gras 	}
11505a645f22SBen Gras 
11515a645f22SBen Gras 	// Unset opt_stdout so that io_open_src() won't accept special files.
11525a645f22SBen Gras 	// Set opt_force so that io_open_src() will follow symlinks.
11535a645f22SBen Gras 	opt_stdout = false;
11545a645f22SBen Gras 	opt_force = true;
11555a645f22SBen Gras 	file_pair *pair = io_open_src(filename);
11565a645f22SBen Gras 	if (pair == NULL)
11575a645f22SBen Gras 		return;
11585a645f22SBen Gras 
11595a645f22SBen Gras 	xz_file_info xfi = XZ_FILE_INFO_INIT;
11605a645f22SBen Gras 	if (!parse_indexes(&xfi, pair)) {
11615a645f22SBen Gras 		bool fail;
11625a645f22SBen Gras 
11635a645f22SBen Gras 		// We have three main modes:
11645a645f22SBen Gras 		//  - --robot, which has submodes if --verbose is specified
11655a645f22SBen Gras 		//    once or twice
11665a645f22SBen Gras 		//  - Normal --list without --verbose
11675a645f22SBen Gras 		//  - --list with one or two --verbose
11685a645f22SBen Gras 		if (opt_robot)
11695a645f22SBen Gras 			fail = print_info_robot(&xfi, pair);
11705a645f22SBen Gras 		else if (message_verbosity_get() <= V_WARNING)
11715a645f22SBen Gras 			fail = print_info_basic(&xfi, pair);
11725a645f22SBen Gras 		else
11735a645f22SBen Gras 			fail = print_info_adv(&xfi, pair);
11745a645f22SBen Gras 
11755a645f22SBen Gras 		// Update the totals that are displayed after all
11765a645f22SBen Gras 		// the individual files have been listed. Don't count
11775a645f22SBen Gras 		// broken files.
11785a645f22SBen Gras 		if (!fail)
11795a645f22SBen Gras 			update_totals(&xfi);
11805a645f22SBen Gras 
11815a645f22SBen Gras 		lzma_index_end(xfi.idx, NULL);
11825a645f22SBen Gras 	}
11835a645f22SBen Gras 
11845a645f22SBen Gras 	io_close(pair, false);
11855a645f22SBen Gras 	return;
11865a645f22SBen Gras }
1187