1 // SPDX-License-Identifier: 0BSD
2
3 ///////////////////////////////////////////////////////////////////////////////
4 //
5 /// \file list.c
6 /// \brief Listing information about .xz files
7 //
8 // Author: Lasse Collin
9 //
10 ///////////////////////////////////////////////////////////////////////////////
11
12 #include "private.h"
13 #include "tuklib_integer.h"
14
15
16 /// Information about a .xz file
17 typedef struct {
18 /// Combined Index of all Streams in the file
19 lzma_index *idx;
20
21 /// Total amount of Stream Padding
22 uint64_t stream_padding;
23
24 /// Highest memory usage so far
25 uint64_t memusage_max;
26
27 /// True if all Blocks so far have Compressed Size and
28 /// Uncompressed Size fields
29 bool all_have_sizes;
30
31 /// Oldest XZ Utils version that will decompress the file
32 uint32_t min_version;
33
34 } xz_file_info;
35
36 #define XZ_FILE_INFO_INIT { NULL, 0, 0, true, 50000002 }
37
38
39 /// Information about a .xz Block
40 typedef struct {
41 /// Size of the Block Header
42 uint32_t header_size;
43
44 /// A few of the Block Flags as a string
45 char flags[3];
46
47 /// Size of the Compressed Data field in the Block
48 lzma_vli compressed_size;
49
50 /// Decoder memory usage for this Block
51 uint64_t memusage;
52
53 /// The filter chain of this Block in human-readable form
54 char *filter_chain;
55
56 } block_header_info;
57
58 #define BLOCK_HEADER_INFO_INIT { .filter_chain = NULL }
59 #define block_header_info_end(bhi) free((bhi)->filter_chain)
60
61
62 /// Strings ending in a colon. These are used for lines like
63 /// " Foo: 123 MiB". These are grouped because translated strings
64 /// may have different maximum string length, and we want to pad all
65 /// strings so that the values are aligned nicely.
66 static const char *colon_strs[] = {
67 N_("Streams:"),
68 N_("Blocks:"),
69 N_("Compressed size:"),
70 N_("Uncompressed size:"),
71 N_("Ratio:"),
72 N_("Check:"),
73 N_("Stream Padding:"),
74 N_("Memory needed:"),
75 N_("Sizes in headers:"),
76 // This won't be aligned because it's so long:
77 //N_("Minimum XZ Utils version:"),
78 N_("Number of files:"),
79 };
80
81 /// Enum matching the above strings.
82 enum {
83 COLON_STR_STREAMS,
84 COLON_STR_BLOCKS,
85 COLON_STR_COMPRESSED_SIZE,
86 COLON_STR_UNCOMPRESSED_SIZE,
87 COLON_STR_RATIO,
88 COLON_STR_CHECK,
89 COLON_STR_STREAM_PADDING,
90 COLON_STR_MEMORY_NEEDED,
91 COLON_STR_SIZES_IN_HEADERS,
92 //COLON_STR_MINIMUM_XZ_VERSION,
93 COLON_STR_NUMBER_OF_FILES,
94 };
95
96 /// Field widths to use with printf to pad the strings to use the same number
97 /// of columns on a terminal.
98 static int colon_strs_fw[ARRAY_SIZE(colon_strs)];
99
100 /// Convenience macro to get the translated string and its field width
101 /// using a COLON_STR_foo enum.
102 #define COLON_STR(num) colon_strs_fw[num], _(colon_strs[num])
103
104
105 /// Column headings
106 static struct {
107 /// Table column heading string
108 const char *str;
109
110 /// Number of terminal-columns to use for this table-column.
111 /// If a translated string is longer than the initial value,
112 /// this value will be increased in init_headings().
113 int columns;
114
115 /// Field width to use for printf() to pad "str" to use "columns"
116 /// number of columns on a terminal. This is calculated in
117 /// init_headings().
118 int fw;
119
120 } headings[] = {
121 { N_("Stream"), 6, 0 },
122 { N_("Block"), 9, 0 },
123 { N_("Blocks"), 9, 0 },
124 { N_("CompOffset"), 15, 0 },
125 { N_("UncompOffset"), 15, 0 },
126 { N_("CompSize"), 15, 0 },
127 { N_("UncompSize"), 15, 0 },
128 { N_("TotalSize"), 15, 0 },
129 { N_("Ratio"), 5, 0 },
130 { N_("Check"), 10, 0 },
131 { N_("CheckVal"), 1, 0 },
132 { N_("Padding"), 7, 0 },
133 { N_("Header"), 5, 0 },
134 { N_("Flags"), 2, 0 },
135 { N_("MemUsage"), 7 + 4, 0 }, // +4 is for " MiB"
136 { N_("Filters"), 1, 0 },
137 };
138
139 /// Enum matching the above strings.
140 enum {
141 HEADING_STREAM,
142 HEADING_BLOCK,
143 HEADING_BLOCKS,
144 HEADING_COMPOFFSET,
145 HEADING_UNCOMPOFFSET,
146 HEADING_COMPSIZE,
147 HEADING_UNCOMPSIZE,
148 HEADING_TOTALSIZE,
149 HEADING_RATIO,
150 HEADING_CHECK,
151 HEADING_CHECKVAL,
152 HEADING_PADDING,
153 HEADING_HEADERSIZE,
154 HEADING_HEADERFLAGS,
155 HEADING_MEMUSAGE,
156 HEADING_FILTERS,
157 };
158
159 #define HEADING_STR(num) headings[num].fw, _(headings[num].str)
160
161
162 /// Check ID to string mapping
163 static const char check_names[LZMA_CHECK_ID_MAX + 1][12] = {
164 // TRANSLATORS: Indicates that there is no integrity check.
165 // This string is used in tables. In older xz version this
166 // string was limited to ten columns in a fixed-width font, but
167 // nowadays there is no strict length restriction anymore.
168 N_("None"),
169 "CRC32",
170 // TRANSLATORS: Indicates that integrity check name is not known,
171 // but the Check ID is known (here 2). In older xz version these
172 // strings were limited to ten columns in a fixed-width font, but
173 // nowadays there is no strict length restriction anymore.
174 N_("Unknown-2"),
175 N_("Unknown-3"),
176 "CRC64",
177 N_("Unknown-5"),
178 N_("Unknown-6"),
179 N_("Unknown-7"),
180 N_("Unknown-8"),
181 N_("Unknown-9"),
182 "SHA-256",
183 N_("Unknown-11"),
184 N_("Unknown-12"),
185 N_("Unknown-13"),
186 N_("Unknown-14"),
187 N_("Unknown-15"),
188 };
189
190 /// Buffer size for get_check_names(). This may be a bit ridiculous,
191 /// but at least it's enough if some language needs many multibyte chars.
192 #define CHECKS_STR_SIZE 1024
193
194
195 /// Value of the Check field as hexadecimal string.
196 /// This is set by parse_check_value().
197 static char check_value[2 * LZMA_CHECK_SIZE_MAX + 1];
198
199
200 /// Totals that are displayed if there was more than one file.
201 /// The "files" counter is also used in print_info_adv() to show
202 /// the file number.
203 static struct {
204 uint64_t files;
205 uint64_t streams;
206 uint64_t blocks;
207 uint64_t compressed_size;
208 uint64_t uncompressed_size;
209 uint64_t stream_padding;
210 uint64_t memusage_max;
211 uint32_t checks;
212 uint32_t min_version;
213 bool all_have_sizes;
214 } totals = { 0, 0, 0, 0, 0, 0, 0, 0, 50000002, true };
215
216
217 /// Initialize colon_strs_fw[].
218 static void
init_colon_strs(void)219 init_colon_strs(void)
220 {
221 // Lengths of translated strings as bytes.
222 size_t lens[ARRAY_SIZE(colon_strs)];
223
224 // Lengths of translated strings as columns.
225 size_t widths[ARRAY_SIZE(colon_strs)];
226
227 // Maximum number of columns needed by a translated string.
228 size_t width_max = 0;
229
230 for (unsigned i = 0; i < ARRAY_SIZE(colon_strs); ++i) {
231 widths[i] = tuklib_mbstr_width(_(colon_strs[i]), &lens[i]);
232
233 // If debugging is enabled, catch invalid strings with
234 // an assertion. However, when not debugging, use the
235 // byte count as the fallback width. This shouldn't
236 // ever happen unless there is a bad string in the
237 // translations, but in such case I guess it's better
238 // to try to print something useful instead of failing
239 // completely.
240 assert(widths[i] != (size_t)-1);
241 if (widths[i] == (size_t)-1)
242 widths[i] = lens[i];
243
244 if (widths[i] > width_max)
245 width_max = widths[i];
246 }
247
248 // Calculate the field width for printf("%*s") so that the strings
249 // will use width_max columns on a terminal.
250 for (unsigned i = 0; i < ARRAY_SIZE(colon_strs); ++i)
251 colon_strs_fw[i] = (int)(lens[i] + width_max - widths[i]);
252
253 return;
254 }
255
256
257 /// Initialize headings[].
258 static void
init_headings(void)259 init_headings(void)
260 {
261 // Before going through the heading strings themselves, treat
262 // the Check heading specially: Look at the widths of the various
263 // check names and increase the width of the Check column if needed.
264 // The width of the heading name "Check" will then be handled normally
265 // with other heading names in the second loop in this function.
266 for (unsigned i = 0; i < ARRAY_SIZE(check_names); ++i) {
267 size_t len;
268 size_t w = tuklib_mbstr_width(_(check_names[i]), &len);
269
270 // Error handling like in init_colon_strs().
271 assert(w != (size_t)-1);
272 if (w == (size_t)-1)
273 w = len;
274
275 // If the translated string is wider than the minimum width
276 // set at compile time, increase the width.
277 if ((size_t)(headings[HEADING_CHECK].columns) < w)
278 headings[HEADING_CHECK].columns = (int)w;
279 }
280
281 for (unsigned i = 0; i < ARRAY_SIZE(headings); ++i) {
282 size_t len;
283 size_t w = tuklib_mbstr_width(_(headings[i].str), &len);
284
285 // Error handling like in init_colon_strs().
286 assert(w != (size_t)-1);
287 if (w == (size_t)-1)
288 w = len;
289
290 // If the translated string is wider than the minimum width
291 // set at compile time, increase the width.
292 if ((size_t)(headings[i].columns) < w)
293 headings[i].columns = (int)w;
294
295 // Calculate the field width for printf("%*s") so that
296 // the string uses .columns number of columns on a terminal.
297 headings[i].fw = (int)(len + (size_t)headings[i].columns - w);
298 }
299
300 return;
301 }
302
303
304 /// Initialize the printf field widths that are needed to get nicely aligned
305 /// output with translated strings.
306 static void
init_field_widths(void)307 init_field_widths(void)
308 {
309 init_colon_strs();
310 init_headings();
311 return;
312 }
313
314
315 /// Convert XZ Utils version number to a string.
316 static const char *
xz_ver_to_str(uint32_t ver)317 xz_ver_to_str(uint32_t ver)
318 {
319 static char buf[32];
320
321 unsigned int major = ver / 10000000U;
322 ver -= major * 10000000U;
323
324 unsigned int minor = ver / 10000U;
325 ver -= minor * 10000U;
326
327 unsigned int patch = ver / 10U;
328 ver -= patch * 10U;
329
330 const char *stability = ver == 0 ? "alpha" : ver == 1 ? "beta" : "";
331
332 snprintf(buf, sizeof(buf), "%u.%u.%u%s",
333 major, minor, patch, stability);
334 return buf;
335 }
336
337
338 /// \brief Parse the Index(es) from the given .xz file
339 ///
340 /// \param xfi Pointer to structure where the decoded information
341 /// is stored.
342 /// \param pair Input file
343 ///
344 /// \return On success, false is returned. On error, true is returned.
345 ///
346 static bool
parse_indexes(xz_file_info * xfi,file_pair * pair)347 parse_indexes(xz_file_info *xfi, file_pair *pair)
348 {
349 if (pair->src_st.st_size <= 0) {
350 message_error(_("%s: File is empty"), pair->src_name);
351 return true;
352 }
353
354 if (pair->src_st.st_size < 2 * LZMA_STREAM_HEADER_SIZE) {
355 message_error(_("%s: Too small to be a valid .xz file"),
356 pair->src_name);
357 return true;
358 }
359
360 io_buf buf;
361 lzma_stream strm = LZMA_STREAM_INIT;
362 lzma_index *idx = NULL;
363
364 lzma_ret ret = lzma_file_info_decoder(&strm, &idx,
365 hardware_memlimit_get(MODE_LIST),
366 (uint64_t)(pair->src_st.st_size));
367 if (ret != LZMA_OK) {
368 message_error(_("%s: %s"), pair->src_name, message_strm(ret));
369 return true;
370 }
371
372 while (true) {
373 if (strm.avail_in == 0) {
374 strm.next_in = buf.u8;
375 strm.avail_in = io_read(pair, &buf, IO_BUFFER_SIZE);
376 if (strm.avail_in == SIZE_MAX)
377 goto error;
378 }
379
380 ret = lzma_code(&strm, LZMA_RUN);
381
382 switch (ret) {
383 case LZMA_OK:
384 break;
385
386 case LZMA_SEEK_NEEDED:
387 // liblzma won't ask us to seek past the known size
388 // of the input file.
389 assert(strm.seek_pos
390 <= (uint64_t)(pair->src_st.st_size));
391 if (io_seek_src(pair, strm.seek_pos))
392 goto error;
393
394 // avail_in must be zero so that we will read new
395 // input.
396 strm.avail_in = 0;
397 break;
398
399 case LZMA_STREAM_END: {
400 lzma_end(&strm);
401 xfi->idx = idx;
402
403 // Calculate xfi->stream_padding.
404 lzma_index_iter iter;
405 lzma_index_iter_init(&iter, xfi->idx);
406 while (!lzma_index_iter_next(&iter,
407 LZMA_INDEX_ITER_STREAM))
408 xfi->stream_padding += iter.stream.padding;
409
410 return false;
411 }
412
413 default:
414 message_error(_("%s: %s"), pair->src_name,
415 message_strm(ret));
416
417 // If the error was too low memory usage limit,
418 // show also how much memory would have been needed.
419 if (ret == LZMA_MEMLIMIT_ERROR)
420 message_mem_needed(V_ERROR,
421 lzma_memusage(&strm));
422
423 goto error;
424 }
425 }
426
427 error:
428 lzma_end(&strm);
429 return true;
430 }
431
432
433 /// \brief Parse the Block Header
434 ///
435 /// The result is stored into *bhi. The caller takes care of initializing it.
436 ///
437 /// \return False on success, true on error.
438 static bool
parse_block_header(file_pair * pair,const lzma_index_iter * iter,block_header_info * bhi,xz_file_info * xfi)439 parse_block_header(file_pair *pair, const lzma_index_iter *iter,
440 block_header_info *bhi, xz_file_info *xfi)
441 {
442 #if IO_BUFFER_SIZE < LZMA_BLOCK_HEADER_SIZE_MAX
443 # error IO_BUFFER_SIZE < LZMA_BLOCK_HEADER_SIZE_MAX
444 #endif
445
446 // Get the whole Block Header with one read, but don't read past
447 // the end of the Block (or even its Check field).
448 const uint32_t size = my_min(iter->block.total_size
449 - lzma_check_size(iter->stream.flags->check),
450 LZMA_BLOCK_HEADER_SIZE_MAX);
451 io_buf buf;
452 if (io_pread(pair, &buf, size, iter->block.compressed_file_offset))
453 return true;
454
455 // Zero would mean Index Indicator and thus not a valid Block.
456 if (buf.u8[0] == 0)
457 goto data_error;
458
459 // Initialize the block structure and decode Block Header Size.
460 lzma_filter filters[LZMA_FILTERS_MAX + 1];
461 lzma_block block;
462 block.version = 0;
463 block.check = iter->stream.flags->check;
464 block.filters = filters;
465
466 block.header_size = lzma_block_header_size_decode(buf.u8[0]);
467 if (block.header_size > size)
468 goto data_error;
469
470 // Decode the Block Header.
471 switch (lzma_block_header_decode(&block, NULL, buf.u8)) {
472 case LZMA_OK:
473 break;
474
475 case LZMA_OPTIONS_ERROR:
476 message_error(_("%s: %s"), pair->src_name,
477 message_strm(LZMA_OPTIONS_ERROR));
478 return true;
479
480 case LZMA_DATA_ERROR:
481 goto data_error;
482
483 default:
484 message_bug();
485 }
486
487 // Check the Block Flags. These must be done before calling
488 // lzma_block_compressed_size(), because it overwrites
489 // block.compressed_size.
490 //
491 // NOTE: If you add new characters here, update the minimum number of
492 // columns in headings[HEADING_HEADERFLAGS] to match the number of
493 // characters used here.
494 bhi->flags[0] = block.compressed_size != LZMA_VLI_UNKNOWN
495 ? 'c' : '-';
496 bhi->flags[1] = block.uncompressed_size != LZMA_VLI_UNKNOWN
497 ? 'u' : '-';
498 bhi->flags[2] = '\0';
499
500 // Collect information if all Blocks have both Compressed Size
501 // and Uncompressed Size fields. They can be useful e.g. for
502 // multi-threaded decompression so it can be useful to know it.
503 xfi->all_have_sizes &= block.compressed_size != LZMA_VLI_UNKNOWN
504 && block.uncompressed_size != LZMA_VLI_UNKNOWN;
505
506 // Validate or set block.compressed_size.
507 switch (lzma_block_compressed_size(&block,
508 iter->block.unpadded_size)) {
509 case LZMA_OK:
510 // Validate also block.uncompressed_size if it is present.
511 // If it isn't present, there's no need to set it since
512 // we aren't going to actually decompress the Block; if
513 // we were decompressing, then we should set it so that
514 // the Block decoder could validate the Uncompressed Size
515 // that was stored in the Index.
516 if (block.uncompressed_size == LZMA_VLI_UNKNOWN
517 || block.uncompressed_size
518 == iter->block.uncompressed_size)
519 break;
520
521 // If the above fails, the file is corrupt so
522 // LZMA_DATA_ERROR is a good error code.
523
524 // Fall through
525
526 case LZMA_DATA_ERROR:
527 // Free the memory allocated by lzma_block_header_decode().
528 lzma_filters_free(filters, NULL);
529 goto data_error;
530
531 default:
532 message_bug();
533 }
534
535 // Copy the known sizes.
536 bhi->header_size = block.header_size;
537 bhi->compressed_size = block.compressed_size;
538
539 // Calculate the decoder memory usage and update the maximum
540 // memory usage of this Block.
541 bhi->memusage = lzma_raw_decoder_memusage(filters);
542 if (xfi->memusage_max < bhi->memusage)
543 xfi->memusage_max = bhi->memusage;
544
545 // Determine the minimum XZ Utils version that supports this Block.
546 // - RISC-V filter needs 5.6.0.
547 //
548 // - ARM64 filter needs 5.4.0.
549 //
550 // - 5.0.0 doesn't support empty LZMA2 streams and thus empty
551 // Blocks that use LZMA2. This decoder bug was fixed in 5.0.2.
552 if (xfi->min_version < 50060002U) {
553 for (size_t i = 0; filters[i].id != LZMA_VLI_UNKNOWN; ++i) {
554 if (filters[i].id == LZMA_FILTER_RISCV) {
555 xfi->min_version = 50060002U;
556 break;
557 }
558 }
559 }
560
561 if (xfi->min_version < 50040002U) {
562 for (size_t i = 0; filters[i].id != LZMA_VLI_UNKNOWN; ++i) {
563 if (filters[i].id == LZMA_FILTER_ARM64) {
564 xfi->min_version = 50040002U;
565 break;
566 }
567 }
568 }
569
570 if (xfi->min_version < 50000022U) {
571 size_t i = 0;
572 while (filters[i + 1].id != LZMA_VLI_UNKNOWN)
573 ++i;
574
575 if (filters[i].id == LZMA_FILTER_LZMA2
576 && iter->block.uncompressed_size == 0)
577 xfi->min_version = 50000022U;
578 }
579
580 // Convert the filter chain to human readable form.
581 const lzma_ret str_ret = lzma_str_from_filters(
582 &bhi->filter_chain, filters,
583 LZMA_STR_DECODER | LZMA_STR_GETOPT_LONG, NULL);
584
585 // Free the memory allocated by lzma_block_header_decode().
586 lzma_filters_free(filters, NULL);
587
588 // Check if the stringification succeeded.
589 if (str_ret != LZMA_OK) {
590 message_error(_("%s: %s"), pair->src_name,
591 message_strm(str_ret));
592 return true;
593 }
594
595 return false;
596
597 data_error:
598 // Show the error message.
599 message_error(_("%s: %s"), pair->src_name,
600 message_strm(LZMA_DATA_ERROR));
601 return true;
602 }
603
604
605 /// \brief Parse the Check field and put it into check_value[]
606 ///
607 /// \return False on success, true on error.
608 static bool
parse_check_value(file_pair * pair,const lzma_index_iter * iter)609 parse_check_value(file_pair *pair, const lzma_index_iter *iter)
610 {
611 // Don't read anything from the file if there is no integrity Check.
612 if (iter->stream.flags->check == LZMA_CHECK_NONE) {
613 snprintf(check_value, sizeof(check_value), "---");
614 return false;
615 }
616
617 // Locate and read the Check field.
618 const uint32_t size = lzma_check_size(iter->stream.flags->check);
619 const uint64_t offset = iter->block.compressed_file_offset
620 + iter->block.total_size - size;
621 io_buf buf;
622 if (io_pread(pair, &buf, size, offset))
623 return true;
624
625 // CRC32 and CRC64 are in little endian. Guess that all the future
626 // 32-bit and 64-bit Check values are little endian too. It shouldn't
627 // be a too big problem if this guess is wrong.
628 if (size == 4)
629 snprintf(check_value, sizeof(check_value),
630 "%08" PRIx32, conv32le(buf.u32[0]));
631 else if (size == 8)
632 snprintf(check_value, sizeof(check_value),
633 "%016" PRIx64, conv64le(buf.u64[0]));
634 else
635 for (size_t i = 0; i < size; ++i)
636 snprintf(check_value + i * 2, 3, "%02x", buf.u8[i]);
637
638 return false;
639 }
640
641
642 /// \brief Parse detailed information about a Block
643 ///
644 /// Since this requires seek(s), listing information about all Blocks can
645 /// be slow.
646 ///
647 /// \param pair Input file
648 /// \param iter Location of the Block whose Check value should
649 /// be printed.
650 /// \param bhi Pointer to structure where to store the information
651 /// about the Block Header field.
652 ///
653 /// \return False on success, true on error. If an error occurs,
654 /// the error message is printed too so the caller doesn't
655 /// need to worry about that.
656 static bool
parse_details(file_pair * pair,const lzma_index_iter * iter,block_header_info * bhi,xz_file_info * xfi)657 parse_details(file_pair *pair, const lzma_index_iter *iter,
658 block_header_info *bhi, xz_file_info *xfi)
659 {
660 if (parse_block_header(pair, iter, bhi, xfi))
661 return true;
662
663 if (parse_check_value(pair, iter))
664 return true;
665
666 return false;
667 }
668
669
670 /// \brief Get the compression ratio
671 ///
672 /// This has slightly different format than that is used in message.c.
673 static const char *
get_ratio(uint64_t compressed_size,uint64_t uncompressed_size)674 get_ratio(uint64_t compressed_size, uint64_t uncompressed_size)
675 {
676 if (uncompressed_size == 0)
677 return "---";
678
679 const double ratio = (double)(compressed_size)
680 / (double)(uncompressed_size);
681 if (ratio > 9.999)
682 return "---";
683
684 static char buf[16];
685 snprintf(buf, sizeof(buf), "%.3f", ratio);
686 return buf;
687 }
688
689
690 /// \brief Get a comma-separated list of Check names
691 ///
692 /// The check names are translated with gettext except when in robot mode.
693 ///
694 /// \param buf Buffer to hold the resulting string
695 /// \param checks Bit mask of Checks to print
696 /// \param space_after_comma
697 /// It's better to not use spaces in table-like listings,
698 /// but in more verbose formats a space after a comma
699 /// is good for readability.
700 static void
get_check_names(char buf[CHECKS_STR_SIZE],uint32_t checks,bool space_after_comma)701 get_check_names(char buf[CHECKS_STR_SIZE],
702 uint32_t checks, bool space_after_comma)
703 {
704 // If we get called when there are no Checks to print, set checks
705 // to 1 so that we print "None". This can happen in the robot mode
706 // when printing the totals line if there are no valid input files.
707 if (checks == 0)
708 checks = 1;
709
710 char *pos = buf;
711 size_t left = CHECKS_STR_SIZE;
712
713 const char *sep = space_after_comma ? ", " : ",";
714 bool comma = false;
715
716 for (size_t i = 0; i <= LZMA_CHECK_ID_MAX; ++i) {
717 if (checks & (UINT32_C(1) << i)) {
718 my_snprintf(&pos, &left, "%s%s",
719 comma ? sep : "",
720 opt_robot ? check_names[i]
721 : _(check_names[i]));
722 comma = true;
723 }
724 }
725
726 return;
727 }
728
729
730 static bool
print_info_basic(const xz_file_info * xfi,file_pair * pair)731 print_info_basic(const xz_file_info *xfi, file_pair *pair)
732 {
733 static bool headings_displayed = false;
734 if (!headings_displayed) {
735 headings_displayed = true;
736 // TRANSLATORS: These are column headings. From Strms (Streams)
737 // to Ratio, the columns are right aligned. Check and Filename
738 // are left aligned. If you need longer words, it's OK to
739 // use two lines here. Test with "xz -l foo.xz".
740 puts(_("Strms Blocks Compressed Uncompressed Ratio "
741 "Check Filename"));
742 }
743
744 char checks[CHECKS_STR_SIZE];
745 get_check_names(checks, lzma_index_checks(xfi->idx), false);
746
747 const char *cols[7] = {
748 uint64_to_str(lzma_index_stream_count(xfi->idx), 0),
749 uint64_to_str(lzma_index_block_count(xfi->idx), 1),
750 uint64_to_nicestr(lzma_index_file_size(xfi->idx),
751 NICESTR_B, NICESTR_TIB, false, 2),
752 uint64_to_nicestr(lzma_index_uncompressed_size(xfi->idx),
753 NICESTR_B, NICESTR_TIB, false, 3),
754 get_ratio(lzma_index_file_size(xfi->idx),
755 lzma_index_uncompressed_size(xfi->idx)),
756 checks,
757 pair->src_name,
758 };
759 printf("%*s %*s %*s %*s %*s %-*s %s\n",
760 tuklib_mbstr_fw(cols[0], 5), cols[0],
761 tuklib_mbstr_fw(cols[1], 7), cols[1],
762 tuklib_mbstr_fw(cols[2], 11), cols[2],
763 tuklib_mbstr_fw(cols[3], 11), cols[3],
764 tuklib_mbstr_fw(cols[4], 5), cols[4],
765 tuklib_mbstr_fw(cols[5], 7), cols[5],
766 cols[6]);
767
768 return false;
769 }
770
771
772 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)773 print_adv_helper(uint64_t stream_count, uint64_t block_count,
774 uint64_t compressed_size, uint64_t uncompressed_size,
775 uint32_t checks, uint64_t stream_padding)
776 {
777 char checks_str[CHECKS_STR_SIZE];
778 get_check_names(checks_str, checks, true);
779
780 printf(" %-*s %s\n", COLON_STR(COLON_STR_STREAMS),
781 uint64_to_str(stream_count, 0));
782 printf(" %-*s %s\n", COLON_STR(COLON_STR_BLOCKS),
783 uint64_to_str(block_count, 0));
784 printf(" %-*s %s\n", COLON_STR(COLON_STR_COMPRESSED_SIZE),
785 uint64_to_nicestr(compressed_size,
786 NICESTR_B, NICESTR_TIB, true, 0));
787 printf(" %-*s %s\n", COLON_STR(COLON_STR_UNCOMPRESSED_SIZE),
788 uint64_to_nicestr(uncompressed_size,
789 NICESTR_B, NICESTR_TIB, true, 0));
790 printf(" %-*s %s\n", COLON_STR(COLON_STR_RATIO),
791 get_ratio(compressed_size, uncompressed_size));
792 printf(" %-*s %s\n", COLON_STR(COLON_STR_CHECK), checks_str);
793 printf(" %-*s %s\n", COLON_STR(COLON_STR_STREAM_PADDING),
794 uint64_to_nicestr(stream_padding,
795 NICESTR_B, NICESTR_TIB, true, 0));
796 return;
797 }
798
799
800 static bool
print_info_adv(xz_file_info * xfi,file_pair * pair)801 print_info_adv(xz_file_info *xfi, file_pair *pair)
802 {
803 // Print the overall information.
804 print_adv_helper(lzma_index_stream_count(xfi->idx),
805 lzma_index_block_count(xfi->idx),
806 lzma_index_file_size(xfi->idx),
807 lzma_index_uncompressed_size(xfi->idx),
808 lzma_index_checks(xfi->idx),
809 xfi->stream_padding);
810
811 // Size of the biggest Check. This is used to calculate the width
812 // of the CheckVal field. The table would get insanely wide if
813 // we always reserved space for 64-byte Check (128 chars as hex).
814 uint32_t check_max = 0;
815
816 // Print information about the Streams.
817 //
818 // All except Check are right aligned; Check is left aligned.
819 // Test with "xz -lv foo.xz".
820 printf(" %s\n %*s %*s %*s %*s %*s %*s %*s %-*s %*s\n",
821 _(colon_strs[COLON_STR_STREAMS]),
822 HEADING_STR(HEADING_STREAM),
823 HEADING_STR(HEADING_BLOCKS),
824 HEADING_STR(HEADING_COMPOFFSET),
825 HEADING_STR(HEADING_UNCOMPOFFSET),
826 HEADING_STR(HEADING_COMPSIZE),
827 HEADING_STR(HEADING_UNCOMPSIZE),
828 HEADING_STR(HEADING_RATIO),
829 HEADING_STR(HEADING_CHECK),
830 HEADING_STR(HEADING_PADDING));
831
832 lzma_index_iter iter;
833 lzma_index_iter_init(&iter, xfi->idx);
834
835 while (!lzma_index_iter_next(&iter, LZMA_INDEX_ITER_STREAM)) {
836 const char *cols1[4] = {
837 uint64_to_str(iter.stream.number, 0),
838 uint64_to_str(iter.stream.block_count, 1),
839 uint64_to_str(iter.stream.compressed_offset, 2),
840 uint64_to_str(iter.stream.uncompressed_offset, 3),
841 };
842 printf(" %*s %*s %*s %*s ",
843 tuklib_mbstr_fw(cols1[0],
844 headings[HEADING_STREAM].columns),
845 cols1[0],
846 tuklib_mbstr_fw(cols1[1],
847 headings[HEADING_BLOCKS].columns),
848 cols1[1],
849 tuklib_mbstr_fw(cols1[2],
850 headings[HEADING_COMPOFFSET].columns),
851 cols1[2],
852 tuklib_mbstr_fw(cols1[3],
853 headings[HEADING_UNCOMPOFFSET].columns),
854 cols1[3]);
855
856 const char *cols2[5] = {
857 uint64_to_str(iter.stream.compressed_size, 0),
858 uint64_to_str(iter.stream.uncompressed_size, 1),
859 get_ratio(iter.stream.compressed_size,
860 iter.stream.uncompressed_size),
861 _(check_names[iter.stream.flags->check]),
862 uint64_to_str(iter.stream.padding, 2),
863 };
864 printf("%*s %*s %*s %-*s %*s\n",
865 tuklib_mbstr_fw(cols2[0],
866 headings[HEADING_COMPSIZE].columns),
867 cols2[0],
868 tuklib_mbstr_fw(cols2[1],
869 headings[HEADING_UNCOMPSIZE].columns),
870 cols2[1],
871 tuklib_mbstr_fw(cols2[2],
872 headings[HEADING_RATIO].columns),
873 cols2[2],
874 tuklib_mbstr_fw(cols2[3],
875 headings[HEADING_CHECK].columns),
876 cols2[3],
877 tuklib_mbstr_fw(cols2[4],
878 headings[HEADING_PADDING].columns),
879 cols2[4]);
880
881 // Update the maximum Check size.
882 if (lzma_check_size(iter.stream.flags->check) > check_max)
883 check_max = lzma_check_size(iter.stream.flags->check);
884 }
885
886 // Cache the verbosity level to a local variable.
887 const bool detailed = message_verbosity_get() >= V_DEBUG;
888
889 // Print information about the Blocks but only if there is
890 // at least one Block.
891 if (lzma_index_block_count(xfi->idx) > 0) {
892 // Calculate the width of the CheckVal column. This can be
893 // used as is as the field width for printf() when printing
894 // the actual check value as it is hexadecimal. However, to
895 // print the column heading, further calculation is needed
896 // to handle a translated string (it's done a few lines later).
897 assert(check_max <= LZMA_CHECK_SIZE_MAX);
898 const int checkval_width = my_max(
899 headings[HEADING_CHECKVAL].columns,
900 (int)(2 * check_max));
901
902 // All except Check are right aligned; Check is left aligned.
903 printf(" %s\n %*s %*s %*s %*s %*s %*s %*s %-*s",
904 _(colon_strs[COLON_STR_BLOCKS]),
905 HEADING_STR(HEADING_STREAM),
906 HEADING_STR(HEADING_BLOCK),
907 HEADING_STR(HEADING_COMPOFFSET),
908 HEADING_STR(HEADING_UNCOMPOFFSET),
909 HEADING_STR(HEADING_TOTALSIZE),
910 HEADING_STR(HEADING_UNCOMPSIZE),
911 HEADING_STR(HEADING_RATIO),
912 detailed ? headings[HEADING_CHECK].fw : 1,
913 _(headings[HEADING_CHECK].str));
914
915 if (detailed) {
916 // CheckVal (Check value), Flags, and Filters are
917 // left aligned. Block Header Size, CompSize, and
918 // MemUsage are right aligned. Test with
919 // "xz -lvv foo.xz".
920 printf(" %-*s %*s %-*s %*s %*s %s",
921 headings[HEADING_CHECKVAL].fw
922 + checkval_width
923 - headings[HEADING_CHECKVAL].columns,
924 _(headings[HEADING_CHECKVAL].str),
925 HEADING_STR(HEADING_HEADERSIZE),
926 HEADING_STR(HEADING_HEADERFLAGS),
927 HEADING_STR(HEADING_COMPSIZE),
928 HEADING_STR(HEADING_MEMUSAGE),
929 _(headings[HEADING_FILTERS].str));
930 }
931
932 putchar('\n');
933
934 lzma_index_iter_init(&iter, xfi->idx);
935
936 // Iterate over the Blocks.
937 while (!lzma_index_iter_next(&iter, LZMA_INDEX_ITER_BLOCK)) {
938 // If in detailed mode, collect the information from
939 // Block Header before starting to print the next line.
940 block_header_info bhi = BLOCK_HEADER_INFO_INIT;
941 if (detailed && parse_details(pair, &iter, &bhi, xfi))
942 return true;
943
944 const char *cols1[4] = {
945 uint64_to_str(iter.stream.number, 0),
946 uint64_to_str(
947 iter.block.number_in_stream, 1),
948 uint64_to_str(
949 iter.block.compressed_file_offset, 2),
950 uint64_to_str(
951 iter.block.uncompressed_file_offset, 3)
952 };
953 printf(" %*s %*s %*s %*s ",
954 tuklib_mbstr_fw(cols1[0],
955 headings[HEADING_STREAM].columns),
956 cols1[0],
957 tuklib_mbstr_fw(cols1[1],
958 headings[HEADING_BLOCK].columns),
959 cols1[1],
960 tuklib_mbstr_fw(cols1[2],
961 headings[HEADING_COMPOFFSET].columns),
962 cols1[2],
963 tuklib_mbstr_fw(cols1[3], headings[
964 HEADING_UNCOMPOFFSET].columns),
965 cols1[3]);
966
967 const char *cols2[4] = {
968 uint64_to_str(iter.block.total_size, 0),
969 uint64_to_str(iter.block.uncompressed_size,
970 1),
971 get_ratio(iter.block.total_size,
972 iter.block.uncompressed_size),
973 _(check_names[iter.stream.flags->check])
974 };
975 printf("%*s %*s %*s %-*s",
976 tuklib_mbstr_fw(cols2[0],
977 headings[HEADING_TOTALSIZE].columns),
978 cols2[0],
979 tuklib_mbstr_fw(cols2[1],
980 headings[HEADING_UNCOMPSIZE].columns),
981 cols2[1],
982 tuklib_mbstr_fw(cols2[2],
983 headings[HEADING_RATIO].columns),
984 cols2[2],
985 tuklib_mbstr_fw(cols2[3], detailed
986 ? headings[HEADING_CHECK].columns : 1),
987 cols2[3]);
988
989 if (detailed) {
990 const lzma_vli compressed_size
991 = iter.block.unpadded_size
992 - bhi.header_size
993 - lzma_check_size(
994 iter.stream.flags->check);
995
996 const char *cols3[6] = {
997 check_value,
998 uint64_to_str(bhi.header_size, 0),
999 bhi.flags,
1000 uint64_to_str(compressed_size, 1),
1001 uint64_to_str(
1002 round_up_to_mib(bhi.memusage),
1003 2),
1004 bhi.filter_chain
1005 };
1006 // Show MiB for memory usage, because it
1007 // is the only size which is not in bytes.
1008 printf(" %-*s %*s %-*s %*s %*s MiB %s",
1009 checkval_width, cols3[0],
1010 tuklib_mbstr_fw(cols3[1], headings[
1011 HEADING_HEADERSIZE].columns),
1012 cols3[1],
1013 tuklib_mbstr_fw(cols3[2], headings[
1014 HEADING_HEADERFLAGS].columns),
1015 cols3[2],
1016 tuklib_mbstr_fw(cols3[3], headings[
1017 HEADING_COMPSIZE].columns),
1018 cols3[3],
1019 tuklib_mbstr_fw(cols3[4], headings[
1020 HEADING_MEMUSAGE].columns - 4),
1021 cols3[4],
1022 cols3[5]);
1023 }
1024
1025 putchar('\n');
1026 block_header_info_end(&bhi);
1027 }
1028 }
1029
1030 if (detailed) {
1031 printf(" %-*s %s MiB\n", COLON_STR(COLON_STR_MEMORY_NEEDED),
1032 uint64_to_str(
1033 round_up_to_mib(xfi->memusage_max), 0));
1034 printf(" %-*s %s\n", COLON_STR(COLON_STR_SIZES_IN_HEADERS),
1035 xfi->all_have_sizes ? _("Yes") : _("No"));
1036 //printf(" %-*s %s\n", COLON_STR(COLON_STR_MINIMUM_XZ_VERSION),
1037 printf(_(" Minimum XZ Utils version: %s\n"),
1038 xz_ver_to_str(xfi->min_version));
1039 }
1040
1041 return false;
1042 }
1043
1044
1045 static bool
print_info_robot(xz_file_info * xfi,file_pair * pair)1046 print_info_robot(xz_file_info *xfi, file_pair *pair)
1047 {
1048 char checks[CHECKS_STR_SIZE];
1049 get_check_names(checks, lzma_index_checks(xfi->idx), false);
1050
1051 printf("name\t%s\n", pair->src_name);
1052
1053 printf("file\t%" PRIu64 "\t%" PRIu64 "\t%" PRIu64 "\t%" PRIu64
1054 "\t%s\t%s\t%" PRIu64 "\n",
1055 lzma_index_stream_count(xfi->idx),
1056 lzma_index_block_count(xfi->idx),
1057 lzma_index_file_size(xfi->idx),
1058 lzma_index_uncompressed_size(xfi->idx),
1059 get_ratio(lzma_index_file_size(xfi->idx),
1060 lzma_index_uncompressed_size(xfi->idx)),
1061 checks,
1062 xfi->stream_padding);
1063
1064 if (message_verbosity_get() >= V_VERBOSE) {
1065 lzma_index_iter iter;
1066 lzma_index_iter_init(&iter, xfi->idx);
1067
1068 while (!lzma_index_iter_next(&iter, LZMA_INDEX_ITER_STREAM))
1069 printf("stream\t%" PRIu64 "\t%" PRIu64 "\t%" PRIu64
1070 "\t%" PRIu64 "\t%" PRIu64 "\t%" PRIu64
1071 "\t%s\t%s\t%" PRIu64 "\n",
1072 iter.stream.number,
1073 iter.stream.block_count,
1074 iter.stream.compressed_offset,
1075 iter.stream.uncompressed_offset,
1076 iter.stream.compressed_size,
1077 iter.stream.uncompressed_size,
1078 get_ratio(iter.stream.compressed_size,
1079 iter.stream.uncompressed_size),
1080 check_names[iter.stream.flags->check],
1081 iter.stream.padding);
1082
1083 lzma_index_iter_rewind(&iter);
1084
1085 while (!lzma_index_iter_next(&iter, LZMA_INDEX_ITER_BLOCK)) {
1086 block_header_info bhi = BLOCK_HEADER_INFO_INIT;
1087 if (message_verbosity_get() >= V_DEBUG
1088 && parse_details(
1089 pair, &iter, &bhi, xfi))
1090 return true;
1091
1092 printf("block\t%" PRIu64 "\t%" PRIu64 "\t%" PRIu64
1093 "\t%" PRIu64 "\t%" PRIu64
1094 "\t%" PRIu64 "\t%" PRIu64 "\t%s\t%s",
1095 iter.stream.number,
1096 iter.block.number_in_stream,
1097 iter.block.number_in_file,
1098 iter.block.compressed_file_offset,
1099 iter.block.uncompressed_file_offset,
1100 iter.block.total_size,
1101 iter.block.uncompressed_size,
1102 get_ratio(iter.block.total_size,
1103 iter.block.uncompressed_size),
1104 check_names[iter.stream.flags->check]);
1105
1106 if (message_verbosity_get() >= V_DEBUG)
1107 printf("\t%s\t%" PRIu32 "\t%s\t%" PRIu64
1108 "\t%" PRIu64 "\t%s",
1109 check_value,
1110 bhi.header_size,
1111 bhi.flags,
1112 bhi.compressed_size,
1113 bhi.memusage,
1114 bhi.filter_chain);
1115
1116 putchar('\n');
1117 block_header_info_end(&bhi);
1118 }
1119 }
1120
1121 if (message_verbosity_get() >= V_DEBUG)
1122 printf("summary\t%" PRIu64 "\t%s\t%" PRIu32 "\n",
1123 xfi->memusage_max,
1124 xfi->all_have_sizes ? "yes" : "no",
1125 xfi->min_version);
1126
1127 return false;
1128 }
1129
1130
1131 static void
update_totals(const xz_file_info * xfi)1132 update_totals(const xz_file_info *xfi)
1133 {
1134 // TODO: Integer overflow checks
1135 ++totals.files;
1136 totals.streams += lzma_index_stream_count(xfi->idx);
1137 totals.blocks += lzma_index_block_count(xfi->idx);
1138 totals.compressed_size += lzma_index_file_size(xfi->idx);
1139 totals.uncompressed_size += lzma_index_uncompressed_size(xfi->idx);
1140 totals.stream_padding += xfi->stream_padding;
1141 totals.checks |= lzma_index_checks(xfi->idx);
1142
1143 if (totals.memusage_max < xfi->memusage_max)
1144 totals.memusage_max = xfi->memusage_max;
1145
1146 if (totals.min_version < xfi->min_version)
1147 totals.min_version = xfi->min_version;
1148
1149 totals.all_have_sizes &= xfi->all_have_sizes;
1150
1151 return;
1152 }
1153
1154
1155 static void
print_totals_basic(void)1156 print_totals_basic(void)
1157 {
1158 // Print a separator line.
1159 char line[80];
1160 memset(line, '-', sizeof(line));
1161 line[sizeof(line) - 1] = '\0';
1162 puts(line);
1163
1164 // Get the check names.
1165 char checks[CHECKS_STR_SIZE];
1166 get_check_names(checks, totals.checks, false);
1167
1168 // Print the totals except the file count, which needs
1169 // special handling.
1170 printf("%5s %7s %11s %11s %5s %-7s ",
1171 uint64_to_str(totals.streams, 0),
1172 uint64_to_str(totals.blocks, 1),
1173 uint64_to_nicestr(totals.compressed_size,
1174 NICESTR_B, NICESTR_TIB, false, 2),
1175 uint64_to_nicestr(totals.uncompressed_size,
1176 NICESTR_B, NICESTR_TIB, false, 3),
1177 get_ratio(totals.compressed_size,
1178 totals.uncompressed_size),
1179 checks);
1180
1181 // Since we print totals only when there are at least two files,
1182 // the English message will always use "%s files". But some other
1183 // languages need different forms for different plurals so we
1184 // have to translate this with ngettext().
1185 //
1186 // TRANSLATORS: %s is an integer. Only the plural form of this
1187 // message is used (e.g. "2 files"). Test with "xz -l foo.xz bar.xz".
1188 printf(ngettext("%s file\n", "%s files\n",
1189 totals.files <= ULONG_MAX ? totals.files
1190 : (totals.files % 1000000) + 1000000),
1191 uint64_to_str(totals.files, 0));
1192
1193 return;
1194 }
1195
1196
1197 static void
print_totals_adv(void)1198 print_totals_adv(void)
1199 {
1200 putchar('\n');
1201 puts(_("Totals:"));
1202 printf(" %-*s %s\n", COLON_STR(COLON_STR_NUMBER_OF_FILES),
1203 uint64_to_str(totals.files, 0));
1204 print_adv_helper(totals.streams, totals.blocks,
1205 totals.compressed_size, totals.uncompressed_size,
1206 totals.checks, totals.stream_padding);
1207
1208 if (message_verbosity_get() >= V_DEBUG) {
1209 printf(" %-*s %s MiB\n", COLON_STR(COLON_STR_MEMORY_NEEDED),
1210 uint64_to_str(
1211 round_up_to_mib(totals.memusage_max), 0));
1212 printf(" %-*s %s\n", COLON_STR(COLON_STR_SIZES_IN_HEADERS),
1213 totals.all_have_sizes ? _("Yes") : _("No"));
1214 //printf(" %-*s %s\n", COLON_STR(COLON_STR_MINIMUM_XZ_VERSION),
1215 printf(_(" Minimum XZ Utils version: %s\n"),
1216 xz_ver_to_str(totals.min_version));
1217 }
1218
1219 return;
1220 }
1221
1222
1223 static void
print_totals_robot(void)1224 print_totals_robot(void)
1225 {
1226 char checks[CHECKS_STR_SIZE];
1227 get_check_names(checks, totals.checks, false);
1228
1229 printf("totals\t%" PRIu64 "\t%" PRIu64 "\t%" PRIu64 "\t%" PRIu64
1230 "\t%s\t%s\t%" PRIu64 "\t%" PRIu64,
1231 totals.streams,
1232 totals.blocks,
1233 totals.compressed_size,
1234 totals.uncompressed_size,
1235 get_ratio(totals.compressed_size,
1236 totals.uncompressed_size),
1237 checks,
1238 totals.stream_padding,
1239 totals.files);
1240
1241 if (message_verbosity_get() >= V_DEBUG)
1242 printf("\t%" PRIu64 "\t%s\t%" PRIu32,
1243 totals.memusage_max,
1244 totals.all_have_sizes ? "yes" : "no",
1245 totals.min_version);
1246
1247 putchar('\n');
1248
1249 return;
1250 }
1251
1252
1253 extern void
list_totals(void)1254 list_totals(void)
1255 {
1256 if (opt_robot) {
1257 // Always print totals in --robot mode. It can be convenient
1258 // in some cases and doesn't complicate usage of the
1259 // single-file case much.
1260 print_totals_robot();
1261
1262 } else if (totals.files > 1) {
1263 // For non-robot mode, totals are printed only if there
1264 // is more than one file.
1265 if (message_verbosity_get() <= V_WARNING)
1266 print_totals_basic();
1267 else
1268 print_totals_adv();
1269 }
1270
1271 return;
1272 }
1273
1274
1275 extern void
list_file(const char * filename)1276 list_file(const char *filename)
1277 {
1278 if (opt_format != FORMAT_XZ && opt_format != FORMAT_AUTO) {
1279 // The 'lzmainfo' message is printed only when --format=lzma
1280 // is used (it is implied if using "lzma" as the command
1281 // name). Thus instead of using message_fatal(), print
1282 // the messages separately and then call tuklib_exit()
1283 // like message_fatal() does.
1284 message(V_ERROR, _("--list works only on .xz files "
1285 "(--format=xz or --format=auto)"));
1286
1287 if (opt_format == FORMAT_LZMA)
1288 message(V_ERROR,
1289 _("Try 'lzmainfo' with .lzma files."));
1290
1291 tuklib_exit(E_ERROR, E_ERROR, false);
1292 }
1293
1294 message_filename(filename);
1295
1296 if (filename == stdin_filename) {
1297 message_error(_("--list does not support reading from "
1298 "standard input"));
1299 return;
1300 }
1301
1302 init_field_widths();
1303
1304 // Unset opt_stdout so that io_open_src() won't accept special files.
1305 // Set opt_force so that io_open_src() will follow symlinks.
1306 opt_stdout = false;
1307 opt_force = true;
1308 file_pair *pair = io_open_src(filename);
1309 if (pair == NULL)
1310 return;
1311
1312 xz_file_info xfi = XZ_FILE_INFO_INIT;
1313 if (!parse_indexes(&xfi, pair)) {
1314 bool fail;
1315
1316 // We have three main modes:
1317 // - --robot, which has submodes if --verbose is specified
1318 // once or twice
1319 // - Normal --list without --verbose
1320 // - --list with one or two --verbose
1321 if (opt_robot)
1322 fail = print_info_robot(&xfi, pair);
1323 else if (message_verbosity_get() <= V_WARNING)
1324 fail = print_info_basic(&xfi, pair);
1325 else
1326 fail = print_info_adv(&xfi, pair);
1327
1328 // Update the totals that are displayed after all
1329 // the individual files have been listed. Don't count
1330 // broken files.
1331 if (!fail)
1332 update_totals(&xfi);
1333
1334 lzma_index_end(xfi.idx, NULL);
1335 }
1336
1337 io_close(pair, false);
1338 return;
1339 }
1340