1207753Smm/////////////////////////////////////////////////////////////////////////////// 2207753Smm// 3207753Smm/// \file list.c 4207753Smm/// \brief Listing information about .xz files 5207753Smm// 6207753Smm// Author: Lasse Collin 7207753Smm// 8207753Smm// This file has been put into the public domain. 9207753Smm// You can do whatever you want with this file. 10207753Smm// 11207753Smm/////////////////////////////////////////////////////////////////////////////// 12207753Smm 13207753Smm#include "private.h" 14207753Smm#include "tuklib_integer.h" 15207753Smm 16207753Smm 17213700Smm/// Information about a .xz file 18213700Smmtypedef struct { 19213700Smm /// Combined Index of all Streams in the file 20213700Smm lzma_index *idx; 21213700Smm 22213700Smm /// Total amount of Stream Padding 23213700Smm uint64_t stream_padding; 24213700Smm 25213700Smm /// Highest memory usage so far 26213700Smm uint64_t memusage_max; 27213700Smm 28213700Smm /// True if all Blocks so far have Compressed Size and 29213700Smm /// Uncompressed Size fields 30213700Smm bool all_have_sizes; 31213700Smm 32213700Smm} xz_file_info; 33213700Smm 34213700Smm#define XZ_FILE_INFO_INIT { NULL, 0, 0, true } 35213700Smm 36213700Smm 37213700Smm/// Information about a .xz Block 38213700Smmtypedef struct { 39213700Smm /// Size of the Block Header 40213700Smm uint32_t header_size; 41213700Smm 42213700Smm /// A few of the Block Flags as a string 43213700Smm char flags[3]; 44213700Smm 45213700Smm /// Size of the Compressed Data field in the Block 46213700Smm lzma_vli compressed_size; 47213700Smm 48213700Smm /// Decoder memory usage for this Block 49213700Smm uint64_t memusage; 50213700Smm 51213700Smm /// The filter chain of this Block in human-readable form 52213700Smm char filter_chain[FILTERS_STR_SIZE]; 53213700Smm 54213700Smm} block_header_info; 55213700Smm 56213700Smm 57213700Smm/// Check ID to string mapping 58213700Smmstatic const char check_names[LZMA_CHECK_ID_MAX + 1][12] = { 59213700Smm // TRANSLATORS: Indicates that there is no integrity check. 60213700Smm // This string is used in tables, so the width must not 61213700Smm // exceed ten columns with a fixed-width font. 62213700Smm N_("None"), 63213700Smm "CRC32", 64213700Smm // TRANSLATORS: Indicates that integrity check name is not known, 65213700Smm // but the Check ID is known (here 2). This and other "Unknown-N" 66213700Smm // strings are used in tables, so the width must not exceed ten 67213700Smm // columns with a fixed-width font. It's OK to omit the dash if 68213700Smm // you need space for one extra letter, but don't use spaces. 69213700Smm N_("Unknown-2"), 70213700Smm N_("Unknown-3"), 71213700Smm "CRC64", 72213700Smm N_("Unknown-5"), 73213700Smm N_("Unknown-6"), 74213700Smm N_("Unknown-7"), 75213700Smm N_("Unknown-8"), 76213700Smm N_("Unknown-9"), 77213700Smm "SHA-256", 78213700Smm N_("Unknown-11"), 79213700Smm N_("Unknown-12"), 80213700Smm N_("Unknown-13"), 81213700Smm N_("Unknown-14"), 82213700Smm N_("Unknown-15"), 83213700Smm}; 84213700Smm 85213700Smm/// Buffer size for get_check_names(). This may be a bit ridiculous, 86213700Smm/// but at least it's enough if some language needs many multibyte chars. 87213700Smm#define CHECKS_STR_SIZE 1024 88213700Smm 89213700Smm 90213700Smm/// Value of the Check field as hexadecimal string. 91213700Smm/// This is set by parse_check_value(). 92213700Smmstatic char check_value[2 * LZMA_CHECK_SIZE_MAX + 1]; 93213700Smm 94213700Smm 95207753Smm/// Totals that are displayed if there was more than one file. 96207753Smm/// The "files" counter is also used in print_info_adv() to show 97207753Smm/// the file number. 98207753Smmstatic struct { 99207753Smm uint64_t files; 100207753Smm uint64_t streams; 101207753Smm uint64_t blocks; 102207753Smm uint64_t compressed_size; 103207753Smm uint64_t uncompressed_size; 104213700Smm uint64_t stream_padding; 105213700Smm uint64_t memusage_max; 106207753Smm uint32_t checks; 107213700Smm bool all_have_sizes; 108213700Smm} totals = { 0, 0, 0, 0, 0, 0, 0, 0, true }; 109207753Smm 110207753Smm 111207753Smm/// \brief Parse the Index(es) from the given .xz file 112207753Smm/// 113213700Smm/// \param xfi Pointer to structure where the decoded information 114213700Smm/// is stored. 115207753Smm/// \param pair Input file 116207753Smm/// 117207753Smm/// \return On success, false is returned. On error, true is returned. 118207753Smm/// 119207753Smm// TODO: This function is pretty big. liblzma should have a function that 120207753Smm// takes a callback function to parse the Index(es) from a .xz file to make 121207753Smm// it easy for applications. 122207753Smmstatic bool 123213700Smmparse_indexes(xz_file_info *xfi, file_pair *pair) 124207753Smm{ 125207753Smm if (pair->src_st.st_size <= 0) { 126207753Smm message_error(_("%s: File is empty"), pair->src_name); 127207753Smm return true; 128207753Smm } 129207753Smm 130207753Smm if (pair->src_st.st_size < 2 * LZMA_STREAM_HEADER_SIZE) { 131207753Smm message_error(_("%s: Too small to be a valid .xz file"), 132207753Smm pair->src_name); 133207753Smm return true; 134207753Smm } 135207753Smm 136207753Smm io_buf buf; 137207753Smm lzma_stream_flags header_flags; 138207753Smm lzma_stream_flags footer_flags; 139207753Smm lzma_ret ret; 140207753Smm 141207753Smm // lzma_stream for the Index decoder 142207753Smm lzma_stream strm = LZMA_STREAM_INIT; 143207753Smm 144207753Smm // All Indexes decoded so far 145207753Smm lzma_index *combined_index = NULL; 146207753Smm 147207753Smm // The Index currently being decoded 148207753Smm lzma_index *this_index = NULL; 149207753Smm 150207753Smm // Current position in the file. We parse the file backwards so 151207753Smm // initialize it to point to the end of the file. 152207753Smm off_t pos = pair->src_st.st_size; 153207753Smm 154207753Smm // Each loop iteration decodes one Index. 155207753Smm do { 156207753Smm // Check that there is enough data left to contain at least 157207753Smm // the Stream Header and Stream Footer. This check cannot 158207753Smm // fail in the first pass of this loop. 159207753Smm if (pos < 2 * LZMA_STREAM_HEADER_SIZE) { 160207753Smm message_error("%s: %s", pair->src_name, 161207753Smm message_strm(LZMA_DATA_ERROR)); 162207753Smm goto error; 163207753Smm } 164207753Smm 165207753Smm pos -= LZMA_STREAM_HEADER_SIZE; 166207753Smm lzma_vli stream_padding = 0; 167207753Smm 168207753Smm // Locate the Stream Footer. There may be Stream Padding which 169207753Smm // we must skip when reading backwards. 170207753Smm while (true) { 171207753Smm if (pos < LZMA_STREAM_HEADER_SIZE) { 172207753Smm message_error("%s: %s", pair->src_name, 173207753Smm message_strm( 174207753Smm LZMA_DATA_ERROR)); 175207753Smm goto error; 176207753Smm } 177207753Smm 178207753Smm if (io_pread(pair, &buf, 179207753Smm LZMA_STREAM_HEADER_SIZE, pos)) 180207753Smm goto error; 181207753Smm 182207753Smm // Stream Padding is always a multiple of four bytes. 183207753Smm int i = 2; 184207753Smm if (buf.u32[i] != 0) 185207753Smm break; 186207753Smm 187207753Smm // To avoid calling io_pread() for every four bytes 188207753Smm // of Stream Padding, take advantage that we read 189207753Smm // 12 bytes (LZMA_STREAM_HEADER_SIZE) already and 190207753Smm // check them too before calling io_pread() again. 191207753Smm do { 192207753Smm stream_padding += 4; 193207753Smm pos -= 4; 194207753Smm --i; 195207753Smm } while (i >= 0 && buf.u32[i] == 0); 196207753Smm } 197207753Smm 198207753Smm // Decode the Stream Footer. 199207753Smm ret = lzma_stream_footer_decode(&footer_flags, buf.u8); 200207753Smm if (ret != LZMA_OK) { 201207753Smm message_error("%s: %s", pair->src_name, 202207753Smm message_strm(ret)); 203207753Smm goto error; 204207753Smm } 205207753Smm 206263286Sdelphij // Check that the Stream Footer doesn't specify something 207263286Sdelphij // that we don't support. This can only happen if the xz 208263286Sdelphij // version is older than liblzma and liblzma supports 209263286Sdelphij // something new. 210263286Sdelphij // 211263286Sdelphij // It is enough to check Stream Footer. Stream Header must 212263286Sdelphij // match when it is compared against Stream Footer with 213263286Sdelphij // lzma_stream_flags_compare(). 214263286Sdelphij if (footer_flags.version != 0) { 215263286Sdelphij message_error("%s: %s", pair->src_name, 216263286Sdelphij message_strm(LZMA_OPTIONS_ERROR)); 217263286Sdelphij goto error; 218263286Sdelphij } 219263286Sdelphij 220207753Smm // Check that the size of the Index field looks sane. 221207753Smm lzma_vli index_size = footer_flags.backward_size; 222207753Smm if ((lzma_vli)(pos) < index_size + LZMA_STREAM_HEADER_SIZE) { 223207753Smm message_error("%s: %s", pair->src_name, 224207753Smm message_strm(LZMA_DATA_ERROR)); 225207753Smm goto error; 226207753Smm } 227207753Smm 228207753Smm // Set pos to the beginning of the Index. 229207753Smm pos -= index_size; 230207753Smm 231207753Smm // See how much memory we can use for decoding this Index. 232213700Smm uint64_t memlimit = hardware_memlimit_get(MODE_LIST); 233207753Smm uint64_t memused = 0; 234207753Smm if (combined_index != NULL) { 235207753Smm memused = lzma_index_memused(combined_index); 236207753Smm if (memused > memlimit) 237207753Smm message_bug(); 238207753Smm 239207753Smm memlimit -= memused; 240207753Smm } 241207753Smm 242207753Smm // Decode the Index. 243207753Smm ret = lzma_index_decoder(&strm, &this_index, memlimit); 244207753Smm if (ret != LZMA_OK) { 245207753Smm message_error("%s: %s", pair->src_name, 246207753Smm message_strm(ret)); 247207753Smm goto error; 248207753Smm } 249207753Smm 250207753Smm do { 251207753Smm // Don't give the decoder more input than the 252207753Smm // Index size. 253213700Smm strm.avail_in = my_min(IO_BUFFER_SIZE, index_size); 254207753Smm if (io_pread(pair, &buf, strm.avail_in, pos)) 255207753Smm goto error; 256207753Smm 257207753Smm pos += strm.avail_in; 258207753Smm index_size -= strm.avail_in; 259207753Smm 260207753Smm strm.next_in = buf.u8; 261207753Smm ret = lzma_code(&strm, LZMA_RUN); 262207753Smm 263207753Smm } while (ret == LZMA_OK); 264207753Smm 265207753Smm // If the decoding seems to be successful, check also that 266207753Smm // the Index decoder consumed as much input as indicated 267207753Smm // by the Backward Size field. 268207753Smm if (ret == LZMA_STREAM_END) 269207753Smm if (index_size != 0 || strm.avail_in != 0) 270207753Smm ret = LZMA_DATA_ERROR; 271207753Smm 272207753Smm if (ret != LZMA_STREAM_END) { 273207753Smm // LZMA_BUFFER_ERROR means that the Index decoder 274207753Smm // would have liked more input than what the Index 275207753Smm // size should be according to Stream Footer. 276207753Smm // The message for LZMA_DATA_ERROR makes more 277207753Smm // sense in that case. 278207753Smm if (ret == LZMA_BUF_ERROR) 279207753Smm ret = LZMA_DATA_ERROR; 280207753Smm 281207753Smm message_error("%s: %s", pair->src_name, 282207753Smm message_strm(ret)); 283207753Smm 284207753Smm // If the error was too low memory usage limit, 285207753Smm // show also how much memory would have been needed. 286207753Smm if (ret == LZMA_MEMLIMIT_ERROR) { 287207753Smm uint64_t needed = lzma_memusage(&strm); 288207753Smm if (UINT64_MAX - needed < memused) 289207753Smm needed = UINT64_MAX; 290207753Smm else 291207753Smm needed += memused; 292207753Smm 293207753Smm message_mem_needed(V_ERROR, needed); 294207753Smm } 295207753Smm 296207753Smm goto error; 297207753Smm } 298207753Smm 299207753Smm // Decode the Stream Header and check that its Stream Flags 300207753Smm // match the Stream Footer. 301207753Smm pos -= footer_flags.backward_size + LZMA_STREAM_HEADER_SIZE; 302207753Smm if ((lzma_vli)(pos) < lzma_index_total_size(this_index)) { 303207753Smm message_error("%s: %s", pair->src_name, 304207753Smm message_strm(LZMA_DATA_ERROR)); 305207753Smm goto error; 306207753Smm } 307207753Smm 308207753Smm pos -= lzma_index_total_size(this_index); 309207753Smm if (io_pread(pair, &buf, LZMA_STREAM_HEADER_SIZE, pos)) 310207753Smm goto error; 311207753Smm 312207753Smm ret = lzma_stream_header_decode(&header_flags, buf.u8); 313207753Smm if (ret != LZMA_OK) { 314207753Smm message_error("%s: %s", pair->src_name, 315207753Smm message_strm(ret)); 316207753Smm goto error; 317207753Smm } 318207753Smm 319207753Smm ret = lzma_stream_flags_compare(&header_flags, &footer_flags); 320207753Smm if (ret != LZMA_OK) { 321207753Smm message_error("%s: %s", pair->src_name, 322207753Smm message_strm(ret)); 323207753Smm goto error; 324207753Smm } 325207753Smm 326207753Smm // Store the decoded Stream Flags into this_index. This is 327207753Smm // needed so that we can print which Check is used in each 328207753Smm // Stream. 329207753Smm ret = lzma_index_stream_flags(this_index, &footer_flags); 330207753Smm if (ret != LZMA_OK) 331207753Smm message_bug(); 332207753Smm 333207753Smm // Store also the size of the Stream Padding field. It is 334207753Smm // needed to show the offsets of the Streams correctly. 335207753Smm ret = lzma_index_stream_padding(this_index, stream_padding); 336207753Smm if (ret != LZMA_OK) 337207753Smm message_bug(); 338207753Smm 339207753Smm if (combined_index != NULL) { 340207753Smm // Append the earlier decoded Indexes 341207753Smm // after this_index. 342207753Smm ret = lzma_index_cat( 343207753Smm this_index, combined_index, NULL); 344207753Smm if (ret != LZMA_OK) { 345207753Smm message_error("%s: %s", pair->src_name, 346207753Smm message_strm(ret)); 347207753Smm goto error; 348207753Smm } 349207753Smm } 350207753Smm 351207753Smm combined_index = this_index; 352207753Smm this_index = NULL; 353207753Smm 354213700Smm xfi->stream_padding += stream_padding; 355213700Smm 356207753Smm } while (pos > 0); 357207753Smm 358207753Smm lzma_end(&strm); 359207753Smm 360207753Smm // All OK. Make combined_index available to the caller. 361213700Smm xfi->idx = combined_index; 362207753Smm return false; 363207753Smm 364207753Smmerror: 365207753Smm // Something went wrong, free the allocated memory. 366207753Smm lzma_end(&strm); 367207753Smm lzma_index_end(combined_index, NULL); 368207753Smm lzma_index_end(this_index, NULL); 369207753Smm return true; 370207753Smm} 371207753Smm 372207753Smm 373213700Smm/// \brief Parse the Block Header 374213700Smm/// 375213700Smm/// The result is stored into *bhi. The caller takes care of initializing it. 376213700Smm/// 377213700Smm/// \return False on success, true on error. 378213700Smmstatic bool 379213700Smmparse_block_header(file_pair *pair, const lzma_index_iter *iter, 380213700Smm block_header_info *bhi, xz_file_info *xfi) 381213700Smm{ 382213700Smm#if IO_BUFFER_SIZE < LZMA_BLOCK_HEADER_SIZE_MAX 383213700Smm# error IO_BUFFER_SIZE < LZMA_BLOCK_HEADER_SIZE_MAX 384213700Smm#endif 385213700Smm 386213700Smm // Get the whole Block Header with one read, but don't read past 387213700Smm // the end of the Block (or even its Check field). 388213700Smm const uint32_t size = my_min(iter->block.total_size 389213700Smm - lzma_check_size(iter->stream.flags->check), 390213700Smm LZMA_BLOCK_HEADER_SIZE_MAX); 391213700Smm io_buf buf; 392213700Smm if (io_pread(pair, &buf, size, iter->block.compressed_file_offset)) 393213700Smm return true; 394213700Smm 395213700Smm // Zero would mean Index Indicator and thus not a valid Block. 396213700Smm if (buf.u8[0] == 0) 397213700Smm goto data_error; 398213700Smm 399223935Smm // Initialize the block structure and decode Block Header Size. 400223935Smm lzma_filter filters[LZMA_FILTERS_MAX + 1]; 401213700Smm lzma_block block; 402213700Smm block.version = 0; 403213700Smm block.check = iter->stream.flags->check; 404213700Smm block.filters = filters; 405213700Smm 406213700Smm block.header_size = lzma_block_header_size_decode(buf.u8[0]); 407213700Smm if (block.header_size > size) 408213700Smm goto data_error; 409213700Smm 410213700Smm // Decode the Block Header. 411213700Smm switch (lzma_block_header_decode(&block, NULL, buf.u8)) { 412213700Smm case LZMA_OK: 413213700Smm break; 414213700Smm 415213700Smm case LZMA_OPTIONS_ERROR: 416213700Smm message_error("%s: %s", pair->src_name, 417213700Smm message_strm(LZMA_OPTIONS_ERROR)); 418213700Smm return true; 419213700Smm 420213700Smm case LZMA_DATA_ERROR: 421213700Smm goto data_error; 422213700Smm 423213700Smm default: 424213700Smm message_bug(); 425213700Smm } 426213700Smm 427213700Smm // Check the Block Flags. These must be done before calling 428213700Smm // lzma_block_compressed_size(), because it overwrites 429213700Smm // block.compressed_size. 430213700Smm bhi->flags[0] = block.compressed_size != LZMA_VLI_UNKNOWN 431213700Smm ? 'c' : '-'; 432213700Smm bhi->flags[1] = block.uncompressed_size != LZMA_VLI_UNKNOWN 433213700Smm ? 'u' : '-'; 434213700Smm bhi->flags[2] = '\0'; 435213700Smm 436213700Smm // Collect information if all Blocks have both Compressed Size 437213700Smm // and Uncompressed Size fields. They can be useful e.g. for 438213700Smm // multi-threaded decompression so it can be useful to know it. 439213700Smm xfi->all_have_sizes &= block.compressed_size != LZMA_VLI_UNKNOWN 440213700Smm && block.uncompressed_size != LZMA_VLI_UNKNOWN; 441213700Smm 442213700Smm // Validate or set block.compressed_size. 443213700Smm switch (lzma_block_compressed_size(&block, 444213700Smm iter->block.unpadded_size)) { 445213700Smm case LZMA_OK: 446263286Sdelphij // Validate also block.uncompressed_size if it is present. 447263286Sdelphij // If it isn't present, there's no need to set it since 448263286Sdelphij // we aren't going to actually decompress the Block; if 449263286Sdelphij // we were decompressing, then we should set it so that 450263286Sdelphij // the Block decoder could validate the Uncompressed Size 451263286Sdelphij // that was stored in the Index. 452263286Sdelphij if (block.uncompressed_size == LZMA_VLI_UNKNOWN 453263286Sdelphij || block.uncompressed_size 454263286Sdelphij == iter->block.uncompressed_size) 455263286Sdelphij break; 456213700Smm 457263286Sdelphij // If the above fails, the file is corrupt so 458263286Sdelphij // LZMA_DATA_ERROR is a good error code. 459263286Sdelphij 460213700Smm case LZMA_DATA_ERROR: 461223935Smm // Free the memory allocated by lzma_block_header_decode(). 462223935Smm for (size_t i = 0; filters[i].id != LZMA_VLI_UNKNOWN; ++i) 463223935Smm free(filters[i].options); 464223935Smm 465213700Smm goto data_error; 466213700Smm 467213700Smm default: 468213700Smm message_bug(); 469213700Smm } 470213700Smm 471213700Smm // Copy the known sizes. 472213700Smm bhi->header_size = block.header_size; 473213700Smm bhi->compressed_size = block.compressed_size; 474213700Smm 475213700Smm // Calculate the decoder memory usage and update the maximum 476213700Smm // memory usage of this Block. 477213700Smm bhi->memusage = lzma_raw_decoder_memusage(filters); 478213700Smm if (xfi->memusage_max < bhi->memusage) 479213700Smm xfi->memusage_max = bhi->memusage; 480213700Smm 481213700Smm // Convert the filter chain to human readable form. 482213700Smm message_filters_to_str(bhi->filter_chain, filters, false); 483213700Smm 484213700Smm // Free the memory allocated by lzma_block_header_decode(). 485213700Smm for (size_t i = 0; filters[i].id != LZMA_VLI_UNKNOWN; ++i) 486213700Smm free(filters[i].options); 487213700Smm 488213700Smm return false; 489213700Smm 490213700Smmdata_error: 491213700Smm // Show the error message. 492213700Smm message_error("%s: %s", pair->src_name, 493213700Smm message_strm(LZMA_DATA_ERROR)); 494213700Smm return true; 495213700Smm} 496213700Smm 497213700Smm 498213700Smm/// \brief Parse the Check field and put it into check_value[] 499213700Smm/// 500213700Smm/// \return False on success, true on error. 501213700Smmstatic bool 502213700Smmparse_check_value(file_pair *pair, const lzma_index_iter *iter) 503213700Smm{ 504213700Smm // Don't read anything from the file if there is no integrity Check. 505213700Smm if (iter->stream.flags->check == LZMA_CHECK_NONE) { 506213700Smm snprintf(check_value, sizeof(check_value), "---"); 507213700Smm return false; 508213700Smm } 509213700Smm 510213700Smm // Locate and read the Check field. 511213700Smm const uint32_t size = lzma_check_size(iter->stream.flags->check); 512213700Smm const off_t offset = iter->block.compressed_file_offset 513213700Smm + iter->block.total_size - size; 514213700Smm io_buf buf; 515213700Smm if (io_pread(pair, &buf, size, offset)) 516213700Smm return true; 517213700Smm 518213700Smm // CRC32 and CRC64 are in little endian. Guess that all the future 519213700Smm // 32-bit and 64-bit Check values are little endian too. It shouldn't 520213700Smm // be a too big problem if this guess is wrong. 521213700Smm if (size == 4) 522213700Smm snprintf(check_value, sizeof(check_value), 523213700Smm "%08" PRIx32, conv32le(buf.u32[0])); 524213700Smm else if (size == 8) 525213700Smm snprintf(check_value, sizeof(check_value), 526213700Smm "%016" PRIx64, conv64le(buf.u64[0])); 527213700Smm else 528213700Smm for (size_t i = 0; i < size; ++i) 529213700Smm snprintf(check_value + i * 2, 3, "%02x", buf.u8[i]); 530213700Smm 531213700Smm return false; 532213700Smm} 533213700Smm 534213700Smm 535213700Smm/// \brief Parse detailed information about a Block 536213700Smm/// 537213700Smm/// Since this requires seek(s), listing information about all Blocks can 538213700Smm/// be slow. 539213700Smm/// 540213700Smm/// \param pair Input file 541213700Smm/// \param iter Location of the Block whose Check value should 542213700Smm/// be printed. 543213700Smm/// \param bhi Pointer to structure where to store the information 544213700Smm/// about the Block Header field. 545213700Smm/// 546213700Smm/// \return False on success, true on error. If an error occurs, 547213700Smm/// the error message is printed too so the caller doesn't 548213700Smm/// need to worry about that. 549213700Smmstatic bool 550213700Smmparse_details(file_pair *pair, const lzma_index_iter *iter, 551213700Smm block_header_info *bhi, xz_file_info *xfi) 552213700Smm{ 553213700Smm if (parse_block_header(pair, iter, bhi, xfi)) 554213700Smm return true; 555213700Smm 556213700Smm if (parse_check_value(pair, iter)) 557213700Smm return true; 558213700Smm 559213700Smm return false; 560213700Smm} 561213700Smm 562213700Smm 563207753Smm/// \brief Get the compression ratio 564207753Smm/// 565213700Smm/// This has slightly different format than that is used in message.c. 566207753Smmstatic const char * 567207753Smmget_ratio(uint64_t compressed_size, uint64_t uncompressed_size) 568207753Smm{ 569207753Smm if (uncompressed_size == 0) 570207753Smm return "---"; 571207753Smm 572207753Smm const double ratio = (double)(compressed_size) 573207753Smm / (double)(uncompressed_size); 574207753Smm if (ratio > 9.999) 575207753Smm return "---"; 576207753Smm 577213700Smm static char buf[16]; 578207753Smm snprintf(buf, sizeof(buf), "%.3f", ratio); 579207753Smm return buf; 580207753Smm} 581207753Smm 582207753Smm 583207753Smm/// \brief Get a comma-separated list of Check names 584207753Smm/// 585213700Smm/// The check names are translated with gettext except when in robot mode. 586213700Smm/// 587213700Smm/// \param buf Buffer to hold the resulting string 588207753Smm/// \param checks Bit mask of Checks to print 589207753Smm/// \param space_after_comma 590207753Smm/// It's better to not use spaces in table-like listings, 591207753Smm/// but in more verbose formats a space after a comma 592207753Smm/// is good for readability. 593213700Smmstatic void 594213700Smmget_check_names(char buf[CHECKS_STR_SIZE], 595213700Smm uint32_t checks, bool space_after_comma) 596207753Smm{ 597207753Smm assert(checks != 0); 598207753Smm 599207753Smm char *pos = buf; 600213700Smm size_t left = CHECKS_STR_SIZE; 601207753Smm 602207753Smm const char *sep = space_after_comma ? ", " : ","; 603207753Smm bool comma = false; 604207753Smm 605207753Smm for (size_t i = 0; i <= LZMA_CHECK_ID_MAX; ++i) { 606207753Smm if (checks & (UINT32_C(1) << i)) { 607207753Smm my_snprintf(&pos, &left, "%s%s", 608213700Smm comma ? sep : "", 609213700Smm opt_robot ? check_names[i] 610213700Smm : _(check_names[i])); 611207753Smm comma = true; 612207753Smm } 613207753Smm } 614207753Smm 615213700Smm return; 616207753Smm} 617207753Smm 618207753Smm 619207753Smmstatic bool 620213700Smmprint_info_basic(const xz_file_info *xfi, file_pair *pair) 621207753Smm{ 622207753Smm static bool headings_displayed = false; 623207753Smm if (!headings_displayed) { 624207753Smm headings_displayed = true; 625213700Smm // TRANSLATORS: These are column headings. From Strms (Streams) 626207753Smm // to Ratio, the columns are right aligned. Check and Filename 627207753Smm // are left aligned. If you need longer words, it's OK to 628213700Smm // use two lines here. Test with "xz -l foo.xz". 629207753Smm puts(_("Strms Blocks Compressed Uncompressed Ratio " 630207753Smm "Check Filename")); 631207753Smm } 632207753Smm 633213700Smm char checks[CHECKS_STR_SIZE]; 634213700Smm get_check_names(checks, lzma_index_checks(xfi->idx), false); 635207753Smm 636213700Smm const char *cols[7] = { 637213700Smm uint64_to_str(lzma_index_stream_count(xfi->idx), 0), 638213700Smm uint64_to_str(lzma_index_block_count(xfi->idx), 1), 639213700Smm uint64_to_nicestr(lzma_index_file_size(xfi->idx), 640213700Smm NICESTR_B, NICESTR_TIB, false, 2), 641213700Smm uint64_to_nicestr(lzma_index_uncompressed_size(xfi->idx), 642213700Smm NICESTR_B, NICESTR_TIB, false, 3), 643213700Smm get_ratio(lzma_index_file_size(xfi->idx), 644213700Smm lzma_index_uncompressed_size(xfi->idx)), 645213700Smm checks, 646213700Smm pair->src_name, 647213700Smm }; 648213700Smm printf("%*s %*s %*s %*s %*s %-*s %s\n", 649213700Smm tuklib_mbstr_fw(cols[0], 5), cols[0], 650213700Smm tuklib_mbstr_fw(cols[1], 7), cols[1], 651213700Smm tuklib_mbstr_fw(cols[2], 11), cols[2], 652213700Smm tuklib_mbstr_fw(cols[3], 11), cols[3], 653213700Smm tuklib_mbstr_fw(cols[4], 5), cols[4], 654213700Smm tuklib_mbstr_fw(cols[5], 7), cols[5], 655213700Smm cols[6]); 656213700Smm 657213700Smm return false; 658207753Smm} 659207753Smm 660207753Smm 661207753Smmstatic void 662207753Smmprint_adv_helper(uint64_t stream_count, uint64_t block_count, 663207753Smm uint64_t compressed_size, uint64_t uncompressed_size, 664213700Smm uint32_t checks, uint64_t stream_padding) 665207753Smm{ 666213700Smm char checks_str[CHECKS_STR_SIZE]; 667213700Smm get_check_names(checks_str, checks, true); 668213700Smm 669213700Smm printf(_(" Streams: %s\n"), 670207753Smm uint64_to_str(stream_count, 0)); 671213700Smm printf(_(" Blocks: %s\n"), 672207753Smm uint64_to_str(block_count, 0)); 673207753Smm printf(_(" Compressed size: %s\n"), 674207753Smm uint64_to_nicestr(compressed_size, 675207753Smm NICESTR_B, NICESTR_TIB, true, 0)); 676207753Smm printf(_(" Uncompressed size: %s\n"), 677207753Smm uint64_to_nicestr(uncompressed_size, 678207753Smm NICESTR_B, NICESTR_TIB, true, 0)); 679207753Smm printf(_(" Ratio: %s\n"), 680207753Smm get_ratio(compressed_size, uncompressed_size)); 681213700Smm printf(_(" Check: %s\n"), checks_str); 682213700Smm printf(_(" Stream padding: %s\n"), 683213700Smm uint64_to_nicestr(stream_padding, 684213700Smm NICESTR_B, NICESTR_TIB, true, 0)); 685207753Smm return; 686207753Smm} 687207753Smm 688207753Smm 689213700Smmstatic bool 690213700Smmprint_info_adv(xz_file_info *xfi, file_pair *pair) 691207753Smm{ 692207753Smm // Print the overall information. 693213700Smm print_adv_helper(lzma_index_stream_count(xfi->idx), 694213700Smm lzma_index_block_count(xfi->idx), 695213700Smm lzma_index_file_size(xfi->idx), 696213700Smm lzma_index_uncompressed_size(xfi->idx), 697213700Smm lzma_index_checks(xfi->idx), 698213700Smm xfi->stream_padding); 699207753Smm 700213700Smm // Size of the biggest Check. This is used to calculate the width 701213700Smm // of the CheckVal field. The table would get insanely wide if 702213700Smm // we always reserved space for 64-byte Check (128 chars as hex). 703213700Smm uint32_t check_max = 0; 704213700Smm 705213700Smm // Print information about the Streams. 706207753Smm // 707213700Smm // TRANSLATORS: The second line is column headings. All except 708213700Smm // Check are right aligned; Check is left aligned. Test with 709213700Smm // "xz -lv foo.xz". 710213700Smm puts(_(" Streams:\n Stream Blocks" 711213700Smm " CompOffset UncompOffset" 712213700Smm " CompSize UncompSize Ratio" 713213700Smm " Check Padding")); 714207753Smm 715213700Smm lzma_index_iter iter; 716213700Smm lzma_index_iter_init(&iter, xfi->idx); 717207753Smm 718213700Smm while (!lzma_index_iter_next(&iter, LZMA_INDEX_ITER_STREAM)) { 719213700Smm const char *cols1[4] = { 720213700Smm uint64_to_str(iter.stream.number, 0), 721213700Smm uint64_to_str(iter.stream.block_count, 1), 722213700Smm uint64_to_str(iter.stream.compressed_offset, 2), 723213700Smm uint64_to_str(iter.stream.uncompressed_offset, 3), 724213700Smm }; 725213700Smm printf(" %*s %*s %*s %*s ", 726213700Smm tuklib_mbstr_fw(cols1[0], 6), cols1[0], 727213700Smm tuklib_mbstr_fw(cols1[1], 9), cols1[1], 728213700Smm tuklib_mbstr_fw(cols1[2], 15), cols1[2], 729213700Smm tuklib_mbstr_fw(cols1[3], 15), cols1[3]); 730207753Smm 731213700Smm const char *cols2[5] = { 732213700Smm uint64_to_str(iter.stream.compressed_size, 0), 733213700Smm uint64_to_str(iter.stream.uncompressed_size, 1), 734213700Smm get_ratio(iter.stream.compressed_size, 735213700Smm iter.stream.uncompressed_size), 736213700Smm _(check_names[iter.stream.flags->check]), 737213700Smm uint64_to_str(iter.stream.padding, 2), 738213700Smm }; 739213700Smm printf("%*s %*s %*s %-*s %*s\n", 740213700Smm tuklib_mbstr_fw(cols2[0], 15), cols2[0], 741213700Smm tuklib_mbstr_fw(cols2[1], 15), cols2[1], 742213700Smm tuklib_mbstr_fw(cols2[2], 5), cols2[2], 743213700Smm tuklib_mbstr_fw(cols2[3], 10), cols2[3], 744213700Smm tuklib_mbstr_fw(cols2[4], 7), cols2[4]); 745213700Smm 746213700Smm // Update the maximum Check size. 747213700Smm if (lzma_check_size(iter.stream.flags->check) > check_max) 748213700Smm check_max = lzma_check_size(iter.stream.flags->check); 749207753Smm } 750207753Smm 751213700Smm // Cache the verbosity level to a local variable. 752213700Smm const bool detailed = message_verbosity_get() >= V_DEBUG; 753207753Smm 754213700Smm // Information collected from Block Headers 755213700Smm block_header_info bhi; 756213700Smm 757213700Smm // Print information about the Blocks but only if there is 758213700Smm // at least one Block. 759213700Smm if (lzma_index_block_count(xfi->idx) > 0) { 760213700Smm // Calculate the width of the CheckVal field. 761213700Smm const int checkval_width = my_max(8, 2 * check_max); 762213700Smm 763213700Smm // TRANSLATORS: The second line is column headings. All 764213700Smm // except Check are right aligned; Check is left aligned. 765213700Smm printf(_(" Blocks:\n Stream Block" 766213700Smm " CompOffset UncompOffset" 767213700Smm " TotalSize UncompSize Ratio Check")); 768213700Smm 769213700Smm if (detailed) { 770213700Smm // TRANSLATORS: These are additional column headings 771213700Smm // for the most verbose listing mode. CheckVal 772213700Smm // (Check value), Flags, and Filters are left aligned. 773213700Smm // Header (Block Header Size), CompSize, and MemUsage 774213700Smm // are right aligned. %*s is replaced with 0-120 775213700Smm // spaces to make the CheckVal column wide enough. 776213700Smm // Test with "xz -lvv foo.xz". 777213700Smm printf(_(" CheckVal %*s Header Flags " 778213700Smm "CompSize MemUsage Filters"), 779213700Smm checkval_width - 8, ""); 780213700Smm } 781213700Smm 782213700Smm putchar('\n'); 783213700Smm 784213700Smm lzma_index_iter_init(&iter, xfi->idx); 785213700Smm 786213700Smm // Iterate over the Blocks. 787207753Smm while (!lzma_index_iter_next(&iter, LZMA_INDEX_ITER_BLOCK)) { 788213700Smm if (detailed && parse_details(pair, &iter, &bhi, xfi)) 789213700Smm return true; 790213700Smm 791213700Smm const char *cols1[4] = { 792207753Smm uint64_to_str(iter.stream.number, 0), 793213700Smm uint64_to_str( 794213700Smm iter.block.number_in_stream, 1), 795213700Smm uint64_to_str( 796213700Smm iter.block.compressed_file_offset, 2), 797213700Smm uint64_to_str( 798213700Smm iter.block.uncompressed_file_offset, 3) 799213700Smm }; 800213700Smm printf(" %*s %*s %*s %*s ", 801213700Smm tuklib_mbstr_fw(cols1[0], 6), cols1[0], 802213700Smm tuklib_mbstr_fw(cols1[1], 9), cols1[1], 803213700Smm tuklib_mbstr_fw(cols1[2], 15), cols1[2], 804213700Smm tuklib_mbstr_fw(cols1[3], 15), cols1[3]); 805213700Smm 806213700Smm const char *cols2[4] = { 807213700Smm uint64_to_str(iter.block.total_size, 0), 808213700Smm uint64_to_str(iter.block.uncompressed_size, 809213700Smm 1), 810207753Smm get_ratio(iter.block.total_size, 811207753Smm iter.block.uncompressed_size), 812213700Smm _(check_names[iter.stream.flags->check]) 813213700Smm }; 814213700Smm printf("%*s %*s %*s %-*s", 815213700Smm tuklib_mbstr_fw(cols2[0], 15), cols2[0], 816213700Smm tuklib_mbstr_fw(cols2[1], 15), cols2[1], 817213700Smm tuklib_mbstr_fw(cols2[2], 5), cols2[2], 818213700Smm tuklib_mbstr_fw(cols2[3], detailed ? 11 : 1), 819213700Smm cols2[3]); 820207753Smm 821213700Smm if (detailed) { 822213700Smm const lzma_vli compressed_size 823213700Smm = iter.block.unpadded_size 824213700Smm - bhi.header_size 825213700Smm - lzma_check_size( 826213700Smm iter.stream.flags->check); 827207753Smm 828213700Smm const char *cols3[6] = { 829213700Smm check_value, 830213700Smm uint64_to_str(bhi.header_size, 0), 831213700Smm bhi.flags, 832213700Smm uint64_to_str(compressed_size, 1), 833213700Smm uint64_to_str( 834213700Smm round_up_to_mib(bhi.memusage), 835213700Smm 2), 836213700Smm bhi.filter_chain 837213700Smm }; 838213700Smm // Show MiB for memory usage, because it 839213700Smm // is the only size which is not in bytes. 840213700Smm printf("%-*s %*s %-5s %*s %*s MiB %s", 841213700Smm checkval_width, cols3[0], 842213700Smm tuklib_mbstr_fw(cols3[1], 6), cols3[1], 843213700Smm cols3[2], 844213700Smm tuklib_mbstr_fw(cols3[3], 15), 845213700Smm cols3[3], 846213700Smm tuklib_mbstr_fw(cols3[4], 7), cols3[4], 847213700Smm cols3[5]); 848213700Smm } 849213700Smm 850207753Smm putchar('\n'); 851207753Smm } 852207753Smm } 853213700Smm 854213700Smm if (detailed) { 855213700Smm printf(_(" Memory needed: %s MiB\n"), uint64_to_str( 856213700Smm round_up_to_mib(xfi->memusage_max), 0)); 857213700Smm printf(_(" Sizes in headers: %s\n"), 858213700Smm xfi->all_have_sizes ? _("Yes") : _("No")); 859213700Smm } 860213700Smm 861213700Smm return false; 862207753Smm} 863207753Smm 864207753Smm 865213700Smmstatic bool 866213700Smmprint_info_robot(xz_file_info *xfi, file_pair *pair) 867207753Smm{ 868213700Smm char checks[CHECKS_STR_SIZE]; 869213700Smm get_check_names(checks, lzma_index_checks(xfi->idx), false); 870213700Smm 871213700Smm printf("name\t%s\n", pair->src_name); 872213700Smm 873207753Smm printf("file\t%" PRIu64 "\t%" PRIu64 "\t%" PRIu64 "\t%" PRIu64 874213700Smm "\t%s\t%s\t%" PRIu64 "\n", 875213700Smm lzma_index_stream_count(xfi->idx), 876213700Smm lzma_index_block_count(xfi->idx), 877213700Smm lzma_index_file_size(xfi->idx), 878213700Smm lzma_index_uncompressed_size(xfi->idx), 879213700Smm get_ratio(lzma_index_file_size(xfi->idx), 880213700Smm lzma_index_uncompressed_size(xfi->idx)), 881213700Smm checks, 882213700Smm xfi->stream_padding); 883207753Smm 884207753Smm if (message_verbosity_get() >= V_VERBOSE) { 885207753Smm lzma_index_iter iter; 886213700Smm lzma_index_iter_init(&iter, xfi->idx); 887207753Smm 888207753Smm while (!lzma_index_iter_next(&iter, LZMA_INDEX_ITER_STREAM)) 889207753Smm printf("stream\t%" PRIu64 "\t%" PRIu64 "\t%" PRIu64 890213700Smm "\t%" PRIu64 "\t%" PRIu64 "\t%" PRIu64 891213700Smm "\t%s\t%s\t%" PRIu64 "\n", 892207753Smm iter.stream.number, 893213700Smm iter.stream.block_count, 894207753Smm iter.stream.compressed_offset, 895207753Smm iter.stream.uncompressed_offset, 896207753Smm iter.stream.compressed_size, 897207753Smm iter.stream.uncompressed_size, 898207753Smm get_ratio(iter.stream.compressed_size, 899207753Smm iter.stream.uncompressed_size), 900213700Smm check_names[iter.stream.flags->check], 901213700Smm iter.stream.padding); 902207753Smm 903207753Smm lzma_index_iter_rewind(&iter); 904213700Smm block_header_info bhi; 905213700Smm 906207753Smm while (!lzma_index_iter_next(&iter, LZMA_INDEX_ITER_BLOCK)) { 907213700Smm if (message_verbosity_get() >= V_DEBUG 908213700Smm && parse_details( 909213700Smm pair, &iter, &bhi, xfi)) 910213700Smm return true; 911213700Smm 912207753Smm printf("block\t%" PRIu64 "\t%" PRIu64 "\t%" PRIu64 913207753Smm "\t%" PRIu64 "\t%" PRIu64 914207753Smm "\t%" PRIu64 "\t%" PRIu64 "\t%s\t%s", 915207753Smm iter.stream.number, 916207753Smm iter.block.number_in_stream, 917207753Smm iter.block.number_in_file, 918207753Smm iter.block.compressed_file_offset, 919207753Smm iter.block.uncompressed_file_offset, 920207753Smm iter.block.total_size, 921207753Smm iter.block.uncompressed_size, 922207753Smm get_ratio(iter.block.total_size, 923207753Smm iter.block.uncompressed_size), 924207753Smm check_names[iter.stream.flags->check]); 925207753Smm 926213700Smm if (message_verbosity_get() >= V_DEBUG) 927213700Smm printf("\t%s\t%" PRIu32 "\t%s\t%" PRIu64 928213700Smm "\t%" PRIu64 "\t%s", 929213700Smm check_value, 930213700Smm bhi.header_size, 931213700Smm bhi.flags, 932213700Smm bhi.compressed_size, 933213700Smm bhi.memusage, 934213700Smm bhi.filter_chain); 935207753Smm 936207753Smm putchar('\n'); 937207753Smm } 938207753Smm } 939207753Smm 940213700Smm if (message_verbosity_get() >= V_DEBUG) 941213700Smm printf("summary\t%" PRIu64 "\t%s\n", 942213700Smm xfi->memusage_max, 943213700Smm xfi->all_have_sizes ? "yes" : "no"); 944213700Smm 945213700Smm return false; 946207753Smm} 947207753Smm 948207753Smm 949207753Smmstatic void 950213700Smmupdate_totals(const xz_file_info *xfi) 951207753Smm{ 952207753Smm // TODO: Integer overflow checks 953207753Smm ++totals.files; 954213700Smm totals.streams += lzma_index_stream_count(xfi->idx); 955213700Smm totals.blocks += lzma_index_block_count(xfi->idx); 956213700Smm totals.compressed_size += lzma_index_file_size(xfi->idx); 957213700Smm totals.uncompressed_size += lzma_index_uncompressed_size(xfi->idx); 958213700Smm totals.stream_padding += xfi->stream_padding; 959213700Smm totals.checks |= lzma_index_checks(xfi->idx); 960213700Smm 961213700Smm if (totals.memusage_max < xfi->memusage_max) 962213700Smm totals.memusage_max = xfi->memusage_max; 963213700Smm 964213700Smm totals.all_have_sizes &= xfi->all_have_sizes; 965213700Smm 966207753Smm return; 967207753Smm} 968207753Smm 969207753Smm 970207753Smmstatic void 971207753Smmprint_totals_basic(void) 972207753Smm{ 973207753Smm // Print a separator line. 974207753Smm char line[80]; 975207753Smm memset(line, '-', sizeof(line)); 976207753Smm line[sizeof(line) - 1] = '\0'; 977207753Smm puts(line); 978207753Smm 979213700Smm // Get the check names. 980213700Smm char checks[CHECKS_STR_SIZE]; 981213700Smm get_check_names(checks, totals.checks, false); 982213700Smm 983207753Smm // Print the totals except the file count, which needs 984207753Smm // special handling. 985207753Smm printf("%5s %7s %11s %11s %5s %-7s ", 986207753Smm uint64_to_str(totals.streams, 0), 987207753Smm uint64_to_str(totals.blocks, 1), 988207753Smm uint64_to_nicestr(totals.compressed_size, 989207753Smm NICESTR_B, NICESTR_TIB, false, 2), 990207753Smm uint64_to_nicestr(totals.uncompressed_size, 991207753Smm NICESTR_B, NICESTR_TIB, false, 3), 992207753Smm get_ratio(totals.compressed_size, 993207753Smm totals.uncompressed_size), 994213700Smm checks); 995207753Smm 996207753Smm // Since we print totals only when there are at least two files, 997207753Smm // the English message will always use "%s files". But some other 998207753Smm // languages need different forms for different plurals so we 999213700Smm // have to translate this with ngettext(). 1000207753Smm // 1001213700Smm // TRANSLATORS: %s is an integer. Only the plural form of this 1002213700Smm // message is used (e.g. "2 files"). Test with "xz -l foo.xz bar.xz". 1003213700Smm printf(ngettext("%s file\n", "%s files\n", 1004207753Smm totals.files <= ULONG_MAX ? totals.files 1005207753Smm : (totals.files % 1000000) + 1000000), 1006207753Smm uint64_to_str(totals.files, 0)); 1007207753Smm 1008207753Smm return; 1009207753Smm} 1010207753Smm 1011207753Smm 1012207753Smmstatic void 1013207753Smmprint_totals_adv(void) 1014207753Smm{ 1015207753Smm putchar('\n'); 1016207753Smm puts(_("Totals:")); 1017207753Smm printf(_(" Number of files: %s\n"), 1018207753Smm uint64_to_str(totals.files, 0)); 1019207753Smm print_adv_helper(totals.streams, totals.blocks, 1020207753Smm totals.compressed_size, totals.uncompressed_size, 1021213700Smm totals.checks, totals.stream_padding); 1022207753Smm 1023213700Smm if (message_verbosity_get() >= V_DEBUG) { 1024213700Smm printf(_(" Memory needed: %s MiB\n"), uint64_to_str( 1025213700Smm round_up_to_mib(totals.memusage_max), 0)); 1026213700Smm printf(_(" Sizes in headers: %s\n"), 1027213700Smm totals.all_have_sizes ? _("Yes") : _("No")); 1028213700Smm } 1029213700Smm 1030207753Smm return; 1031207753Smm} 1032207753Smm 1033207753Smm 1034207753Smmstatic void 1035207753Smmprint_totals_robot(void) 1036207753Smm{ 1037213700Smm char checks[CHECKS_STR_SIZE]; 1038213700Smm get_check_names(checks, totals.checks, false); 1039213700Smm 1040207753Smm printf("totals\t%" PRIu64 "\t%" PRIu64 "\t%" PRIu64 "\t%" PRIu64 1041213700Smm "\t%s\t%s\t%" PRIu64 "\t%" PRIu64, 1042207753Smm totals.streams, 1043207753Smm totals.blocks, 1044207753Smm totals.compressed_size, 1045207753Smm totals.uncompressed_size, 1046207753Smm get_ratio(totals.compressed_size, 1047207753Smm totals.uncompressed_size), 1048213700Smm checks, 1049213700Smm totals.stream_padding, 1050207753Smm totals.files); 1051207753Smm 1052213700Smm if (message_verbosity_get() >= V_DEBUG) 1053213700Smm printf("\t%" PRIu64 "\t%s", 1054213700Smm totals.memusage_max, 1055213700Smm totals.all_have_sizes ? "yes" : "no"); 1056213700Smm 1057213700Smm putchar('\n'); 1058213700Smm 1059207753Smm return; 1060207753Smm} 1061207753Smm 1062207753Smm 1063207753Smmextern void 1064207753Smmlist_totals(void) 1065207753Smm{ 1066207753Smm if (opt_robot) { 1067207753Smm // Always print totals in --robot mode. It can be convenient 1068207753Smm // in some cases and doesn't complicate usage of the 1069207753Smm // single-file case much. 1070207753Smm print_totals_robot(); 1071207753Smm 1072207753Smm } else if (totals.files > 1) { 1073207753Smm // For non-robot mode, totals are printed only if there 1074207753Smm // is more than one file. 1075207753Smm if (message_verbosity_get() <= V_WARNING) 1076207753Smm print_totals_basic(); 1077207753Smm else 1078207753Smm print_totals_adv(); 1079207753Smm } 1080207753Smm 1081207753Smm return; 1082207753Smm} 1083207753Smm 1084207753Smm 1085207753Smmextern void 1086207753Smmlist_file(const char *filename) 1087207753Smm{ 1088207753Smm if (opt_format != FORMAT_XZ && opt_format != FORMAT_AUTO) 1089207753Smm message_fatal(_("--list works only on .xz files " 1090207753Smm "(--format=xz or --format=auto)")); 1091207753Smm 1092207753Smm message_filename(filename); 1093207753Smm 1094207753Smm if (filename == stdin_filename) { 1095207753Smm message_error(_("--list does not support reading from " 1096207753Smm "standard input")); 1097207753Smm return; 1098207753Smm } 1099207753Smm 1100207753Smm // Unset opt_stdout so that io_open_src() won't accept special files. 1101207753Smm // Set opt_force so that io_open_src() will follow symlinks. 1102207753Smm opt_stdout = false; 1103207753Smm opt_force = true; 1104207753Smm file_pair *pair = io_open_src(filename); 1105207753Smm if (pair == NULL) 1106207753Smm return; 1107207753Smm 1108213700Smm xz_file_info xfi = XZ_FILE_INFO_INIT; 1109213700Smm if (!parse_indexes(&xfi, pair)) { 1110213700Smm bool fail; 1111207753Smm 1112207753Smm // We have three main modes: 1113207753Smm // - --robot, which has submodes if --verbose is specified 1114207753Smm // once or twice 1115207753Smm // - Normal --list without --verbose 1116207753Smm // - --list with one or two --verbose 1117207753Smm if (opt_robot) 1118213700Smm fail = print_info_robot(&xfi, pair); 1119207753Smm else if (message_verbosity_get() <= V_WARNING) 1120213700Smm fail = print_info_basic(&xfi, pair); 1121207753Smm else 1122213700Smm fail = print_info_adv(&xfi, pair); 1123207753Smm 1124213700Smm // Update the totals that are displayed after all 1125213700Smm // the individual files have been listed. Don't count 1126213700Smm // broken files. 1127213700Smm if (!fail) 1128213700Smm update_totals(&xfi); 1129213700Smm 1130213700Smm lzma_index_end(xfi.idx, NULL); 1131207753Smm } 1132207753Smm 1133207753Smm io_close(pair, false); 1134207753Smm return; 1135207753Smm} 1136