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 32292588Sdelphij /// Oldest XZ Utils version that will decompress the file 33292588Sdelphij uint32_t min_version; 34292588Sdelphij 35213700Smm} xz_file_info; 36213700Smm 37292588Sdelphij#define XZ_FILE_INFO_INIT { NULL, 0, 0, true, 50000002 } 38213700Smm 39213700Smm 40213700Smm/// Information about a .xz Block 41213700Smmtypedef struct { 42213700Smm /// Size of the Block Header 43213700Smm uint32_t header_size; 44213700Smm 45213700Smm /// A few of the Block Flags as a string 46213700Smm char flags[3]; 47213700Smm 48213700Smm /// Size of the Compressed Data field in the Block 49213700Smm lzma_vli compressed_size; 50213700Smm 51213700Smm /// Decoder memory usage for this Block 52213700Smm uint64_t memusage; 53213700Smm 54213700Smm /// The filter chain of this Block in human-readable form 55213700Smm char filter_chain[FILTERS_STR_SIZE]; 56213700Smm 57213700Smm} block_header_info; 58213700Smm 59213700Smm 60213700Smm/// Check ID to string mapping 61213700Smmstatic const char check_names[LZMA_CHECK_ID_MAX + 1][12] = { 62213700Smm // TRANSLATORS: Indicates that there is no integrity check. 63213700Smm // This string is used in tables, so the width must not 64213700Smm // exceed ten columns with a fixed-width font. 65213700Smm N_("None"), 66213700Smm "CRC32", 67213700Smm // TRANSLATORS: Indicates that integrity check name is not known, 68213700Smm // but the Check ID is known (here 2). This and other "Unknown-N" 69213700Smm // strings are used in tables, so the width must not exceed ten 70213700Smm // columns with a fixed-width font. It's OK to omit the dash if 71213700Smm // you need space for one extra letter, but don't use spaces. 72213700Smm N_("Unknown-2"), 73213700Smm N_("Unknown-3"), 74213700Smm "CRC64", 75213700Smm N_("Unknown-5"), 76213700Smm N_("Unknown-6"), 77213700Smm N_("Unknown-7"), 78213700Smm N_("Unknown-8"), 79213700Smm N_("Unknown-9"), 80213700Smm "SHA-256", 81213700Smm N_("Unknown-11"), 82213700Smm N_("Unknown-12"), 83213700Smm N_("Unknown-13"), 84213700Smm N_("Unknown-14"), 85213700Smm N_("Unknown-15"), 86213700Smm}; 87213700Smm 88213700Smm/// Buffer size for get_check_names(). This may be a bit ridiculous, 89213700Smm/// but at least it's enough if some language needs many multibyte chars. 90213700Smm#define CHECKS_STR_SIZE 1024 91213700Smm 92213700Smm 93213700Smm/// Value of the Check field as hexadecimal string. 94213700Smm/// This is set by parse_check_value(). 95213700Smmstatic char check_value[2 * LZMA_CHECK_SIZE_MAX + 1]; 96213700Smm 97213700Smm 98207753Smm/// Totals that are displayed if there was more than one file. 99207753Smm/// The "files" counter is also used in print_info_adv() to show 100207753Smm/// the file number. 101207753Smmstatic struct { 102207753Smm uint64_t files; 103207753Smm uint64_t streams; 104207753Smm uint64_t blocks; 105207753Smm uint64_t compressed_size; 106207753Smm uint64_t uncompressed_size; 107213700Smm uint64_t stream_padding; 108213700Smm uint64_t memusage_max; 109207753Smm uint32_t checks; 110292588Sdelphij uint32_t min_version; 111213700Smm bool all_have_sizes; 112292588Sdelphij} totals = { 0, 0, 0, 0, 0, 0, 0, 0, 0, true }; 113207753Smm 114207753Smm 115292588Sdelphij/// Convert XZ Utils version number to a string. 116292588Sdelphijstatic const char * 117292588Sdelphijxz_ver_to_str(uint32_t ver) 118292588Sdelphij{ 119292588Sdelphij static char buf[32]; 120292588Sdelphij 121292588Sdelphij unsigned int major = ver / 10000000U; 122292588Sdelphij ver -= major * 10000000U; 123292588Sdelphij 124292588Sdelphij unsigned int minor = ver / 10000U; 125292588Sdelphij ver -= minor * 10000U; 126292588Sdelphij 127292588Sdelphij unsigned int patch = ver / 10U; 128292588Sdelphij ver -= patch * 10U; 129292588Sdelphij 130292588Sdelphij const char *stability = ver == 0 ? "alpha" : ver == 1 ? "beta" : ""; 131292588Sdelphij 132292588Sdelphij snprintf(buf, sizeof(buf), "%u.%u.%u%s", 133292588Sdelphij major, minor, patch, stability); 134292588Sdelphij return buf; 135292588Sdelphij} 136292588Sdelphij 137292588Sdelphij 138207753Smm/// \brief Parse the Index(es) from the given .xz file 139207753Smm/// 140213700Smm/// \param xfi Pointer to structure where the decoded information 141213700Smm/// is stored. 142207753Smm/// \param pair Input file 143207753Smm/// 144207753Smm/// \return On success, false is returned. On error, true is returned. 145207753Smm/// 146207753Smm// TODO: This function is pretty big. liblzma should have a function that 147207753Smm// takes a callback function to parse the Index(es) from a .xz file to make 148207753Smm// it easy for applications. 149207753Smmstatic bool 150213700Smmparse_indexes(xz_file_info *xfi, file_pair *pair) 151207753Smm{ 152207753Smm if (pair->src_st.st_size <= 0) { 153207753Smm message_error(_("%s: File is empty"), pair->src_name); 154207753Smm return true; 155207753Smm } 156207753Smm 157207753Smm if (pair->src_st.st_size < 2 * LZMA_STREAM_HEADER_SIZE) { 158207753Smm message_error(_("%s: Too small to be a valid .xz file"), 159207753Smm pair->src_name); 160207753Smm return true; 161207753Smm } 162207753Smm 163207753Smm io_buf buf; 164207753Smm lzma_stream_flags header_flags; 165207753Smm lzma_stream_flags footer_flags; 166207753Smm lzma_ret ret; 167207753Smm 168207753Smm // lzma_stream for the Index decoder 169207753Smm lzma_stream strm = LZMA_STREAM_INIT; 170207753Smm 171207753Smm // All Indexes decoded so far 172207753Smm lzma_index *combined_index = NULL; 173207753Smm 174207753Smm // The Index currently being decoded 175207753Smm lzma_index *this_index = NULL; 176207753Smm 177207753Smm // Current position in the file. We parse the file backwards so 178207753Smm // initialize it to point to the end of the file. 179207753Smm off_t pos = pair->src_st.st_size; 180207753Smm 181207753Smm // Each loop iteration decodes one Index. 182207753Smm do { 183207753Smm // Check that there is enough data left to contain at least 184207753Smm // the Stream Header and Stream Footer. This check cannot 185207753Smm // fail in the first pass of this loop. 186207753Smm if (pos < 2 * LZMA_STREAM_HEADER_SIZE) { 187207753Smm message_error("%s: %s", pair->src_name, 188207753Smm message_strm(LZMA_DATA_ERROR)); 189207753Smm goto error; 190207753Smm } 191207753Smm 192207753Smm pos -= LZMA_STREAM_HEADER_SIZE; 193207753Smm lzma_vli stream_padding = 0; 194207753Smm 195207753Smm // Locate the Stream Footer. There may be Stream Padding which 196207753Smm // we must skip when reading backwards. 197207753Smm while (true) { 198207753Smm if (pos < LZMA_STREAM_HEADER_SIZE) { 199207753Smm message_error("%s: %s", pair->src_name, 200207753Smm message_strm( 201207753Smm LZMA_DATA_ERROR)); 202207753Smm goto error; 203207753Smm } 204207753Smm 205207753Smm if (io_pread(pair, &buf, 206207753Smm LZMA_STREAM_HEADER_SIZE, pos)) 207207753Smm goto error; 208207753Smm 209207753Smm // Stream Padding is always a multiple of four bytes. 210207753Smm int i = 2; 211207753Smm if (buf.u32[i] != 0) 212207753Smm break; 213207753Smm 214207753Smm // To avoid calling io_pread() for every four bytes 215207753Smm // of Stream Padding, take advantage that we read 216207753Smm // 12 bytes (LZMA_STREAM_HEADER_SIZE) already and 217207753Smm // check them too before calling io_pread() again. 218207753Smm do { 219207753Smm stream_padding += 4; 220207753Smm pos -= 4; 221207753Smm --i; 222207753Smm } while (i >= 0 && buf.u32[i] == 0); 223207753Smm } 224207753Smm 225207753Smm // Decode the Stream Footer. 226207753Smm ret = lzma_stream_footer_decode(&footer_flags, buf.u8); 227207753Smm if (ret != LZMA_OK) { 228207753Smm message_error("%s: %s", pair->src_name, 229207753Smm message_strm(ret)); 230207753Smm goto error; 231207753Smm } 232207753Smm 233263285Sdelphij // Check that the Stream Footer doesn't specify something 234263285Sdelphij // that we don't support. This can only happen if the xz 235263285Sdelphij // version is older than liblzma and liblzma supports 236263285Sdelphij // something new. 237263285Sdelphij // 238263285Sdelphij // It is enough to check Stream Footer. Stream Header must 239263285Sdelphij // match when it is compared against Stream Footer with 240263285Sdelphij // lzma_stream_flags_compare(). 241263285Sdelphij if (footer_flags.version != 0) { 242263285Sdelphij message_error("%s: %s", pair->src_name, 243263285Sdelphij message_strm(LZMA_OPTIONS_ERROR)); 244263285Sdelphij goto error; 245263285Sdelphij } 246263285Sdelphij 247207753Smm // Check that the size of the Index field looks sane. 248207753Smm lzma_vli index_size = footer_flags.backward_size; 249207753Smm if ((lzma_vli)(pos) < index_size + LZMA_STREAM_HEADER_SIZE) { 250207753Smm message_error("%s: %s", pair->src_name, 251207753Smm message_strm(LZMA_DATA_ERROR)); 252207753Smm goto error; 253207753Smm } 254207753Smm 255207753Smm // Set pos to the beginning of the Index. 256207753Smm pos -= index_size; 257207753Smm 258207753Smm // See how much memory we can use for decoding this Index. 259213700Smm uint64_t memlimit = hardware_memlimit_get(MODE_LIST); 260207753Smm uint64_t memused = 0; 261207753Smm if (combined_index != NULL) { 262207753Smm memused = lzma_index_memused(combined_index); 263207753Smm if (memused > memlimit) 264207753Smm message_bug(); 265207753Smm 266207753Smm memlimit -= memused; 267207753Smm } 268207753Smm 269207753Smm // Decode the Index. 270207753Smm ret = lzma_index_decoder(&strm, &this_index, memlimit); 271207753Smm if (ret != LZMA_OK) { 272207753Smm message_error("%s: %s", pair->src_name, 273207753Smm message_strm(ret)); 274207753Smm goto error; 275207753Smm } 276207753Smm 277207753Smm do { 278207753Smm // Don't give the decoder more input than the 279207753Smm // Index size. 280213700Smm strm.avail_in = my_min(IO_BUFFER_SIZE, index_size); 281207753Smm if (io_pread(pair, &buf, strm.avail_in, pos)) 282207753Smm goto error; 283207753Smm 284207753Smm pos += strm.avail_in; 285207753Smm index_size -= strm.avail_in; 286207753Smm 287207753Smm strm.next_in = buf.u8; 288207753Smm ret = lzma_code(&strm, LZMA_RUN); 289207753Smm 290207753Smm } while (ret == LZMA_OK); 291207753Smm 292207753Smm // If the decoding seems to be successful, check also that 293207753Smm // the Index decoder consumed as much input as indicated 294207753Smm // by the Backward Size field. 295207753Smm if (ret == LZMA_STREAM_END) 296207753Smm if (index_size != 0 || strm.avail_in != 0) 297207753Smm ret = LZMA_DATA_ERROR; 298207753Smm 299207753Smm if (ret != LZMA_STREAM_END) { 300207753Smm // LZMA_BUFFER_ERROR means that the Index decoder 301207753Smm // would have liked more input than what the Index 302207753Smm // size should be according to Stream Footer. 303207753Smm // The message for LZMA_DATA_ERROR makes more 304207753Smm // sense in that case. 305207753Smm if (ret == LZMA_BUF_ERROR) 306207753Smm ret = LZMA_DATA_ERROR; 307207753Smm 308207753Smm message_error("%s: %s", pair->src_name, 309207753Smm message_strm(ret)); 310207753Smm 311207753Smm // If the error was too low memory usage limit, 312207753Smm // show also how much memory would have been needed. 313207753Smm if (ret == LZMA_MEMLIMIT_ERROR) { 314207753Smm uint64_t needed = lzma_memusage(&strm); 315207753Smm if (UINT64_MAX - needed < memused) 316207753Smm needed = UINT64_MAX; 317207753Smm else 318207753Smm needed += memused; 319207753Smm 320207753Smm message_mem_needed(V_ERROR, needed); 321207753Smm } 322207753Smm 323207753Smm goto error; 324207753Smm } 325207753Smm 326207753Smm // Decode the Stream Header and check that its Stream Flags 327207753Smm // match the Stream Footer. 328207753Smm pos -= footer_flags.backward_size + LZMA_STREAM_HEADER_SIZE; 329207753Smm if ((lzma_vli)(pos) < lzma_index_total_size(this_index)) { 330207753Smm message_error("%s: %s", pair->src_name, 331207753Smm message_strm(LZMA_DATA_ERROR)); 332207753Smm goto error; 333207753Smm } 334207753Smm 335207753Smm pos -= lzma_index_total_size(this_index); 336207753Smm if (io_pread(pair, &buf, LZMA_STREAM_HEADER_SIZE, pos)) 337207753Smm goto error; 338207753Smm 339207753Smm ret = lzma_stream_header_decode(&header_flags, buf.u8); 340207753Smm if (ret != LZMA_OK) { 341207753Smm message_error("%s: %s", pair->src_name, 342207753Smm message_strm(ret)); 343207753Smm goto error; 344207753Smm } 345207753Smm 346207753Smm ret = lzma_stream_flags_compare(&header_flags, &footer_flags); 347207753Smm if (ret != LZMA_OK) { 348207753Smm message_error("%s: %s", pair->src_name, 349207753Smm message_strm(ret)); 350207753Smm goto error; 351207753Smm } 352207753Smm 353207753Smm // Store the decoded Stream Flags into this_index. This is 354207753Smm // needed so that we can print which Check is used in each 355207753Smm // Stream. 356207753Smm ret = lzma_index_stream_flags(this_index, &footer_flags); 357207753Smm if (ret != LZMA_OK) 358207753Smm message_bug(); 359207753Smm 360207753Smm // Store also the size of the Stream Padding field. It is 361207753Smm // needed to show the offsets of the Streams correctly. 362207753Smm ret = lzma_index_stream_padding(this_index, stream_padding); 363207753Smm if (ret != LZMA_OK) 364207753Smm message_bug(); 365207753Smm 366207753Smm if (combined_index != NULL) { 367207753Smm // Append the earlier decoded Indexes 368207753Smm // after this_index. 369207753Smm ret = lzma_index_cat( 370207753Smm this_index, combined_index, NULL); 371207753Smm if (ret != LZMA_OK) { 372207753Smm message_error("%s: %s", pair->src_name, 373207753Smm message_strm(ret)); 374207753Smm goto error; 375207753Smm } 376207753Smm } 377207753Smm 378207753Smm combined_index = this_index; 379207753Smm this_index = NULL; 380207753Smm 381213700Smm xfi->stream_padding += stream_padding; 382213700Smm 383207753Smm } while (pos > 0); 384207753Smm 385207753Smm lzma_end(&strm); 386207753Smm 387207753Smm // All OK. Make combined_index available to the caller. 388213700Smm xfi->idx = combined_index; 389207753Smm return false; 390207753Smm 391207753Smmerror: 392207753Smm // Something went wrong, free the allocated memory. 393207753Smm lzma_end(&strm); 394207753Smm lzma_index_end(combined_index, NULL); 395207753Smm lzma_index_end(this_index, NULL); 396207753Smm return true; 397207753Smm} 398207753Smm 399207753Smm 400213700Smm/// \brief Parse the Block Header 401213700Smm/// 402213700Smm/// The result is stored into *bhi. The caller takes care of initializing it. 403213700Smm/// 404213700Smm/// \return False on success, true on error. 405213700Smmstatic bool 406213700Smmparse_block_header(file_pair *pair, const lzma_index_iter *iter, 407213700Smm block_header_info *bhi, xz_file_info *xfi) 408213700Smm{ 409213700Smm#if IO_BUFFER_SIZE < LZMA_BLOCK_HEADER_SIZE_MAX 410213700Smm# error IO_BUFFER_SIZE < LZMA_BLOCK_HEADER_SIZE_MAX 411213700Smm#endif 412213700Smm 413213700Smm // Get the whole Block Header with one read, but don't read past 414213700Smm // the end of the Block (or even its Check field). 415213700Smm const uint32_t size = my_min(iter->block.total_size 416213700Smm - lzma_check_size(iter->stream.flags->check), 417213700Smm LZMA_BLOCK_HEADER_SIZE_MAX); 418213700Smm io_buf buf; 419213700Smm if (io_pread(pair, &buf, size, iter->block.compressed_file_offset)) 420213700Smm return true; 421213700Smm 422213700Smm // Zero would mean Index Indicator and thus not a valid Block. 423213700Smm if (buf.u8[0] == 0) 424213700Smm goto data_error; 425213700Smm 426223935Smm // Initialize the block structure and decode Block Header Size. 427223935Smm lzma_filter filters[LZMA_FILTERS_MAX + 1]; 428213700Smm lzma_block block; 429213700Smm block.version = 0; 430213700Smm block.check = iter->stream.flags->check; 431213700Smm block.filters = filters; 432213700Smm 433213700Smm block.header_size = lzma_block_header_size_decode(buf.u8[0]); 434213700Smm if (block.header_size > size) 435213700Smm goto data_error; 436213700Smm 437213700Smm // Decode the Block Header. 438213700Smm switch (lzma_block_header_decode(&block, NULL, buf.u8)) { 439213700Smm case LZMA_OK: 440213700Smm break; 441213700Smm 442213700Smm case LZMA_OPTIONS_ERROR: 443213700Smm message_error("%s: %s", pair->src_name, 444213700Smm message_strm(LZMA_OPTIONS_ERROR)); 445213700Smm return true; 446213700Smm 447213700Smm case LZMA_DATA_ERROR: 448213700Smm goto data_error; 449213700Smm 450213700Smm default: 451213700Smm message_bug(); 452213700Smm } 453213700Smm 454213700Smm // Check the Block Flags. These must be done before calling 455213700Smm // lzma_block_compressed_size(), because it overwrites 456213700Smm // block.compressed_size. 457213700Smm bhi->flags[0] = block.compressed_size != LZMA_VLI_UNKNOWN 458213700Smm ? 'c' : '-'; 459213700Smm bhi->flags[1] = block.uncompressed_size != LZMA_VLI_UNKNOWN 460213700Smm ? 'u' : '-'; 461213700Smm bhi->flags[2] = '\0'; 462213700Smm 463213700Smm // Collect information if all Blocks have both Compressed Size 464213700Smm // and Uncompressed Size fields. They can be useful e.g. for 465213700Smm // multi-threaded decompression so it can be useful to know it. 466213700Smm xfi->all_have_sizes &= block.compressed_size != LZMA_VLI_UNKNOWN 467213700Smm && block.uncompressed_size != LZMA_VLI_UNKNOWN; 468213700Smm 469213700Smm // Validate or set block.compressed_size. 470213700Smm switch (lzma_block_compressed_size(&block, 471213700Smm iter->block.unpadded_size)) { 472213700Smm case LZMA_OK: 473263285Sdelphij // Validate also block.uncompressed_size if it is present. 474263285Sdelphij // If it isn't present, there's no need to set it since 475263285Sdelphij // we aren't going to actually decompress the Block; if 476263285Sdelphij // we were decompressing, then we should set it so that 477263285Sdelphij // the Block decoder could validate the Uncompressed Size 478263285Sdelphij // that was stored in the Index. 479263285Sdelphij if (block.uncompressed_size == LZMA_VLI_UNKNOWN 480263285Sdelphij || block.uncompressed_size 481263285Sdelphij == iter->block.uncompressed_size) 482263285Sdelphij break; 483213700Smm 484263285Sdelphij // If the above fails, the file is corrupt so 485263285Sdelphij // LZMA_DATA_ERROR is a good error code. 486263285Sdelphij 487213700Smm case LZMA_DATA_ERROR: 488223935Smm // Free the memory allocated by lzma_block_header_decode(). 489223935Smm for (size_t i = 0; filters[i].id != LZMA_VLI_UNKNOWN; ++i) 490223935Smm free(filters[i].options); 491223935Smm 492213700Smm goto data_error; 493213700Smm 494213700Smm default: 495213700Smm message_bug(); 496213700Smm } 497213700Smm 498213700Smm // Copy the known sizes. 499213700Smm bhi->header_size = block.header_size; 500213700Smm bhi->compressed_size = block.compressed_size; 501213700Smm 502213700Smm // Calculate the decoder memory usage and update the maximum 503213700Smm // memory usage of this Block. 504213700Smm bhi->memusage = lzma_raw_decoder_memusage(filters); 505213700Smm if (xfi->memusage_max < bhi->memusage) 506213700Smm xfi->memusage_max = bhi->memusage; 507213700Smm 508292588Sdelphij // Determine the minimum XZ Utils version that supports this Block. 509292588Sdelphij // 510292588Sdelphij // Currently the only thing that 5.0.0 doesn't support is empty 511292588Sdelphij // LZMA2 Block. This decoder bug was fixed in 5.0.2. 512292588Sdelphij { 513292588Sdelphij size_t i = 0; 514292588Sdelphij while (filters[i + 1].id != LZMA_VLI_UNKNOWN) 515292588Sdelphij ++i; 516292588Sdelphij 517292588Sdelphij if (filters[i].id == LZMA_FILTER_LZMA2 518292588Sdelphij && iter->block.uncompressed_size == 0 519292588Sdelphij && xfi->min_version < 50000022U) 520292588Sdelphij xfi->min_version = 50000022U; 521292588Sdelphij } 522292588Sdelphij 523213700Smm // Convert the filter chain to human readable form. 524213700Smm message_filters_to_str(bhi->filter_chain, filters, false); 525213700Smm 526213700Smm // Free the memory allocated by lzma_block_header_decode(). 527213700Smm for (size_t i = 0; filters[i].id != LZMA_VLI_UNKNOWN; ++i) 528213700Smm free(filters[i].options); 529213700Smm 530213700Smm return false; 531213700Smm 532213700Smmdata_error: 533213700Smm // Show the error message. 534213700Smm message_error("%s: %s", pair->src_name, 535213700Smm message_strm(LZMA_DATA_ERROR)); 536213700Smm return true; 537213700Smm} 538213700Smm 539213700Smm 540213700Smm/// \brief Parse the Check field and put it into check_value[] 541213700Smm/// 542213700Smm/// \return False on success, true on error. 543213700Smmstatic bool 544213700Smmparse_check_value(file_pair *pair, const lzma_index_iter *iter) 545213700Smm{ 546213700Smm // Don't read anything from the file if there is no integrity Check. 547213700Smm if (iter->stream.flags->check == LZMA_CHECK_NONE) { 548213700Smm snprintf(check_value, sizeof(check_value), "---"); 549213700Smm return false; 550213700Smm } 551213700Smm 552213700Smm // Locate and read the Check field. 553213700Smm const uint32_t size = lzma_check_size(iter->stream.flags->check); 554213700Smm const off_t offset = iter->block.compressed_file_offset 555213700Smm + iter->block.total_size - size; 556213700Smm io_buf buf; 557213700Smm if (io_pread(pair, &buf, size, offset)) 558213700Smm return true; 559213700Smm 560213700Smm // CRC32 and CRC64 are in little endian. Guess that all the future 561213700Smm // 32-bit and 64-bit Check values are little endian too. It shouldn't 562213700Smm // be a too big problem if this guess is wrong. 563213700Smm if (size == 4) 564213700Smm snprintf(check_value, sizeof(check_value), 565213700Smm "%08" PRIx32, conv32le(buf.u32[0])); 566213700Smm else if (size == 8) 567213700Smm snprintf(check_value, sizeof(check_value), 568213700Smm "%016" PRIx64, conv64le(buf.u64[0])); 569213700Smm else 570213700Smm for (size_t i = 0; i < size; ++i) 571213700Smm snprintf(check_value + i * 2, 3, "%02x", buf.u8[i]); 572213700Smm 573213700Smm return false; 574213700Smm} 575213700Smm 576213700Smm 577213700Smm/// \brief Parse detailed information about a Block 578213700Smm/// 579213700Smm/// Since this requires seek(s), listing information about all Blocks can 580213700Smm/// be slow. 581213700Smm/// 582213700Smm/// \param pair Input file 583213700Smm/// \param iter Location of the Block whose Check value should 584213700Smm/// be printed. 585213700Smm/// \param bhi Pointer to structure where to store the information 586213700Smm/// about the Block Header field. 587213700Smm/// 588213700Smm/// \return False on success, true on error. If an error occurs, 589213700Smm/// the error message is printed too so the caller doesn't 590213700Smm/// need to worry about that. 591213700Smmstatic bool 592213700Smmparse_details(file_pair *pair, const lzma_index_iter *iter, 593213700Smm block_header_info *bhi, xz_file_info *xfi) 594213700Smm{ 595213700Smm if (parse_block_header(pair, iter, bhi, xfi)) 596213700Smm return true; 597213700Smm 598213700Smm if (parse_check_value(pair, iter)) 599213700Smm return true; 600213700Smm 601213700Smm return false; 602213700Smm} 603213700Smm 604213700Smm 605207753Smm/// \brief Get the compression ratio 606207753Smm/// 607213700Smm/// This has slightly different format than that is used in message.c. 608207753Smmstatic const char * 609207753Smmget_ratio(uint64_t compressed_size, uint64_t uncompressed_size) 610207753Smm{ 611207753Smm if (uncompressed_size == 0) 612207753Smm return "---"; 613207753Smm 614207753Smm const double ratio = (double)(compressed_size) 615207753Smm / (double)(uncompressed_size); 616207753Smm if (ratio > 9.999) 617207753Smm return "---"; 618207753Smm 619213700Smm static char buf[16]; 620207753Smm snprintf(buf, sizeof(buf), "%.3f", ratio); 621207753Smm return buf; 622207753Smm} 623207753Smm 624207753Smm 625207753Smm/// \brief Get a comma-separated list of Check names 626207753Smm/// 627213700Smm/// The check names are translated with gettext except when in robot mode. 628213700Smm/// 629213700Smm/// \param buf Buffer to hold the resulting string 630207753Smm/// \param checks Bit mask of Checks to print 631207753Smm/// \param space_after_comma 632207753Smm/// It's better to not use spaces in table-like listings, 633207753Smm/// but in more verbose formats a space after a comma 634207753Smm/// is good for readability. 635213700Smmstatic void 636213700Smmget_check_names(char buf[CHECKS_STR_SIZE], 637213700Smm uint32_t checks, bool space_after_comma) 638207753Smm{ 639207753Smm assert(checks != 0); 640207753Smm 641207753Smm char *pos = buf; 642213700Smm size_t left = CHECKS_STR_SIZE; 643207753Smm 644207753Smm const char *sep = space_after_comma ? ", " : ","; 645207753Smm bool comma = false; 646207753Smm 647207753Smm for (size_t i = 0; i <= LZMA_CHECK_ID_MAX; ++i) { 648207753Smm if (checks & (UINT32_C(1) << i)) { 649207753Smm my_snprintf(&pos, &left, "%s%s", 650213700Smm comma ? sep : "", 651213700Smm opt_robot ? check_names[i] 652213700Smm : _(check_names[i])); 653207753Smm comma = true; 654207753Smm } 655207753Smm } 656207753Smm 657213700Smm return; 658207753Smm} 659207753Smm 660207753Smm 661207753Smmstatic bool 662213700Smmprint_info_basic(const xz_file_info *xfi, file_pair *pair) 663207753Smm{ 664207753Smm static bool headings_displayed = false; 665207753Smm if (!headings_displayed) { 666207753Smm headings_displayed = true; 667213700Smm // TRANSLATORS: These are column headings. From Strms (Streams) 668207753Smm // to Ratio, the columns are right aligned. Check and Filename 669207753Smm // are left aligned. If you need longer words, it's OK to 670213700Smm // use two lines here. Test with "xz -l foo.xz". 671207753Smm puts(_("Strms Blocks Compressed Uncompressed Ratio " 672207753Smm "Check Filename")); 673207753Smm } 674207753Smm 675213700Smm char checks[CHECKS_STR_SIZE]; 676213700Smm get_check_names(checks, lzma_index_checks(xfi->idx), false); 677207753Smm 678213700Smm const char *cols[7] = { 679213700Smm uint64_to_str(lzma_index_stream_count(xfi->idx), 0), 680213700Smm uint64_to_str(lzma_index_block_count(xfi->idx), 1), 681213700Smm uint64_to_nicestr(lzma_index_file_size(xfi->idx), 682213700Smm NICESTR_B, NICESTR_TIB, false, 2), 683213700Smm uint64_to_nicestr(lzma_index_uncompressed_size(xfi->idx), 684213700Smm NICESTR_B, NICESTR_TIB, false, 3), 685213700Smm get_ratio(lzma_index_file_size(xfi->idx), 686213700Smm lzma_index_uncompressed_size(xfi->idx)), 687213700Smm checks, 688213700Smm pair->src_name, 689213700Smm }; 690213700Smm printf("%*s %*s %*s %*s %*s %-*s %s\n", 691213700Smm tuklib_mbstr_fw(cols[0], 5), cols[0], 692213700Smm tuklib_mbstr_fw(cols[1], 7), cols[1], 693213700Smm tuklib_mbstr_fw(cols[2], 11), cols[2], 694213700Smm tuklib_mbstr_fw(cols[3], 11), cols[3], 695213700Smm tuklib_mbstr_fw(cols[4], 5), cols[4], 696213700Smm tuklib_mbstr_fw(cols[5], 7), cols[5], 697213700Smm cols[6]); 698213700Smm 699213700Smm return false; 700207753Smm} 701207753Smm 702207753Smm 703207753Smmstatic void 704207753Smmprint_adv_helper(uint64_t stream_count, uint64_t block_count, 705207753Smm uint64_t compressed_size, uint64_t uncompressed_size, 706213700Smm uint32_t checks, uint64_t stream_padding) 707207753Smm{ 708213700Smm char checks_str[CHECKS_STR_SIZE]; 709213700Smm get_check_names(checks_str, checks, true); 710213700Smm 711213700Smm printf(_(" Streams: %s\n"), 712207753Smm uint64_to_str(stream_count, 0)); 713213700Smm printf(_(" Blocks: %s\n"), 714207753Smm uint64_to_str(block_count, 0)); 715207753Smm printf(_(" Compressed size: %s\n"), 716207753Smm uint64_to_nicestr(compressed_size, 717207753Smm NICESTR_B, NICESTR_TIB, true, 0)); 718207753Smm printf(_(" Uncompressed size: %s\n"), 719207753Smm uint64_to_nicestr(uncompressed_size, 720207753Smm NICESTR_B, NICESTR_TIB, true, 0)); 721207753Smm printf(_(" Ratio: %s\n"), 722207753Smm get_ratio(compressed_size, uncompressed_size)); 723213700Smm printf(_(" Check: %s\n"), checks_str); 724213700Smm printf(_(" Stream padding: %s\n"), 725213700Smm uint64_to_nicestr(stream_padding, 726213700Smm NICESTR_B, NICESTR_TIB, true, 0)); 727207753Smm return; 728207753Smm} 729207753Smm 730207753Smm 731213700Smmstatic bool 732213700Smmprint_info_adv(xz_file_info *xfi, file_pair *pair) 733207753Smm{ 734207753Smm // Print the overall information. 735213700Smm print_adv_helper(lzma_index_stream_count(xfi->idx), 736213700Smm lzma_index_block_count(xfi->idx), 737213700Smm lzma_index_file_size(xfi->idx), 738213700Smm lzma_index_uncompressed_size(xfi->idx), 739213700Smm lzma_index_checks(xfi->idx), 740213700Smm xfi->stream_padding); 741207753Smm 742213700Smm // Size of the biggest Check. This is used to calculate the width 743213700Smm // of the CheckVal field. The table would get insanely wide if 744213700Smm // we always reserved space for 64-byte Check (128 chars as hex). 745213700Smm uint32_t check_max = 0; 746213700Smm 747213700Smm // Print information about the Streams. 748207753Smm // 749213700Smm // TRANSLATORS: The second line is column headings. All except 750213700Smm // Check are right aligned; Check is left aligned. Test with 751213700Smm // "xz -lv foo.xz". 752213700Smm puts(_(" Streams:\n Stream Blocks" 753213700Smm " CompOffset UncompOffset" 754213700Smm " CompSize UncompSize Ratio" 755213700Smm " Check Padding")); 756207753Smm 757213700Smm lzma_index_iter iter; 758213700Smm lzma_index_iter_init(&iter, xfi->idx); 759207753Smm 760213700Smm while (!lzma_index_iter_next(&iter, LZMA_INDEX_ITER_STREAM)) { 761213700Smm const char *cols1[4] = { 762213700Smm uint64_to_str(iter.stream.number, 0), 763213700Smm uint64_to_str(iter.stream.block_count, 1), 764213700Smm uint64_to_str(iter.stream.compressed_offset, 2), 765213700Smm uint64_to_str(iter.stream.uncompressed_offset, 3), 766213700Smm }; 767213700Smm printf(" %*s %*s %*s %*s ", 768213700Smm tuklib_mbstr_fw(cols1[0], 6), cols1[0], 769213700Smm tuklib_mbstr_fw(cols1[1], 9), cols1[1], 770213700Smm tuklib_mbstr_fw(cols1[2], 15), cols1[2], 771213700Smm tuklib_mbstr_fw(cols1[3], 15), cols1[3]); 772207753Smm 773213700Smm const char *cols2[5] = { 774213700Smm uint64_to_str(iter.stream.compressed_size, 0), 775213700Smm uint64_to_str(iter.stream.uncompressed_size, 1), 776213700Smm get_ratio(iter.stream.compressed_size, 777213700Smm iter.stream.uncompressed_size), 778213700Smm _(check_names[iter.stream.flags->check]), 779213700Smm uint64_to_str(iter.stream.padding, 2), 780213700Smm }; 781213700Smm printf("%*s %*s %*s %-*s %*s\n", 782213700Smm tuklib_mbstr_fw(cols2[0], 15), cols2[0], 783213700Smm tuklib_mbstr_fw(cols2[1], 15), cols2[1], 784213700Smm tuklib_mbstr_fw(cols2[2], 5), cols2[2], 785213700Smm tuklib_mbstr_fw(cols2[3], 10), cols2[3], 786213700Smm tuklib_mbstr_fw(cols2[4], 7), cols2[4]); 787213700Smm 788213700Smm // Update the maximum Check size. 789213700Smm if (lzma_check_size(iter.stream.flags->check) > check_max) 790213700Smm check_max = lzma_check_size(iter.stream.flags->check); 791207753Smm } 792207753Smm 793213700Smm // Cache the verbosity level to a local variable. 794213700Smm const bool detailed = message_verbosity_get() >= V_DEBUG; 795207753Smm 796213700Smm // Information collected from Block Headers 797213700Smm block_header_info bhi; 798213700Smm 799213700Smm // Print information about the Blocks but only if there is 800213700Smm // at least one Block. 801213700Smm if (lzma_index_block_count(xfi->idx) > 0) { 802213700Smm // Calculate the width of the CheckVal field. 803213700Smm const int checkval_width = my_max(8, 2 * check_max); 804213700Smm 805213700Smm // TRANSLATORS: The second line is column headings. All 806213700Smm // except Check are right aligned; Check is left aligned. 807213700Smm printf(_(" Blocks:\n Stream Block" 808213700Smm " CompOffset UncompOffset" 809213700Smm " TotalSize UncompSize Ratio Check")); 810213700Smm 811213700Smm if (detailed) { 812213700Smm // TRANSLATORS: These are additional column headings 813213700Smm // for the most verbose listing mode. CheckVal 814213700Smm // (Check value), Flags, and Filters are left aligned. 815213700Smm // Header (Block Header Size), CompSize, and MemUsage 816213700Smm // are right aligned. %*s is replaced with 0-120 817213700Smm // spaces to make the CheckVal column wide enough. 818213700Smm // Test with "xz -lvv foo.xz". 819213700Smm printf(_(" CheckVal %*s Header Flags " 820213700Smm "CompSize MemUsage Filters"), 821213700Smm checkval_width - 8, ""); 822213700Smm } 823213700Smm 824213700Smm putchar('\n'); 825213700Smm 826213700Smm lzma_index_iter_init(&iter, xfi->idx); 827213700Smm 828213700Smm // Iterate over the Blocks. 829207753Smm while (!lzma_index_iter_next(&iter, LZMA_INDEX_ITER_BLOCK)) { 830213700Smm if (detailed && parse_details(pair, &iter, &bhi, xfi)) 831213700Smm return true; 832213700Smm 833213700Smm const char *cols1[4] = { 834207753Smm uint64_to_str(iter.stream.number, 0), 835213700Smm uint64_to_str( 836213700Smm iter.block.number_in_stream, 1), 837213700Smm uint64_to_str( 838213700Smm iter.block.compressed_file_offset, 2), 839213700Smm uint64_to_str( 840213700Smm iter.block.uncompressed_file_offset, 3) 841213700Smm }; 842213700Smm printf(" %*s %*s %*s %*s ", 843213700Smm tuklib_mbstr_fw(cols1[0], 6), cols1[0], 844213700Smm tuklib_mbstr_fw(cols1[1], 9), cols1[1], 845213700Smm tuklib_mbstr_fw(cols1[2], 15), cols1[2], 846213700Smm tuklib_mbstr_fw(cols1[3], 15), cols1[3]); 847213700Smm 848213700Smm const char *cols2[4] = { 849213700Smm uint64_to_str(iter.block.total_size, 0), 850213700Smm uint64_to_str(iter.block.uncompressed_size, 851213700Smm 1), 852207753Smm get_ratio(iter.block.total_size, 853207753Smm iter.block.uncompressed_size), 854213700Smm _(check_names[iter.stream.flags->check]) 855213700Smm }; 856213700Smm printf("%*s %*s %*s %-*s", 857213700Smm tuklib_mbstr_fw(cols2[0], 15), cols2[0], 858213700Smm tuklib_mbstr_fw(cols2[1], 15), cols2[1], 859213700Smm tuklib_mbstr_fw(cols2[2], 5), cols2[2], 860213700Smm tuklib_mbstr_fw(cols2[3], detailed ? 11 : 1), 861213700Smm cols2[3]); 862207753Smm 863213700Smm if (detailed) { 864213700Smm const lzma_vli compressed_size 865213700Smm = iter.block.unpadded_size 866213700Smm - bhi.header_size 867213700Smm - lzma_check_size( 868213700Smm iter.stream.flags->check); 869207753Smm 870213700Smm const char *cols3[6] = { 871213700Smm check_value, 872213700Smm uint64_to_str(bhi.header_size, 0), 873213700Smm bhi.flags, 874213700Smm uint64_to_str(compressed_size, 1), 875213700Smm uint64_to_str( 876213700Smm round_up_to_mib(bhi.memusage), 877213700Smm 2), 878213700Smm bhi.filter_chain 879213700Smm }; 880213700Smm // Show MiB for memory usage, because it 881213700Smm // is the only size which is not in bytes. 882213700Smm printf("%-*s %*s %-5s %*s %*s MiB %s", 883213700Smm checkval_width, cols3[0], 884213700Smm tuklib_mbstr_fw(cols3[1], 6), cols3[1], 885213700Smm cols3[2], 886213700Smm tuklib_mbstr_fw(cols3[3], 15), 887213700Smm cols3[3], 888213700Smm tuklib_mbstr_fw(cols3[4], 7), cols3[4], 889213700Smm cols3[5]); 890213700Smm } 891213700Smm 892207753Smm putchar('\n'); 893207753Smm } 894207753Smm } 895213700Smm 896213700Smm if (detailed) { 897213700Smm printf(_(" Memory needed: %s MiB\n"), uint64_to_str( 898213700Smm round_up_to_mib(xfi->memusage_max), 0)); 899213700Smm printf(_(" Sizes in headers: %s\n"), 900213700Smm xfi->all_have_sizes ? _("Yes") : _("No")); 901292588Sdelphij printf(_(" Minimum XZ Utils version: %s\n"), 902292588Sdelphij xz_ver_to_str(xfi->min_version)); 903213700Smm } 904213700Smm 905213700Smm return false; 906207753Smm} 907207753Smm 908207753Smm 909213700Smmstatic bool 910213700Smmprint_info_robot(xz_file_info *xfi, file_pair *pair) 911207753Smm{ 912213700Smm char checks[CHECKS_STR_SIZE]; 913213700Smm get_check_names(checks, lzma_index_checks(xfi->idx), false); 914213700Smm 915213700Smm printf("name\t%s\n", pair->src_name); 916213700Smm 917207753Smm printf("file\t%" PRIu64 "\t%" PRIu64 "\t%" PRIu64 "\t%" PRIu64 918213700Smm "\t%s\t%s\t%" PRIu64 "\n", 919213700Smm lzma_index_stream_count(xfi->idx), 920213700Smm lzma_index_block_count(xfi->idx), 921213700Smm lzma_index_file_size(xfi->idx), 922213700Smm lzma_index_uncompressed_size(xfi->idx), 923213700Smm get_ratio(lzma_index_file_size(xfi->idx), 924213700Smm lzma_index_uncompressed_size(xfi->idx)), 925213700Smm checks, 926213700Smm xfi->stream_padding); 927207753Smm 928207753Smm if (message_verbosity_get() >= V_VERBOSE) { 929207753Smm lzma_index_iter iter; 930213700Smm lzma_index_iter_init(&iter, xfi->idx); 931207753Smm 932207753Smm while (!lzma_index_iter_next(&iter, LZMA_INDEX_ITER_STREAM)) 933207753Smm printf("stream\t%" PRIu64 "\t%" PRIu64 "\t%" PRIu64 934213700Smm "\t%" PRIu64 "\t%" PRIu64 "\t%" PRIu64 935213700Smm "\t%s\t%s\t%" PRIu64 "\n", 936207753Smm iter.stream.number, 937213700Smm iter.stream.block_count, 938207753Smm iter.stream.compressed_offset, 939207753Smm iter.stream.uncompressed_offset, 940207753Smm iter.stream.compressed_size, 941207753Smm iter.stream.uncompressed_size, 942207753Smm get_ratio(iter.stream.compressed_size, 943207753Smm iter.stream.uncompressed_size), 944213700Smm check_names[iter.stream.flags->check], 945213700Smm iter.stream.padding); 946207753Smm 947207753Smm lzma_index_iter_rewind(&iter); 948213700Smm block_header_info bhi; 949213700Smm 950207753Smm while (!lzma_index_iter_next(&iter, LZMA_INDEX_ITER_BLOCK)) { 951213700Smm if (message_verbosity_get() >= V_DEBUG 952213700Smm && parse_details( 953213700Smm pair, &iter, &bhi, xfi)) 954213700Smm return true; 955213700Smm 956207753Smm printf("block\t%" PRIu64 "\t%" PRIu64 "\t%" PRIu64 957207753Smm "\t%" PRIu64 "\t%" PRIu64 958207753Smm "\t%" PRIu64 "\t%" PRIu64 "\t%s\t%s", 959207753Smm iter.stream.number, 960207753Smm iter.block.number_in_stream, 961207753Smm iter.block.number_in_file, 962207753Smm iter.block.compressed_file_offset, 963207753Smm iter.block.uncompressed_file_offset, 964207753Smm iter.block.total_size, 965207753Smm iter.block.uncompressed_size, 966207753Smm get_ratio(iter.block.total_size, 967207753Smm iter.block.uncompressed_size), 968207753Smm check_names[iter.stream.flags->check]); 969207753Smm 970213700Smm if (message_verbosity_get() >= V_DEBUG) 971213700Smm printf("\t%s\t%" PRIu32 "\t%s\t%" PRIu64 972213700Smm "\t%" PRIu64 "\t%s", 973213700Smm check_value, 974213700Smm bhi.header_size, 975213700Smm bhi.flags, 976213700Smm bhi.compressed_size, 977213700Smm bhi.memusage, 978213700Smm bhi.filter_chain); 979207753Smm 980207753Smm putchar('\n'); 981207753Smm } 982207753Smm } 983207753Smm 984213700Smm if (message_verbosity_get() >= V_DEBUG) 985292588Sdelphij printf("summary\t%" PRIu64 "\t%s\t%" PRIu32 "\n", 986213700Smm xfi->memusage_max, 987292588Sdelphij xfi->all_have_sizes ? "yes" : "no", 988292588Sdelphij xfi->min_version); 989213700Smm 990213700Smm return false; 991207753Smm} 992207753Smm 993207753Smm 994207753Smmstatic void 995213700Smmupdate_totals(const xz_file_info *xfi) 996207753Smm{ 997207753Smm // TODO: Integer overflow checks 998207753Smm ++totals.files; 999213700Smm totals.streams += lzma_index_stream_count(xfi->idx); 1000213700Smm totals.blocks += lzma_index_block_count(xfi->idx); 1001213700Smm totals.compressed_size += lzma_index_file_size(xfi->idx); 1002213700Smm totals.uncompressed_size += lzma_index_uncompressed_size(xfi->idx); 1003213700Smm totals.stream_padding += xfi->stream_padding; 1004213700Smm totals.checks |= lzma_index_checks(xfi->idx); 1005213700Smm 1006213700Smm if (totals.memusage_max < xfi->memusage_max) 1007213700Smm totals.memusage_max = xfi->memusage_max; 1008213700Smm 1009292588Sdelphij if (totals.min_version < xfi->min_version) 1010292588Sdelphij totals.min_version = xfi->min_version; 1011292588Sdelphij 1012213700Smm totals.all_have_sizes &= xfi->all_have_sizes; 1013213700Smm 1014207753Smm return; 1015207753Smm} 1016207753Smm 1017207753Smm 1018207753Smmstatic void 1019207753Smmprint_totals_basic(void) 1020207753Smm{ 1021207753Smm // Print a separator line. 1022207753Smm char line[80]; 1023207753Smm memset(line, '-', sizeof(line)); 1024207753Smm line[sizeof(line) - 1] = '\0'; 1025207753Smm puts(line); 1026207753Smm 1027213700Smm // Get the check names. 1028213700Smm char checks[CHECKS_STR_SIZE]; 1029213700Smm get_check_names(checks, totals.checks, false); 1030213700Smm 1031207753Smm // Print the totals except the file count, which needs 1032207753Smm // special handling. 1033207753Smm printf("%5s %7s %11s %11s %5s %-7s ", 1034207753Smm uint64_to_str(totals.streams, 0), 1035207753Smm uint64_to_str(totals.blocks, 1), 1036207753Smm uint64_to_nicestr(totals.compressed_size, 1037207753Smm NICESTR_B, NICESTR_TIB, false, 2), 1038207753Smm uint64_to_nicestr(totals.uncompressed_size, 1039207753Smm NICESTR_B, NICESTR_TIB, false, 3), 1040207753Smm get_ratio(totals.compressed_size, 1041207753Smm totals.uncompressed_size), 1042213700Smm checks); 1043207753Smm 1044207753Smm // Since we print totals only when there are at least two files, 1045207753Smm // the English message will always use "%s files". But some other 1046207753Smm // languages need different forms for different plurals so we 1047213700Smm // have to translate this with ngettext(). 1048207753Smm // 1049213700Smm // TRANSLATORS: %s is an integer. Only the plural form of this 1050213700Smm // message is used (e.g. "2 files"). Test with "xz -l foo.xz bar.xz". 1051213700Smm printf(ngettext("%s file\n", "%s files\n", 1052207753Smm totals.files <= ULONG_MAX ? totals.files 1053207753Smm : (totals.files % 1000000) + 1000000), 1054207753Smm uint64_to_str(totals.files, 0)); 1055207753Smm 1056207753Smm return; 1057207753Smm} 1058207753Smm 1059207753Smm 1060207753Smmstatic void 1061207753Smmprint_totals_adv(void) 1062207753Smm{ 1063207753Smm putchar('\n'); 1064207753Smm puts(_("Totals:")); 1065207753Smm printf(_(" Number of files: %s\n"), 1066207753Smm uint64_to_str(totals.files, 0)); 1067207753Smm print_adv_helper(totals.streams, totals.blocks, 1068207753Smm totals.compressed_size, totals.uncompressed_size, 1069213700Smm totals.checks, totals.stream_padding); 1070207753Smm 1071213700Smm if (message_verbosity_get() >= V_DEBUG) { 1072213700Smm printf(_(" Memory needed: %s MiB\n"), uint64_to_str( 1073213700Smm round_up_to_mib(totals.memusage_max), 0)); 1074213700Smm printf(_(" Sizes in headers: %s\n"), 1075213700Smm totals.all_have_sizes ? _("Yes") : _("No")); 1076292588Sdelphij printf(_(" Minimum XZ Utils version: %s\n"), 1077292588Sdelphij xz_ver_to_str(totals.min_version)); 1078213700Smm } 1079213700Smm 1080207753Smm return; 1081207753Smm} 1082207753Smm 1083207753Smm 1084207753Smmstatic void 1085207753Smmprint_totals_robot(void) 1086207753Smm{ 1087213700Smm char checks[CHECKS_STR_SIZE]; 1088213700Smm get_check_names(checks, totals.checks, false); 1089213700Smm 1090207753Smm printf("totals\t%" PRIu64 "\t%" PRIu64 "\t%" PRIu64 "\t%" PRIu64 1091213700Smm "\t%s\t%s\t%" PRIu64 "\t%" PRIu64, 1092207753Smm totals.streams, 1093207753Smm totals.blocks, 1094207753Smm totals.compressed_size, 1095207753Smm totals.uncompressed_size, 1096207753Smm get_ratio(totals.compressed_size, 1097207753Smm totals.uncompressed_size), 1098213700Smm checks, 1099213700Smm totals.stream_padding, 1100207753Smm totals.files); 1101207753Smm 1102213700Smm if (message_verbosity_get() >= V_DEBUG) 1103292588Sdelphij printf("\t%" PRIu64 "\t%s\t%" PRIu32, 1104213700Smm totals.memusage_max, 1105292588Sdelphij totals.all_have_sizes ? "yes" : "no", 1106292588Sdelphij totals.min_version); 1107213700Smm 1108213700Smm putchar('\n'); 1109213700Smm 1110207753Smm return; 1111207753Smm} 1112207753Smm 1113207753Smm 1114207753Smmextern void 1115207753Smmlist_totals(void) 1116207753Smm{ 1117207753Smm if (opt_robot) { 1118207753Smm // Always print totals in --robot mode. It can be convenient 1119207753Smm // in some cases and doesn't complicate usage of the 1120207753Smm // single-file case much. 1121207753Smm print_totals_robot(); 1122207753Smm 1123207753Smm } else if (totals.files > 1) { 1124207753Smm // For non-robot mode, totals are printed only if there 1125207753Smm // is more than one file. 1126207753Smm if (message_verbosity_get() <= V_WARNING) 1127207753Smm print_totals_basic(); 1128207753Smm else 1129207753Smm print_totals_adv(); 1130207753Smm } 1131207753Smm 1132207753Smm return; 1133207753Smm} 1134207753Smm 1135207753Smm 1136207753Smmextern void 1137207753Smmlist_file(const char *filename) 1138207753Smm{ 1139207753Smm if (opt_format != FORMAT_XZ && opt_format != FORMAT_AUTO) 1140207753Smm message_fatal(_("--list works only on .xz files " 1141207753Smm "(--format=xz or --format=auto)")); 1142207753Smm 1143207753Smm message_filename(filename); 1144207753Smm 1145207753Smm if (filename == stdin_filename) { 1146207753Smm message_error(_("--list does not support reading from " 1147207753Smm "standard input")); 1148207753Smm return; 1149207753Smm } 1150207753Smm 1151207753Smm // Unset opt_stdout so that io_open_src() won't accept special files. 1152207753Smm // Set opt_force so that io_open_src() will follow symlinks. 1153207753Smm opt_stdout = false; 1154207753Smm opt_force = true; 1155207753Smm file_pair *pair = io_open_src(filename); 1156207753Smm if (pair == NULL) 1157207753Smm return; 1158207753Smm 1159213700Smm xz_file_info xfi = XZ_FILE_INFO_INIT; 1160213700Smm if (!parse_indexes(&xfi, pair)) { 1161213700Smm bool fail; 1162207753Smm 1163207753Smm // We have three main modes: 1164207753Smm // - --robot, which has submodes if --verbose is specified 1165207753Smm // once or twice 1166207753Smm // - Normal --list without --verbose 1167207753Smm // - --list with one or two --verbose 1168207753Smm if (opt_robot) 1169213700Smm fail = print_info_robot(&xfi, pair); 1170207753Smm else if (message_verbosity_get() <= V_WARNING) 1171213700Smm fail = print_info_basic(&xfi, pair); 1172207753Smm else 1173213700Smm fail = print_info_adv(&xfi, pair); 1174207753Smm 1175213700Smm // Update the totals that are displayed after all 1176213700Smm // the individual files have been listed. Don't count 1177213700Smm // broken files. 1178213700Smm if (!fail) 1179213700Smm update_totals(&xfi); 1180213700Smm 1181213700Smm lzma_index_end(xfi.idx, NULL); 1182207753Smm } 1183207753Smm 1184207753Smm io_close(pair, false); 1185207753Smm return; 1186207753Smm} 1187