]> icculus.org git repositories - icculus/xz.git/blob - src/xz/list.c
Add initial version of xz --list.
[icculus/xz.git] / src / xz / list.c
1 ///////////////////////////////////////////////////////////////////////////////
2 //
3 /// \file       list.c
4 /// \brief      Listing information about .xz files
5 //
6 //  Author:     Lasse Collin
7 //
8 //  This file has been put into the public domain.
9 //  You can do whatever you want with this file.
10 //
11 ///////////////////////////////////////////////////////////////////////////////
12
13 #include "private.h"
14 #include "tuklib_integer.h"
15
16
17 /// Totals that are displayed if there was more than one file.
18 /// The "files" counter is also used in print_info_adv() to show
19 /// the file number.
20 static struct {
21         uint64_t files;
22         uint64_t streams;
23         uint64_t blocks;
24         uint64_t compressed_size;
25         uint64_t uncompressed_size;
26         uint32_t checks;
27 } totals = { 0, 0, 0, 0, 0, 0 };
28
29
30 /// \brief      Parse the Index(es) from the given .xz file
31 ///
32 /// \param      idx     If decoding is successful, *idx will be set to point
33 ///                     to lzma_index containing the decoded information.
34 ///                     On error, *idx is not modified.
35 /// \param      pair    Input file
36 ///
37 /// \return     On success, false is returned. On error, true is returned.
38 ///
39 // TODO: This function is pretty big. liblzma should have a function that
40 // takes a callback function to parse the Index(es) from a .xz file to make
41 // it easy for applications.
42 static bool
43 parse_indexes(lzma_index **idx, file_pair *pair)
44 {
45         if (pair->src_st.st_size <= 0) {
46                 message_error(_("%s: File is empty"), pair->src_name);
47                 return true;
48         }
49
50         if (pair->src_st.st_size < 2 * LZMA_STREAM_HEADER_SIZE) {
51                 message_error(_("%s: Too small to be a valid .xz file"),
52                                 pair->src_name);
53                 return true;
54         }
55
56         io_buf buf;
57         lzma_stream_flags header_flags;
58         lzma_stream_flags footer_flags;
59         lzma_ret ret;
60
61         // lzma_stream for the Index decoder
62         lzma_stream strm = LZMA_STREAM_INIT;
63
64         // All Indexes decoded so far
65         lzma_index *combined_index = NULL;
66
67         // The Index currently being decoded
68         lzma_index *this_index = NULL;
69
70         // Current position in the file. We parse the file backwards so
71         // initialize it to point to the end of the file.
72         off_t pos = pair->src_st.st_size;
73
74         // Each loop iteration decodes one Index.
75         do {
76                 // Check that there is enough data left to contain at least
77                 // the Stream Header and Stream Footer. This check cannot
78                 // fail in the first pass of this loop.
79                 if (pos < 2 * LZMA_STREAM_HEADER_SIZE) {
80                         message_error("%s: %s", pair->src_name,
81                                         message_strm(LZMA_DATA_ERROR));
82                         goto error;
83                 }
84
85                 pos -= LZMA_STREAM_HEADER_SIZE;
86                 lzma_vli stream_padding = 0;
87
88                 // Locate the Stream Footer. There may be Stream Padding which
89                 // we must skip when reading backwards.
90                 while (true) {
91                         if (pos < LZMA_STREAM_HEADER_SIZE) {
92                                 message_error("%s: %s", pair->src_name,
93                                                 message_strm(
94                                                         LZMA_DATA_ERROR));
95                                 goto error;
96                         }
97
98                         if (io_pread(pair, &buf,
99                                         LZMA_STREAM_HEADER_SIZE, pos))
100                                 goto error;
101
102                         // Stream Padding is always a multiple of four bytes.
103                         int i = 2;
104                         if (buf.u32[i] != 0)
105                                 break;
106
107                         // To avoid calling io_pread() for every four bytes
108                         // of Stream Padding, take advantage that we read
109                         // 12 bytes (LZMA_STREAM_HEADER_SIZE) already and
110                         // check them too before calling io_pread() again.
111                         do {
112                                 stream_padding += 4;
113                                 pos -= 4;
114                                 --i;
115                         } while (i >= 0 && buf.u32[i] == 0);
116                 }
117
118                 // Decode the Stream Footer.
119                 ret = lzma_stream_footer_decode(&footer_flags, buf.u8);
120                 if (ret != LZMA_OK) {
121                         message_error("%s: %s", pair->src_name,
122                                         message_strm(ret));
123                         goto error;
124                 }
125
126                 // Check that the size of the Index field looks sane.
127                 lzma_vli index_size = footer_flags.backward_size;
128                 if ((lzma_vli)(pos) < index_size + LZMA_STREAM_HEADER_SIZE) {
129                         message_error("%s: %s", pair->src_name,
130                                         message_strm(LZMA_DATA_ERROR));
131                         goto error;
132                 }
133
134                 // Set pos to the beginning of the Index.
135                 pos -= index_size;
136
137                 // See how much memory we can use for decoding this Index.
138                 uint64_t memlimit = hardware_memlimit_get();
139                 uint64_t memused = 0;
140                 if (combined_index != NULL) {
141                         memused = lzma_index_memused(combined_index);
142                         if (memused > memlimit)
143                                 message_bug();
144
145                         memlimit -= memused;
146                 }
147
148                 // Decode the Index.
149                 ret = lzma_index_decoder(&strm, &this_index, memlimit);
150                 if (ret != LZMA_OK) {
151                         message_error("%s: %s", pair->src_name,
152                                         message_strm(ret));
153                         goto error;
154                 }
155
156                 do {
157                         // Don't give the decoder more input than the
158                         // Index size.
159                         strm.avail_in = MIN(IO_BUFFER_SIZE, index_size);
160                         if (io_pread(pair, &buf, strm.avail_in, pos))
161                                 goto error;
162
163                         pos += strm.avail_in;
164                         index_size -= strm.avail_in;
165
166                         strm.next_in = buf.u8;
167                         ret = lzma_code(&strm, LZMA_RUN);
168
169                 } while (ret == LZMA_OK);
170
171                 // If the decoding seems to be successful, check also that
172                 // the Index decoder consumed as much input as indicated
173                 // by the Backward Size field.
174                 if (ret == LZMA_STREAM_END)
175                         if (index_size != 0 || strm.avail_in != 0)
176                                 ret = LZMA_DATA_ERROR;
177
178                 if (ret != LZMA_STREAM_END) {
179                         // LZMA_BUFFER_ERROR means that the Index decoder
180                         // would have liked more input than what the Index
181                         // size should be according to Stream Footer.
182                         // The message for LZMA_DATA_ERROR makes more
183                         // sense in that case.
184                         if (ret == LZMA_BUF_ERROR)
185                                 ret = LZMA_DATA_ERROR;
186
187                         message_error("%s: %s", pair->src_name,
188                                         message_strm(ret));
189
190                         // If the error was too low memory usage limit,
191                         // show also how much memory would have been needed.
192                         if (ret == LZMA_MEMLIMIT_ERROR) {
193                                 uint64_t needed = lzma_memusage(&strm);
194                                 if (UINT64_MAX - needed < memused)
195                                         needed = UINT64_MAX;
196                                 else
197                                         needed += memused;
198
199                                 message_mem_needed(V_ERROR, needed);
200                         }
201
202                         goto error;
203                 }
204
205                 // Decode the Stream Header and check that its Stream Flags
206                 // match the Stream Footer.
207                 pos -= footer_flags.backward_size + LZMA_STREAM_HEADER_SIZE;
208                 if ((lzma_vli)(pos) < lzma_index_total_size(this_index)) {
209                         message_error("%s: %s", pair->src_name,
210                                         message_strm(LZMA_DATA_ERROR));
211                         goto error;
212                 }
213
214                 pos -= lzma_index_total_size(this_index);
215                 if (io_pread(pair, &buf, LZMA_STREAM_HEADER_SIZE, pos))
216                         goto error;
217
218                 ret = lzma_stream_header_decode(&header_flags, buf.u8);
219                 if (ret != LZMA_OK) {
220                         message_error("%s: %s", pair->src_name,
221                                         message_strm(ret));
222                         goto error;
223                 }
224
225                 ret = lzma_stream_flags_compare(&header_flags, &footer_flags);
226                 if (ret != LZMA_OK) {
227                         message_error("%s: %s", pair->src_name,
228                                         message_strm(ret));
229                         goto error;
230                 }
231
232                 // Store the decoded Stream Flags into this_index. This is
233                 // needed so that we can print which Check is used in each
234                 // Stream.
235                 ret = lzma_index_stream_flags(this_index, &footer_flags);
236                 if (ret != LZMA_OK)
237                         message_bug();
238
239                 // Store also the size of the Stream Padding field. It is
240                 // needed to show the offsets of the Streams correctly.
241                 ret = lzma_index_stream_padding(this_index, stream_padding);
242                 if (ret != LZMA_OK)
243                         message_bug();
244
245                 if (combined_index != NULL) {
246                         // Append the earlier decoded Indexes
247                         // after this_index.
248                         ret = lzma_index_cat(
249                                         this_index, combined_index, NULL);
250                         if (ret != LZMA_OK) {
251                                 message_error("%s: %s", pair->src_name,
252                                                 message_strm(ret));
253                                 goto error;
254                         }
255                 }
256
257                 combined_index = this_index;
258                 this_index = NULL;
259
260         } while (pos > 0);
261
262         lzma_end(&strm);
263
264         // All OK. Make combined_index available to the caller.
265         *idx = combined_index;
266         return false;
267
268 error:
269         // Something went wrong, free the allocated memory.
270         lzma_end(&strm);
271         lzma_index_end(combined_index, NULL);
272         lzma_index_end(this_index, NULL);
273         return true;
274 }
275
276
277 /// \brief      Get the compression ratio
278 ///
279 /// This has slightly different format than that is used by in message.c.
280 static const char *
281 get_ratio(uint64_t compressed_size, uint64_t uncompressed_size)
282 {
283         if (uncompressed_size == 0)
284                 return "---";
285
286         const double ratio = (double)(compressed_size)
287                         / (double)(uncompressed_size);
288         if (ratio > 9.999)
289                 return "---";
290
291         static char buf[6];
292         snprintf(buf, sizeof(buf), "%.3f", ratio);
293         return buf;
294 }
295
296
297 static const char check_names[LZMA_CHECK_ID_MAX + 1][12] = {
298         "None",
299         "CRC32",
300         "Unknown-2",
301         "Unknown-3",
302         "CRC64",
303         "Unknown-5",
304         "Unknown-6",
305         "Unknown-7",
306         "Unknown-8",
307         "Unknown-9",
308         "SHA-256",
309         "Unknown-11",
310         "Unknown-12",
311         "Unknown-13",
312         "Unknown-14",
313         "Unknown-15",
314 };
315
316
317 /// \brief      Get a comma-separated list of Check names
318 ///
319 /// \param      checks  Bit mask of Checks to print
320 /// \param      space_after_comma
321 ///                     It's better to not use spaces in table-like listings,
322 ///                     but in more verbose formats a space after a comma
323 ///                     is good for readability.
324 static const char *
325 get_check_names(uint32_t checks, bool space_after_comma)
326 {
327         assert(checks != 0);
328
329         static char buf[sizeof(check_names)];
330         char *pos = buf;
331         size_t left = sizeof(buf);
332
333         const char *sep = space_after_comma ? ", " : ",";
334         bool comma = false;
335
336         for (size_t i = 0; i <= LZMA_CHECK_ID_MAX; ++i) {
337                 if (checks & (UINT32_C(1) << i)) {
338                         my_snprintf(&pos, &left, "%s%s",
339                                         comma ? sep : "", check_names[i]);
340                         comma = true;
341                 }
342         }
343
344         return buf;
345 }
346
347
348 /// \brief      Read the Check value from the .xz file and print it
349 ///
350 /// Since this requires a seek, listing all Check values for all Blocks can
351 /// be slow.
352 ///
353 /// \param      pair    Input file
354 /// \param      iter    Location of the Block whose Check value should
355 ///                     be printed.
356 ///
357 /// \return     False on success, true on I/O error.
358 static bool
359 print_check_value(file_pair *pair, const lzma_index_iter *iter)
360 {
361         // Don't read anything from the file if there is no integrity Check.
362         if (iter->stream.flags->check == LZMA_CHECK_NONE) {
363                 printf("---");
364                 return false;
365         }
366
367         // Locate and read the Check field.
368         const uint32_t size = lzma_check_size(iter->stream.flags->check);
369         const off_t offset = iter->block.compressed_file_offset
370                         + iter->block.total_size - size;
371         io_buf buf;
372         if (io_pread(pair, &buf, size, offset))
373                 return true;
374
375         // CRC32 and CRC64 are in little endian. Guess that all the future
376         // 32-bit and 64-bit Check values are little endian too. It shouldn't
377         // be a too big problem if this guess is wrong.
378         if (size == 4) {
379                 printf("%08" PRIx32, conv32le(buf.u32[0]));
380         } else if (size == 8) {
381                 printf("%016" PRIx64, conv64le(buf.u64[0]));
382         } else {
383                 for (size_t i = 0; i < size; ++i)
384                         printf("%02x", buf.u8[i]);
385         }
386
387         return false;
388 }
389
390
391 static void
392 print_info_basic(const lzma_index *idx, file_pair *pair)
393 {
394         static bool headings_displayed = false;
395         if (!headings_displayed) {
396                 headings_displayed = true;
397                 // TRANSLATORS: These are column titles. From Strms (Streams)
398                 // to Ratio, the columns are right aligned. Check and Filename
399                 // are left aligned. If you need longer words, it's OK to
400                 // use two lines here. Test with xz --list.
401                 puts(_("Strms  Blocks   Compressed Uncompressed  Ratio  "
402                                 "Check   Filename"));
403         }
404
405         printf("%5s %7s  %11s  %11s  %5s  %-7s %s\n",
406                         uint64_to_str(lzma_index_stream_count(idx), 0),
407                         uint64_to_str(lzma_index_block_count(idx), 1),
408                         uint64_to_nicestr(lzma_index_file_size(idx),
409                                 NICESTR_B, NICESTR_TIB, false, 2),
410                         uint64_to_nicestr(lzma_index_uncompressed_size(idx),
411                                 NICESTR_B, NICESTR_TIB, false, 3),
412                         get_ratio(lzma_index_file_size(idx),
413                                 lzma_index_uncompressed_size(idx)),
414                         get_check_names(lzma_index_checks(idx), false),
415                         pair->src_name);
416
417         return;
418 }
419
420
421 static void
422 print_adv_helper(uint64_t stream_count, uint64_t block_count,
423                 uint64_t compressed_size, uint64_t uncompressed_size,
424                 uint32_t checks)
425 {
426         printf(_("  Stream count:       %s\n"),
427                         uint64_to_str(stream_count, 0));
428         printf(_("  Block count:        %s\n"),
429                         uint64_to_str(block_count, 0));
430         printf(_("  Compressed size:    %s\n"),
431                         uint64_to_nicestr(compressed_size,
432                                 NICESTR_B, NICESTR_TIB, true, 0));
433         printf(_("  Uncompressed size:  %s\n"),
434                         uint64_to_nicestr(uncompressed_size,
435                                 NICESTR_B, NICESTR_TIB, true, 0));
436         printf(_("  Ratio:              %s\n"),
437                         get_ratio(compressed_size, uncompressed_size));
438         printf(_("  Check:              %s\n"),
439                         get_check_names(checks, true));
440         return;
441 }
442
443
444 static void
445 print_info_adv(const lzma_index *idx, file_pair *pair)
446 {
447         // Print an empty line between files.
448         static bool first_filename_printed = false;
449         if (!first_filename_printed)
450                 first_filename_printed = true;
451         else
452                 putchar('\n');
453
454         // Print the filename and overall information.
455         printf("%s (%" PRIu64 "):\n", pair->src_name, totals.files);
456         print_adv_helper(lzma_index_stream_count(idx),
457                         lzma_index_block_count(idx),
458                         lzma_index_file_size(idx),
459                         lzma_index_uncompressed_size(idx),
460                         lzma_index_checks(idx));
461
462         // TODO: The rest of this function needs some work. Currently
463         // the offsets are not printed, which could be useful even when
464         // printed in a less accurate format. On the other hand, maybe
465         // this should print the information with exact byte values,
466         // or maybe there should be at least an option to do that.
467         //
468         // We could also display some other info. E.g. it could be useful
469         // to quickly see how big is the biggest Block (uncompressed size)
470         // and if all Blocks have Compressed Size and Uncompressed Size
471         // fields present, which can be used e.g. for multithreaded
472         // decompression.
473
474         // Avoid printing Stream and Block lists when they wouldn't be useful.
475         bool show_blocks = false;
476         if (lzma_index_stream_count(idx) > 1) {
477                 puts(_("  Streams:"));
478                 puts(_("      Number      Blocks    Compressed   "
479                                 "Uncompressed   Ratio   Check"));
480
481                 lzma_index_iter iter;
482                 lzma_index_iter_init(&iter, idx);
483                 while (!lzma_index_iter_next(&iter, LZMA_INDEX_ITER_STREAM)) {
484                         if (iter.stream.block_count > 1)
485                                 show_blocks = true;
486
487                         printf("    %8s  %10s   %11s    %11s   %5s   %s\n",
488                                 uint64_to_str(iter.stream.number, 0),
489                                 uint64_to_str(iter.stream.block_count, 1),
490                                 uint64_to_nicestr(
491                                         iter.stream.compressed_size,
492                                         NICESTR_B, NICESTR_TIB, false, 2),
493                                 uint64_to_nicestr(
494                                         iter.stream.uncompressed_size,
495                                         NICESTR_B, NICESTR_TIB, false, 3),
496                                 get_ratio(iter.stream.compressed_size,
497                                         iter.stream.uncompressed_size),
498                                 check_names[iter.stream.flags->check]);
499                 }
500         }
501
502         if (show_blocks || lzma_index_block_count(idx)
503                                 > lzma_index_stream_count(idx)
504                         || message_verbosity_get() >= V_DEBUG) {
505                 puts(_("  Blocks:"));
506                 // FIXME: Number in Stream/file, which one is better?
507                 puts(_("      Stream      Number    Compressed   "
508                                 "Uncompressed   Ratio   Check"));
509
510                 lzma_index_iter iter;
511                 lzma_index_iter_init(&iter, idx);
512                 while (!lzma_index_iter_next(&iter, LZMA_INDEX_ITER_BLOCK)) {
513                         printf("    %8s  %10s   %11s    %11s   %5s   %-7s",
514                                 uint64_to_str(iter.stream.number, 0),
515                                 uint64_to_str(iter.block.number_in_stream, 1),
516                                 uint64_to_nicestr(iter.block.total_size,
517                                         NICESTR_B, NICESTR_TIB, false, 2),
518                                 uint64_to_nicestr(
519                                         iter.block.uncompressed_size,
520                                         NICESTR_B, NICESTR_TIB, false, 3),
521                                 get_ratio(iter.block.total_size,
522                                         iter.block.uncompressed_size),
523                                 check_names[iter.stream.flags->check]);
524
525                         if (message_verbosity_get() >= V_DEBUG)
526                                 if (print_check_value(pair, &iter))
527                                         return;
528
529                         putchar('\n');
530                 }
531         }
532 }
533
534
535 static void
536 print_info_robot(const lzma_index *idx, file_pair *pair)
537 {
538         printf("file\t%" PRIu64 "\t%" PRIu64 "\t%" PRIu64 "\t%" PRIu64
539                         "\t%s\t%s\t%s\n",
540                         lzma_index_stream_count(idx),
541                         lzma_index_block_count(idx),
542                         lzma_index_file_size(idx),
543                         lzma_index_uncompressed_size(idx),
544                         get_ratio(lzma_index_file_size(idx),
545                                 lzma_index_uncompressed_size(idx)),
546                         get_check_names(lzma_index_checks(idx), false),
547                         pair->src_name);
548
549         if (message_verbosity_get() >= V_VERBOSE) {
550                 lzma_index_iter iter;
551                 lzma_index_iter_init(&iter, idx);
552
553                 while (!lzma_index_iter_next(&iter, LZMA_INDEX_ITER_STREAM))
554                         printf("stream\t%" PRIu64 "\t%" PRIu64 "\t%" PRIu64
555                                 "\t%" PRIu64 "\t%" PRIu64
556                                 "\t%s\t%" PRIu64 "\t%s\n",
557                                 iter.stream.number,
558                                 iter.stream.compressed_offset,
559                                 iter.stream.uncompressed_offset,
560                                 iter.stream.compressed_size,
561                                 iter.stream.uncompressed_size,
562                                 get_ratio(iter.stream.compressed_size,
563                                         iter.stream.uncompressed_size),
564                                 iter.stream.padding,
565                                 check_names[iter.stream.flags->check]);
566
567                 lzma_index_iter_rewind(&iter);
568                 while (!lzma_index_iter_next(&iter, LZMA_INDEX_ITER_BLOCK)) {
569                         printf("block\t%" PRIu64 "\t%" PRIu64 "\t%" PRIu64
570                                         "\t%" PRIu64 "\t%" PRIu64
571                                         "\t%" PRIu64 "\t%" PRIu64 "\t%s\t%s",
572                                         iter.stream.number,
573                                         iter.block.number_in_stream,
574                                         iter.block.number_in_file,
575                                         iter.block.compressed_file_offset,
576                                         iter.block.uncompressed_file_offset,
577                                         iter.block.total_size,
578                                         iter.block.uncompressed_size,
579                                         get_ratio(iter.block.total_size,
580                                                 iter.block.uncompressed_size),
581                                         check_names[iter.stream.flags->check]);
582
583                         if (message_verbosity_get() >= V_DEBUG) {
584                                 putchar('\t');
585                                 if (print_check_value(pair, &iter))
586                                         return;
587                         }
588
589                         putchar('\n');
590                 }
591         }
592
593         return;
594 }
595
596
597 static void
598 update_totals(const lzma_index *idx)
599 {
600         // TODO: Integer overflow checks
601         ++totals.files;
602         totals.streams += lzma_index_stream_count(idx);
603         totals.blocks += lzma_index_block_count(idx);
604         totals.compressed_size += lzma_index_file_size(idx);
605         totals.uncompressed_size += lzma_index_uncompressed_size(idx);
606         totals.checks |= lzma_index_checks(idx);
607         return;
608 }
609
610
611 static void
612 print_totals_basic(void)
613 {
614         // Print a separator line.
615         char line[80];
616         memset(line, '-', sizeof(line));
617         line[sizeof(line) - 1] = '\0';
618         puts(line);
619
620         // Print the totals except the file count, which needs
621         // special handling.
622         printf("%5s %7s  %11s  %11s  %5s  %-7s ",
623                         uint64_to_str(totals.streams, 0),
624                         uint64_to_str(totals.blocks, 1),
625                         uint64_to_nicestr(totals.compressed_size,
626                                 NICESTR_B, NICESTR_TIB, false, 2),
627                         uint64_to_nicestr(totals.uncompressed_size,
628                                 NICESTR_B, NICESTR_TIB, false, 3),
629                         get_ratio(totals.compressed_size,
630                                 totals.uncompressed_size),
631                         get_check_names(totals.checks, false));
632
633         // Since we print totals only when there are at least two files,
634         // the English message will always use "%s files". But some other
635         // languages need different forms for different plurals so we
636         // have to translate this string still.
637         //
638         // TRANSLATORS: This simply indicates the number of files shown
639         // by --list even though the format string uses %s.
640         printf(N_("%s file", "%s files\n",
641                         totals.files <= ULONG_MAX ? totals.files
642                                 : (totals.files % 1000000) + 1000000),
643                         uint64_to_str(totals.files, 0));
644
645         return;
646 }
647
648
649 static void
650 print_totals_adv(void)
651 {
652         putchar('\n');
653         puts(_("Totals:"));
654         printf(_("  Number of files:    %s\n"),
655                         uint64_to_str(totals.files, 0));
656         print_adv_helper(totals.streams, totals.blocks,
657                         totals.compressed_size, totals.uncompressed_size,
658                         totals.checks);
659
660         return;
661 }
662
663
664 static void
665 print_totals_robot(void)
666 {
667         printf("totals\t%" PRIu64 "\t%" PRIu64 "\t%" PRIu64 "\t%" PRIu64
668                         "\t%s\t%s\t%" PRIu64 "\n",
669                         totals.streams,
670                         totals.blocks,
671                         totals.compressed_size,
672                         totals.uncompressed_size,
673                         get_ratio(totals.compressed_size,
674                                 totals.uncompressed_size),
675                         get_check_names(totals.checks, false),
676                         totals.files);
677
678         return;
679 }
680
681
682 extern void
683 list_totals(void)
684 {
685         if (opt_robot) {
686                 // Always print totals in --robot mode. It can be convenient
687                 // in some cases and doesn't complicate usage of the
688                 // single-file case much.
689                 print_totals_robot();
690
691         } else if (totals.files > 1) {
692                 // For non-robot mode, totals are printed only if there
693                 // is more than one file.
694                 if (message_verbosity_get() <= V_WARNING)
695                         print_totals_basic();
696                 else
697                         print_totals_adv();
698         }
699
700         return;
701 }
702
703
704 extern void
705 list_file(const char *filename)
706 {
707         if (opt_format != FORMAT_XZ && opt_format != FORMAT_AUTO)
708                 message_fatal(_("--list works only on .xz files "
709                                 "(--format=xz or --format=auto)"));
710
711         if (strcmp(filename, "-") == 0) {
712                 message_error(_("--list does not support reading from "
713                                 "standard input"));
714                 return;
715         }
716
717         if (is_empty_filename(filename))
718                 return;
719
720         // Set opt_stdout so that io_open() won't create a new file.
721         // Disable also sparse mode so that it doesn't remove O_APPEND
722         // from stdout.
723         opt_stdout = true;
724         io_no_sparse();
725         file_pair *pair = io_open(filename);
726         if (pair == NULL)
727                 return;
728
729         lzma_index *idx;
730         if (!parse_indexes(&idx, pair)) {
731                 // Update the totals that are displayed after all
732                 // the individual files have been listed.
733                 update_totals(idx);
734
735                 // We have three main modes:
736                 //  - --robot, which has submodes if --verbose is specified
737                 //     once or twice
738                 //  - Normal --list without --verbose
739                 //  - --list with one or two --verbose
740                 if (opt_robot)
741                         print_info_robot(idx, pair);
742                 else if (message_verbosity_get() <= V_WARNING)
743                         print_info_basic(idx, pair);
744                 else
745                         print_info_adv(idx, pair);
746
747                 lzma_index_end(idx, NULL);
748         }
749
750         io_close(pair, false);
751         return;
752 }