diff options
-rw-r--r-- | ChangeLog | 13 | ||||
-rw-r--r-- | INSTALL | 7 | ||||
-rw-r--r-- | Makefile.in | 2 | ||||
-rw-r--r-- | NEWS | 42 | ||||
-rw-r--r-- | README | 21 | ||||
-rw-r--r-- | arg_parser.cc | 15 | ||||
-rw-r--r-- | arg_parser.h | 23 | ||||
-rwxr-xr-x | configure | 6 | ||||
-rw-r--r-- | decoder.cc | 14 | ||||
-rw-r--r-- | decoder.h | 111 | ||||
-rw-r--r-- | doc/lzip.1 | 21 | ||||
-rw-r--r-- | doc/lzip.info | 346 | ||||
-rw-r--r-- | doc/lzip.texi | 326 | ||||
-rw-r--r-- | encoder.cc | 4 | ||||
-rw-r--r-- | encoder.h | 2 | ||||
-rw-r--r-- | encoder_base.cc | 8 | ||||
-rw-r--r-- | encoder_base.h | 5 | ||||
-rw-r--r-- | fast_encoder.cc | 2 | ||||
-rw-r--r-- | fast_encoder.h | 2 | ||||
-rw-r--r-- | list.cc | 53 | ||||
-rw-r--r-- | lzip.h | 10 | ||||
-rw-r--r-- | lzip_index.cc | 2 | ||||
-rw-r--r-- | lzip_index.h | 2 | ||||
-rw-r--r-- | main.cc | 124 | ||||
-rwxr-xr-x | testsuite/check.sh | 17 |
25 files changed, 645 insertions, 533 deletions
@@ -1,3 +1,12 @@ +2022-01-24 Antonio Diaz Diaz <antonio@gnu.org> + + * Version 1.23 released. + * Decompression time has been reduced by 5-12% depending on the file. + * main.cc (getnum): Show option name and valid range if error. + * Improve several descriptions in manual, '--help', and man page. + * lzip.texi: Change GNU Texinfo category to 'Compression'. + (Reported by Alfred M. Szmidt). + 2021-01-04 Antonio Diaz Diaz <antonio@gnu.org> * Version 1.22 released. @@ -24,7 +33,7 @@ * main.cc: Compile on DOS with DJGPP. * Fix a GCC warning about catching std::bad_alloc by value. * lzip.texi: Improve description of '-0..-9', '-m', and '-s'. - * configure: Accept appending to CXXFLAGS, 'CXXFLAGS+=OPTIONS'. + * configure: Accept appending to CXXFLAGS; 'CXXFLAGS+=OPTIONS'. * INSTALL: Document use of CXXFLAGS+='-D __USE_MINGW_ANSI_STDIO'. 2018-02-11 Antonio Diaz Diaz <antonio@gnu.org> @@ -318,7 +327,7 @@ * Version 0.1 released. -Copyright (C) 2008-2021 Antonio Diaz Diaz. +Copyright (C) 2008-2022 Antonio Diaz Diaz. This file is a collection of facts, and thus it is not copyrightable, but just in case, you have unlimited permission to copy, distribute, and @@ -1,7 +1,8 @@ Requirements ------------ -You will need a C++11 compiler. (gcc 3.3.6 or newer is recommended). -I use gcc 6.1.0 and 4.1.2, but the code should compile with any standards +You will need a C++98 compiler with suport for 'long long'. +(gcc 3.3.6 or newer is recommended). +I use gcc 6.1.0 and 3.3.6, but the code should compile with any standards compliant compiler. Gcc is available at http://gcc.gnu.org. @@ -74,7 +75,7 @@ If you need to build lzip on a system lacking a 'make' program, you can use ./configure --build --check --installdir=/usr/local/bin -Copyright (C) 2008-2021 Antonio Diaz Diaz. +Copyright (C) 2008-2022 Antonio Diaz Diaz. This file is free documentation: you have unlimited permission to copy, distribute, and modify it. diff --git a/Makefile.in b/Makefile.in index 7df577a..d07ad5a 100644 --- a/Makefile.in +++ b/Makefile.in @@ -20,7 +20,7 @@ objs = arg_parser.o lzip_index.o list.o encoder_base.o encoder.o \ all : $(progname) $(progname) : $(objs) - $(CXX) $(LDFLAGS) $(CXXFLAGS) -o $@ $(objs) + $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $(objs) main.o : main.cc $(CXX) $(CPPFLAGS) $(CXXFLAGS) -DPROGVERSION=\"$(pkgversion)\" -c -o $@ $< @@ -1,39 +1,11 @@ -Changes in version 1.22: +Changes in version 1.23: -Lzip now reports an error if a file name is empty (lzip -t ""). +Decompression time has been reduced by 5-12% depending on the file. -Option '-o, --output' now behaves like '-c, --stdout', but sending the -output unconditionally to a file instead of to standard output. See the new -description of '-o' in the manual. This change is backwards compatible only -when (de)compressing from standard input alone. Therefore commands like: - lzip -o foo.lz - bar < foo -must now be split into: - lzip -o foo.lz - < foo - lzip bar -or rewritten as: - lzip - bar < foo > foo.lz +In case of error in a numerical argument to a command line option, lzip +now shows the name of the option and the range of valid values. -When using '-c' or '-o', lzip now checks whether the output is a terminal -only once. +Several descriptions have been improved in manual, '--help', and man page. -Lzip now does not even open the output file if the input file is a terminal. - -Lzip can now be built, tested, and installed on systems lacking a 'make' -program. (Feature suggested by Mohammad Akhlaghi). - -The words 'decompressed' and 'compressed' have been replaced with the -shorter 'out' and 'in' in the verbose output when decompressing or testing. - -Option '--list' now reports corruption or truncation of the last header in a -multimenber file specifically instead of showing the generic message "Last -member in input file is truncated or corrupt." - -The commands needed to extract files from a tar.lz archive have been -documented in the manual, in the output of '--help', and in the man page. - -Plzip and tarlz are mentioned in the manual as alternatives for -multiprocessors. - -Several fixes and improvements have been made to the manual. - -9 new test files have been added to the testsuite. +The texinfo category of the manual has been changed from 'Data Compression' +to 'Compression' to match that of gzip. (Reported by Alfred M. Szmidt). @@ -2,13 +2,14 @@ Description Lzip is a lossless data compressor with a user interface similar to the one of gzip or bzip2. Lzip uses a simplified form of the 'Lempel-Ziv-Markov -chain-Algorithm' (LZMA) stream format, chosen to maximize safety and -interoperability. Lzip can compress about as fast as gzip (lzip -0) or -compress most files more than bzip2 (lzip -9). Decompression speed is -intermediate between gzip and bzip2. Lzip is better than gzip and bzip2 from -a data recovery perspective. Lzip has been designed, written, and tested -with great care to replace gzip and bzip2 as the standard general-purpose -compressed format for unix-like systems. +chain-Algorithm' (LZMA) stream format and provides a 3 factor integrity +checking to maximize interoperability and optimize safety. Lzip can compress +about as fast as gzip (lzip -0) or compress most files more than bzip2 +(lzip -9). Decompression speed is intermediate between gzip and bzip2. +Lzip is better than gzip and bzip2 from a data recovery perspective. Lzip +has been designed, written, and tested with great care to replace gzip and +bzip2 as the standard general-purpose compressed format for unix-like +systems. For compressing/decompressing large files on multiprocessor machines plzip can be much faster than lzip at the cost of a slightly reduced compression @@ -67,7 +68,7 @@ filename.lz becomes filename filename.tlz becomes filename.tar anyothername becomes anyothername.out -(De)compressing a file is much like copying or moving it; therefore lzip +(De)compressing a file is much like copying or moving it. Therefore lzip preserves the access and modification dates, permissions, and, when possible, ownership of the file just as 'cp -p' does. (If the user ID or the group ID can't be duplicated, the file permission bits S_ISUID and @@ -104,7 +105,7 @@ finding coding sequences of minimum size than the one currently used by lzip could be developed, and the resulting sequence could also be coded using the LZMA coding scheme. -Lzip currently implements two variants of the LZMA algorithm; fast +Lzip currently implements two variants of the LZMA algorithm: fast (used by option '-0') and normal (used by all other compression levels). The high compression of LZMA comes from combining two basic, well-proven @@ -124,7 +125,7 @@ been compressed. Decompressed is used to refer to data which have undergone the process of decompression. -Copyright (C) 2008-2021 Antonio Diaz Diaz. +Copyright (C) 2008-2022 Antonio Diaz Diaz. This file is free documentation: you have unlimited permission to copy, distribute, and modify it. diff --git a/arg_parser.cc b/arg_parser.cc index 2e40a13..59998ac 100644 --- a/arg_parser.cc +++ b/arg_parser.cc @@ -1,5 +1,5 @@ /* Arg_parser - POSIX/GNU command line argument parser. (C++ version) - Copyright (C) 2006-2021 Antonio Diaz Diaz. + Copyright (C) 2006-2022 Antonio Diaz Diaz. This library is free software. Redistribution and use in source and binary forms, with or without modification, are permitted provided @@ -35,9 +35,10 @@ bool Arg_parser::parse_long_option( const char * const opt, const char * const a // Test all long options for either exact match or abbreviated matches. for( int i = 0; options[i].code != 0; ++i ) - if( options[i].name && std::strncmp( options[i].name, &opt[2], len ) == 0 ) + if( options[i].long_name && + std::strncmp( options[i].long_name, &opt[2], len ) == 0 ) { - if( std::strlen( options[i].name ) == len ) // Exact match found + if( std::strlen( options[i].long_name ) == len ) // Exact match found { index = i; exact = true; break; } else if( index < 0 ) index = i; // First nonexact match found else if( options[index].code != options[i].code || @@ -58,19 +59,19 @@ bool Arg_parser::parse_long_option( const char * const opt, const char * const a } ++argind; - data.push_back( Record( options[index].code ) ); + data.push_back( Record( options[index].code, options[index].long_name ) ); if( opt[len+2] ) // '--<long_option>=<argument>' syntax { if( options[index].has_arg == no ) { - error_ = "option '--"; error_ += options[index].name; + error_ = "option '--"; error_ += options[index].long_name; error_ += "' doesn't allow an argument"; return false; } if( options[index].has_arg == yes && !opt[len+3] ) { - error_ = "option '--"; error_ += options[index].name; + error_ = "option '--"; error_ += options[index].long_name; error_ += "' requires an argument"; return false; } @@ -82,7 +83,7 @@ bool Arg_parser::parse_long_option( const char * const opt, const char * const a { if( !arg || !arg[0] ) { - error_ = "option '--"; error_ += options[index].name; + error_ = "option '--"; error_ += options[index].long_name; error_ += "' requires an argument"; return false; } diff --git a/arg_parser.h b/arg_parser.h index 5629b90..e854838 100644 --- a/arg_parser.h +++ b/arg_parser.h @@ -1,5 +1,5 @@ /* Arg_parser - POSIX/GNU command line argument parser. (C++ version) - Copyright (C) 2006-2021 Antonio Diaz Diaz. + Copyright (C) 2006-2022 Antonio Diaz Diaz. This library is free software. Redistribution and use in source and binary forms, with or without modification, are permitted provided @@ -23,9 +23,9 @@ In case of error, 'error' returns a non-empty error message. 'options' is an array of 'struct Option' terminated by an element - containing a code which is zero. A null name means a short-only - option. A code value outside the unsigned char range means a - long-only option. + containing a code which is zero. A null long_name means a short-only + option. A code value outside the unsigned char range means a long-only + option. Arg_parser normally makes it appear as if all the option arguments were specified before all the non-option arguments for the purposes @@ -48,7 +48,7 @@ public: struct Option { int code; // Short option letter or code ( code != 0 ) - const char * name; // Long option name (maybe null) + const char * long_name; // Long option name (maybe null) Has_arg has_arg; }; @@ -56,8 +56,12 @@ private: struct Record { int code; + std::string parsed_name; std::string argument; - explicit Record( const int c ) : code( c ) {} + explicit Record( const unsigned char c ) + : code( c ), parsed_name( "-" ) { parsed_name += c; } + Record( const int c, const char * const long_name ) + : code( c ), parsed_name( "--" ) { parsed_name += long_name; } explicit Record( const char * const arg ) : code( 0 ), argument( arg ) {} }; @@ -91,6 +95,13 @@ public: else return 0; } + // Full name of the option parsed (short or long). + const std::string & parsed_name( const int i ) const + { + if( i >= 0 && i < arguments() ) return data[i].parsed_name; + else return empty_arg; + } + const std::string & argument( const int i ) const { if( i >= 0 && i < arguments() ) return data[i].argument; @@ -1,12 +1,12 @@ #! /bin/sh # configure script for Lzip - LZMA lossless data compressor -# Copyright (C) 2008-2021 Antonio Diaz Diaz. +# Copyright (C) 2008-2022 Antonio Diaz Diaz. # # This configure script is free software: you have unlimited permission # to copy, distribute, and modify it. pkgname=lzip -pkgversion=1.22 +pkgversion=1.23 progname=lzip srctrigger=doc/${pkgname}.texi @@ -200,7 +200,7 @@ echo "LDFLAGS = ${LDFLAGS}" rm -f Makefile cat > Makefile << EOF # Makefile for Lzip - LZMA lossless data compressor -# Copyright (C) 2008-2021 Antonio Diaz Diaz. +# Copyright (C) 2008-2022 Antonio Diaz Diaz. # This file was generated automatically by configure. Don't edit. # # This Makefile is free software: you have unlimited permission @@ -1,5 +1,5 @@ /* Lzip - LZMA lossless data compressor - Copyright (C) 2008-2021 Antonio Diaz Diaz. + Copyright (C) 2008-2022 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -31,8 +31,8 @@ #include "decoder.h" -/* Returns the number of bytes really read. - If (returned value < size) and (errno == 0), means EOF was reached. +/* Return the number of bytes really read. + If (value returned < size) and (errno == 0), means EOF was reached. */ int readblock( const int fd, uint8_t * const buf, const int size ) { @@ -50,8 +50,8 @@ int readblock( const int fd, uint8_t * const buf, const int size ) } -/* Returns the number of bytes really written. - If (returned value < size), it is always an error. +/* Return the number of bytes really written. + If (value returned < size), it is always an error. */ int writeblock( const int fd, const uint8_t * const buf, const int size ) { @@ -232,11 +232,11 @@ int LZ_decoder::decode_member( const Pretty_print & pp ) rep0 = distance; } state.set_rep(); - len = min_match_len + rdec.decode_len( rep_len_model, pos_state ); + len = rdec.decode_len( rep_len_model, pos_state ); } else // match { - len = min_match_len + rdec.decode_len( match_len_model, pos_state ); + len = rdec.decode_len( match_len_model, pos_state ); unsigned distance = rdec.decode_tree6( bm_dis_slot[get_len_state(len)] ); if( distance >= start_dis_model ) { @@ -1,5 +1,5 @@ /* Lzip - LZMA lossless data compressor - Copyright (C) 2008-2021 Antonio Diaz Diaz. + Copyright (C) 2008-2022 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -105,7 +105,7 @@ public: return symbol; } - unsigned decode_bit( Bit_model & bm ) + bool decode_bit( Bit_model & bm ) { normalize(); const uint32_t bound = ( range >> bit_model_total_bits ) * bm.probability; @@ -118,37 +118,78 @@ public: } else { - range -= bound; code -= bound; + range -= bound; bm.probability -= bm.probability >> bit_model_move_bits; return 1; } } - unsigned decode_tree3( Bit_model bm[] ) + void decode_symbol_bit( Bit_model & bm, unsigned & symbol ) { - unsigned symbol = 2 | decode_bit( bm[1] ); - symbol = ( symbol << 1 ) | decode_bit( bm[symbol] ); - symbol = ( symbol << 1 ) | decode_bit( bm[symbol] ); - return symbol & 7; + normalize(); + symbol <<= 1; + const uint32_t bound = ( range >> bit_model_total_bits ) * bm.probability; + if( code < bound ) + { + range = bound; + bm.probability += + ( bit_model_total - bm.probability ) >> bit_model_move_bits; + } + else + { + code -= bound; + range -= bound; + bm.probability -= bm.probability >> bit_model_move_bits; + symbol |= 1; + } + } + + void decode_symbol_bit_reversed( Bit_model & bm, unsigned & model, + unsigned & symbol, const int i ) + { + normalize(); + model <<= 1; + const uint32_t bound = ( range >> bit_model_total_bits ) * bm.probability; + if( code < bound ) + { + range = bound; + bm.probability += + ( bit_model_total - bm.probability ) >> bit_model_move_bits; + } + else + { + code -= bound; + range -= bound; + bm.probability -= bm.probability >> bit_model_move_bits; + model |= 1; + symbol |= 1 << i; + } } unsigned decode_tree6( Bit_model bm[] ) { - unsigned symbol = 2 | decode_bit( bm[1] ); - symbol = ( symbol << 1 ) | decode_bit( bm[symbol] ); - symbol = ( symbol << 1 ) | decode_bit( bm[symbol] ); - symbol = ( symbol << 1 ) | decode_bit( bm[symbol] ); - symbol = ( symbol << 1 ) | decode_bit( bm[symbol] ); - symbol = ( symbol << 1 ) | decode_bit( bm[symbol] ); + unsigned symbol = 1; + decode_symbol_bit( bm[symbol], symbol ); + decode_symbol_bit( bm[symbol], symbol ); + decode_symbol_bit( bm[symbol], symbol ); + decode_symbol_bit( bm[symbol], symbol ); + decode_symbol_bit( bm[symbol], symbol ); + decode_symbol_bit( bm[symbol], symbol ); return symbol & 0x3F; } unsigned decode_tree8( Bit_model bm[] ) { unsigned symbol = 1; - for( int i = 0; i < 8; ++i ) - symbol = ( symbol << 1 ) | decode_bit( bm[symbol] ); + decode_symbol_bit( bm[symbol], symbol ); + decode_symbol_bit( bm[symbol], symbol ); + decode_symbol_bit( bm[symbol], symbol ); + decode_symbol_bit( bm[symbol], symbol ); + decode_symbol_bit( bm[symbol], symbol ); + decode_symbol_bit( bm[symbol], symbol ); + decode_symbol_bit( bm[symbol], symbol ); + decode_symbol_bit( bm[symbol], symbol ); return symbol & 0xFF; } @@ -157,20 +198,18 @@ public: unsigned model = 1; unsigned symbol = 0; for( int i = 0; i < num_bits; ++i ) - { - const unsigned bit = decode_bit( bm[model] ); - model <<= 1; model += bit; - symbol |= ( bit << i ); - } + decode_symbol_bit_reversed( bm[model], model, symbol, i ); return symbol; } unsigned decode_tree_reversed4( Bit_model bm[] ) { - unsigned symbol = decode_bit( bm[1] ); - symbol += decode_bit( bm[2+symbol] ) << 1; - symbol += decode_bit( bm[4+symbol] ) << 2; - symbol += decode_bit( bm[8+symbol] ) << 3; + unsigned model = 1; + unsigned symbol = 0; + decode_symbol_bit_reversed( bm[model], model, symbol, 0 ); + decode_symbol_bit_reversed( bm[model], model, symbol, 1 ); + decode_symbol_bit_reversed( bm[model], model, symbol, 2 ); + decode_symbol_bit_reversed( bm[model], model, symbol, 3 ); return symbol; } @@ -185,8 +224,7 @@ public: symbol <<= 1; symbol |= bit; if( match_bit >> 8 != bit ) { - while( symbol < 0x100 ) - symbol = ( symbol << 1 ) | decode_bit( bm[symbol] ); + while( symbol < 0x100 ) decode_symbol_bit( bm[symbol], symbol ); break; } } @@ -195,11 +233,24 @@ public: unsigned decode_len( Len_model & lm, const int pos_state ) { + Bit_model * bm; + unsigned mask, offset, symbol = 1; + if( decode_bit( lm.choice1 ) == 0 ) - return decode_tree3( lm.bm_low[pos_state] ); + { bm = lm.bm_low[pos_state]; mask = 7; offset = 0; goto len3; } if( decode_bit( lm.choice2 ) == 0 ) - return len_low_symbols + decode_tree3( lm.bm_mid[pos_state] ); - return len_low_symbols + len_mid_symbols + decode_tree8( lm.bm_high ); + { bm = lm.bm_mid[pos_state]; mask = 7; offset = len_low_symbols; goto len3; } + bm = lm.bm_high; mask = 0xFF; offset = len_low_symbols + len_mid_symbols; + decode_symbol_bit( bm[symbol], symbol ); + decode_symbol_bit( bm[symbol], symbol ); + decode_symbol_bit( bm[symbol], symbol ); + decode_symbol_bit( bm[symbol], symbol ); + decode_symbol_bit( bm[symbol], symbol ); +len3: + decode_symbol_bit( bm[symbol], symbol ); + decode_symbol_bit( bm[symbol], symbol ); + decode_symbol_bit( bm[symbol], symbol ); + return ( symbol & mask ) + min_match_len + offset; } }; @@ -1,5 +1,5 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.16. -.TH LZIP "1" "January 2021" "lzip 1.22" "User Commands" +.TH LZIP "1" "January 2022" "lzip 1.23" "User Commands" .SH NAME lzip \- reduces the size of files .SH SYNOPSIS @@ -8,13 +8,14 @@ lzip \- reduces the size of files .SH DESCRIPTION Lzip is a lossless data compressor with a user interface similar to the one of gzip or bzip2. Lzip uses a simplified form of the 'Lempel\-Ziv\-Markov -chain\-Algorithm' (LZMA) stream format, chosen to maximize safety and -interoperability. Lzip can compress about as fast as gzip (lzip \fB\-0\fR) or -compress most files more than bzip2 (lzip \fB\-9\fR). Decompression speed is -intermediate between gzip and bzip2. Lzip is better than gzip and bzip2 from -a data recovery perspective. Lzip has been designed, written, and tested -with great care to replace gzip and bzip2 as the standard general\-purpose -compressed format for unix\-like systems. +chain\-Algorithm' (LZMA) stream format and provides a 3 factor integrity +checking to maximize interoperability and optimize safety. Lzip can compress +about as fast as gzip (lzip \fB\-0\fR) or compress most files more than bzip2 +(lzip \fB\-9\fR). Decompression speed is intermediate between gzip and bzip2. +Lzip is better than gzip and bzip2 from a data recovery perspective. Lzip +has been designed, written, and tested with great care to replace gzip and +bzip2 as the standard general\-purpose compressed format for unix\-like +systems. .SH OPTIONS .TP \fB\-h\fR, \fB\-\-help\fR @@ -97,7 +98,7 @@ To extract all the files from archive 'foo.tar.lz', use the commands .PP Exit status: 0 for a normal exit, 1 for environmental problems (file not found, invalid flags, I/O errors, etc), 2 to indicate a corrupt or -invalid input file, 3 for an internal consistency error (eg, bug) which +invalid input file, 3 for an internal consistency error (e.g., bug) which caused lzip to panic. .PP The ideas embodied in lzip are due to (at least) the following people: @@ -110,7 +111,7 @@ Report bugs to lzip\-bug@nongnu.org .br Lzip home page: http://www.nongnu.org/lzip/lzip.html .SH COPYRIGHT -Copyright \(co 2021 Antonio Diaz Diaz. +Copyright \(co 2022 Antonio Diaz Diaz. License GPLv2+: GNU GPL version 2 or later <http://gnu.org/licenses/gpl.html> .br This is free software: you are free to change and redistribute it. diff --git a/doc/lzip.info b/doc/lzip.info index 883ab08..7c6d812 100644 --- a/doc/lzip.info +++ b/doc/lzip.info @@ -1,6 +1,6 @@ This is lzip.info, produced by makeinfo version 4.13+ from lzip.texi. -INFO-DIR-SECTION Data Compression +INFO-DIR-SECTION Compression START-INFO-DIR-ENTRY * Lzip: (lzip). LZMA lossless data compressor END-INFO-DIR-ENTRY @@ -11,7 +11,7 @@ File: lzip.info, Node: Top, Next: Introduction, Up: (dir) Lzip Manual *********** -This manual is for Lzip (version 1.22, 4 January 2021). +This manual is for Lzip (version 1.23, 24 January 2022). * Menu: @@ -19,8 +19,8 @@ This manual is for Lzip (version 1.22, 4 January 2021). * Output:: Meaning of lzip's output * Invoking lzip:: Command line interface * Quality assurance:: Design, development, and testing of lzip -* File format:: Detailed format of the compressed file * Algorithm:: How lzip compresses the data +* File format:: Detailed format of the compressed file * Stream format:: Format of the LZMA stream in lzip files * Trailing data:: Extra data appended to the file * Examples:: A small tutorial with examples @@ -29,7 +29,7 @@ This manual is for Lzip (version 1.22, 4 January 2021). * Concept index:: Index of concepts - Copyright (C) 2008-2021 Antonio Diaz Diaz. + Copyright (C) 2008-2022 Antonio Diaz Diaz. This manual is free documentation: you have unlimited permission to copy, distribute, and modify it. @@ -42,13 +42,14 @@ File: lzip.info, Node: Introduction, Next: Output, Prev: Top, Up: Top Lzip is a lossless data compressor with a user interface similar to the one of gzip or bzip2. Lzip uses a simplified form of the 'Lempel-Ziv-Markov -chain-Algorithm' (LZMA) stream format, chosen to maximize safety and -interoperability. Lzip can compress about as fast as gzip (lzip -0) or -compress most files more than bzip2 (lzip -9). Decompression speed is -intermediate between gzip and bzip2. Lzip is better than gzip and bzip2 from -a data recovery perspective. Lzip has been designed, written, and tested -with great care to replace gzip and bzip2 as the standard general-purpose -compressed format for unix-like systems. +chain-Algorithm' (LZMA) stream format and provides a 3 factor integrity +checking to maximize interoperability and optimize safety. Lzip can compress +about as fast as gzip (lzip -0) or compress most files more than bzip2 +(lzip -9). Decompression speed is intermediate between gzip and bzip2. Lzip +is better than gzip and bzip2 from a data recovery perspective. Lzip has +been designed, written, and tested with great care to replace gzip and +bzip2 as the standard general-purpose compressed format for unix-like +systems. For compressing/decompressing large files on multiprocessor machines plzip can be much faster than lzip at the cost of a slightly reduced @@ -86,9 +87,9 @@ byte near the beginning is a thing of the past. The member trailer stores the 32-bit CRC of the original data, the size of the original data, and the size of the member. These values, together -with the end-of-stream marker, provide a 3 factor integrity checking which -guarantees that the decompressed version of the data is identical to the -original. This guards against corruption of the compressed data, and +with the "End Of Stream" marker, provide a 3 factor integrity checking +which guarantees that the decompressed version of the data is identical to +the original. This guards against corruption of the compressed data, and against undetected bugs in lzip (hopefully very unlikely). The chances of data corruption going undetected are microscopic. Be aware, though, that the check occurs upon decompression, so it can only tell you that something @@ -119,7 +120,7 @@ filename.lz becomes filename filename.tlz becomes filename.tar anyothername becomes anyothername.out - (De)compressing a file is much like copying or moving it; therefore lzip + (De)compressing a file is much like copying or moving it. Therefore lzip preserves the access and modification dates, permissions, and, when possible, ownership of the file just as 'cp -p' does. (If the user ID or the group ID can't be duplicated, the file permission bits S_ISUID and @@ -247,10 +248,13 @@ once, the first time it appears in the command line. '-d' '--decompress' - Decompress the files specified. If a file does not exist or can't be - opened, lzip continues decompressing the rest of the files. If a file - fails to decompress, or is a terminal, lzip exits immediately without - decompressing the rest of the files. + Decompress the files specified. If a file does not exist, can't be + opened, or the destination file already exists and '--force' has not + been specified, lzip continues decompressing the rest of the files and + exits with error status 1. If a file fails to decompress, or is a + terminal, lzip exits immediately with error status 2 without + decompressing the rest of the files. A terminal is considered an + uncompressed file, and therefore invalid. '-f' '--force' @@ -276,10 +280,12 @@ once, the first time it appears in the command line. positions and sizes of each member in multimember files are also printed. - '-lq' can be used to verify quickly (without decompressing) the - structural integrity of the files specified. (Use '--test' to verify - the data integrity). '-alq' additionally verifies that none of the - files specified contain trailing data. + If any file is damaged, does not exist, can't be opened, or is not + regular, the final exit status will be > 0. '-lq' can be used to verify + quickly (without decompressing) the structural integrity of the files + specified. (Use '--test' to verify the data integrity). '-alq' + additionally verifies that none of the files specified contain + trailing data. '-m BYTES' '--match-length=BYTES' @@ -418,11 +424,11 @@ Y yottabyte (10^24) | Yi yobibyte (2^80) Exit status: 0 for a normal exit, 1 for environmental problems (file not found, invalid flags, I/O errors, etc), 2 to indicate a corrupt or invalid -input file, 3 for an internal consistency error (eg, bug) which caused lzip -to panic. +input file, 3 for an internal consistency error (e.g., bug) which caused +lzip to panic. -File: lzip.info, Node: Quality assurance, Next: File format, Prev: Invoking lzip, Up: Top +File: lzip.info, Node: Quality assurance, Next: Algorithm, Prev: Invoking lzip, Up: Top 4 Design, development, and testing of lzip ****************************************** @@ -570,12 +576,13 @@ extraction of the decompressed data. ============================= 'Accurate and robust error detection' - The lzip format provides 3 factor integrity checking and the - decompressors report mismatches in each factor separately. This way if - just one byte in one factor fails but the other two factors match the - data, it probably means that the data are intact and the corruption - just affects the mismatching factor (CRC or data size) in the check - sequence. + The lzip format provides 3 factor integrity checking, and the + decompressors report mismatches in each factor separately. This method + detects most false positives for corruption. If just one byte in one + factor fails but the other two factors match the data, it probably + means that the data are intact and the corruption just affects the + mismatching factor (CRC, data size, or member size) in the member + trailer. 'Multiple implementations' Just like the lzip format provides 3 factor protection against @@ -609,82 +616,9 @@ extraction of the decompressed data. -File: lzip.info, Node: File format, Next: Algorithm, Prev: Quality assurance, Up: Top - -5 File format -************* - -Perfection is reached, not when there is no longer anything to add, but -when there is no longer anything to take away. --- Antoine de Saint-Exupery - - - In the diagram below, a box like this: - -+---+ -| | <-- the vertical bars might be missing -+---+ - - represents one byte; a box like this: - -+==============+ -| | -+==============+ - - represents a variable number of bytes. - - - A lzip file consists of a series of "members" (compressed data sets). -The members simply appear one after another in the file, with no additional -information before, between, or after them. - - Each member has the following structure: - -+--+--+--+--+----+----+=============+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -| ID string | VN | DS | LZMA stream | CRC32 | Data size | Member size | -+--+--+--+--+----+----+=============+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - - All multibyte values are stored in little endian order. - -'ID string (the "magic" bytes)' - A four byte string, identifying the lzip format, with the value "LZIP" - (0x4C, 0x5A, 0x49, 0x50). - -'VN (version number, 1 byte)' - Just in case something needs to be modified in the future. 1 for now. - -'DS (coded dictionary size, 1 byte)' - The dictionary size is calculated by taking a power of 2 (the base - size) and subtracting from it a fraction between 0/16 and 7/16 of the - base size. - Bits 4-0 contain the base 2 logarithm of the base size (12 to 29). - Bits 7-5 contain the numerator of the fraction (0 to 7) to subtract - from the base size to obtain the dictionary size. - Example: 0xD3 = 2^19 - 6 * 2^15 = 512 KiB - 6 * 32 KiB = 320 KiB - Valid values for dictionary size range from 4 KiB to 512 MiB. - -'LZMA stream' - The LZMA stream, finished by an end of stream marker. Uses default - values for encoder properties. *Note Stream format::, for a complete - description. - -'CRC32 (4 bytes)' - Cyclic Redundancy Check (CRC) of the uncompressed original data. - -'Data size (8 bytes)' - Size of the uncompressed original data. - -'Member size (8 bytes)' - Total size of the member, including header and trailer. This field acts - as a distributed index, allows the verification of stream integrity, - and facilitates safe recovery of undamaged members from multimember - files. - - - -File: lzip.info, Node: Algorithm, Next: Stream format, Prev: File format, Up: Top +File: lzip.info, Node: Algorithm, Next: File format, Prev: Quality assurance, Up: Top -6 Algorithm +5 Algorithm *********** In spite of its name (Lempel-Ziv-Markov chain-Algorithm), LZMA is not a @@ -699,7 +633,7 @@ finding coding sequences of minimum size than the one currently used by lzip could be developed, and the resulting sequence could also be coded using the LZMA coding scheme. - Lzip currently implements two variants of the LZMA algorithm; fast (used + Lzip currently implements two variants of the LZMA algorithm: fast (used by option '-0') and normal (used by all other compression levels). The high compression of LZMA comes from combining two basic, well-proven @@ -711,7 +645,7 @@ contexts according to what the bits are used for. Lzip is a two stage compressor. The first stage is a Lempel-Ziv coder, which reduces redundancy by translating chunks of data to their corresponding distance-length pairs. The second stage is a range encoder -that uses a different probability model for each type of data; distances, +that uses a different probability model for each type of data: distances, lengths, literal bytes, etc. Here is how it works, step by step: @@ -757,17 +691,90 @@ encoding), Igor Pavlov (for putting all the above together in LZMA), and Julian Seward (for bzip2's CLI). -File: lzip.info, Node: Stream format, Next: Trailing data, Prev: Algorithm, Up: Top +File: lzip.info, Node: File format, Next: Stream format, Prev: Algorithm, Up: Top + +6 File format +************* + +Perfection is reached, not when there is no longer anything to add, but +when there is no longer anything to take away. +-- Antoine de Saint-Exupery + + + In the diagram below, a box like this: + ++---+ +| | <-- the vertical bars might be missing ++---+ + + represents one byte; a box like this: + ++==============+ +| | ++==============+ + + represents a variable number of bytes. + + + A lzip file consists of a series of independent "members" (compressed +data sets). The members simply appear one after another in the file, with no +additional information before, between, or after them. Each member can +encode in compressed form up to 16 EiB - 1 byte of uncompressed data. The +size of a multimember file is unlimited. + + Each member has the following structure: + ++--+--+--+--+----+----+=============+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| ID string | VN | DS | LZMA stream | CRC32 | Data size | Member size | ++--+--+--+--+----+----+=============+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + All multibyte values are stored in little endian order. + +'ID string (the "magic" bytes)' + A four byte string, identifying the lzip format, with the value "LZIP" + (0x4C, 0x5A, 0x49, 0x50). + +'VN (version number, 1 byte)' + Just in case something needs to be modified in the future. 1 for now. + +'DS (coded dictionary size, 1 byte)' + The dictionary size is calculated by taking a power of 2 (the base + size) and subtracting from it a fraction between 0/16 and 7/16 of the + base size. + Bits 4-0 contain the base 2 logarithm of the base size (12 to 29). + Bits 7-5 contain the numerator of the fraction (0 to 7) to subtract + from the base size to obtain the dictionary size. + Example: 0xD3 = 2^19 - 6 * 2^15 = 512 KiB - 6 * 32 KiB = 320 KiB + Valid values for dictionary size range from 4 KiB to 512 MiB. + +'LZMA stream' + The LZMA stream, finished by an "End Of Stream" marker. Uses default + values for encoder properties. *Note Stream format::, for a complete + description. + +'CRC32 (4 bytes)' + Cyclic Redundancy Check (CRC) of the original uncompressed data. + +'Data size (8 bytes)' + Size of the original uncompressed data. + +'Member size (8 bytes)' + Total size of the member, including header and trailer. This field acts + as a distributed index, allows the verification of stream integrity, + and facilitates the safe recovery of undamaged members from + multimember files. Member size should be limited to 2 PiB to prevent + the data size field from overflowing. + + + +File: lzip.info, Node: Stream format, Next: Trailing data, Prev: File format, Up: Top 7 Format of the LZMA stream in lzip files ***************************************** -Lzip uses a simplified form of the LZMA stream format chosen to maximize -safety and interoperability. - - The LZMA algorithm has three parameters, called "special LZMA -properties", to adjust it for some kinds of binary data. These parameters -are; 'literal_context_bits' (with a default value of 3), +The LZMA algorithm has three parameters, called "special LZMA properties", +to adjust it for some kinds of binary data. These parameters are: +'literal_context_bits' (with a default value of 3), 'literal_pos_state_bits' (with a default value of 0), and 'pos_state_bits' (with a default value of 2). As a general purpose compressor, lzip only uses the default values for these parameters. In particular @@ -777,12 +784,14 @@ in the code. Lzip finishes the LZMA stream with an "End Of Stream" (EOS) marker (the distance-length pair 0xFFFFFFFFU, 2), which in conjunction with the 'member size' field in the member trailer allows the verification of stream -integrity. The LZMA stream in lzip files always has these two features -(default properties and EOS marker) and is referred to in this document as -LZMA-302eos. The EOS marker is the only marker allowed in lzip files. +integrity. The EOS marker is the only marker allowed in lzip files. The +LZMA stream in lzip files always has these two features (default properties +and EOS marker) and is referred to in this document as LZMA-302eos. This +simplified form of the LZMA stream format has been chosen to maximize +interoperability and safety. The second stage of LZMA is a range encoder that uses a different -probability model for each type of symbol; distances, lengths, literal +probability model for each type of symbol: distances, lengths, literal bytes, etc. Range encoding conceptually encodes all the symbols of the message into one number. Unlike Huffman coding, which assigns to each symbol a bit-pattern and concatenates all the bit-patterns together, range @@ -790,16 +799,16 @@ encoding can compress one symbol to less than one bit. Therefore the compressed data produced by a range encoder can't be split in pieces that could be described individually. - It seems that the only way of describing the LZMA-302eos stream is -describing the algorithm that decodes it. And given the many details about + It seems that the only way of describing the LZMA-302eos stream is to +describe the algorithm that decodes it. And given the many details about the range decoder that need to be described accurately, the source code of -a real decoder seems the only appropriate reference to use. +a real decompressor seems the only appropriate reference to use. What follows is a description of the decoding algorithm for LZMA-302eos streams using as reference the source code of "lzd", an educational decompressor for lzip files which can be downloaded from the lzip download -directory. The source code of lzd is included in appendix A. *Note -Reference source code::. +directory. Lzd is written in C++11 and its source code is included in +appendix A. *Note Reference source code::. 7.1 What is coded @@ -835,7 +844,7 @@ Bit sequence Description 1 + 1 + 8 bits lengths from 18 to 273 - The coding of distances is a little more complicated, so I'll begin + The coding of distances is a little more complicated, so I'll begin by explaining a simpler version of the encoding. Imagine you need to encode a number from 0 to 2^32 - 1, and you want to @@ -845,7 +854,7 @@ which you may find by making a bit scan from the left (from the MSB). A position of 0 means that the number is 0 (no bit is set), 1 means the LSB is the first bit set (the number is 1), and 32 means the MSB is set (i.e., the number is >= 0x80000000). Then, if the position is >= 2, you encode the -remaining position - 1 bits. Let's call these bits "direct_bits" because +remaining position - 1 bits. Let's call these bits "direct bits" because they are coded directly by value instead of indirectly by position. The inconvenient of this simple method is that it needs 6 bits to encode @@ -901,9 +910,10 @@ integers representing the probability of the corresponding bit being 0. of 3. The resulting value is in the range 0 to 3. - In the following table, '!literal' is any sequence except a literal -byte. 'rep' is any one of 'rep0', 'rep1', 'rep2', or 'rep3'. The types of -previous sequences corresponding to each state are: + The types of previous sequences corresponding to each state are shown in +the following table. '!literal' is any sequence except a literal byte. +'rep' is any one of 'rep0', 'rep1', 'rep2', or 'rep3'. The last type in +each line is the most recent. State Types of previous sequences ------------------------------------------------------ @@ -974,9 +984,9 @@ The LZMA stream is consumed one byte at a time by the range decoder. (See of decoded bits, depending on how well these bits agree with their context. (See 'decode_bit' in the source). - The range decoder state consists of two unsigned 32-bit variables; + The range decoder state consists of two unsigned 32-bit variables: 'range' (representing the most significant part of the range size not yet -decoded), and 'code' (representing the current point within 'range'). +decoded) and 'code' (representing the current point within 'range'). 'range' is initialized to 2^32 - 1, and 'code' is initialized to 0. The range encoder produces a first 0 byte that must be ignored by the @@ -988,7 +998,7 @@ range decoder. This is done by shifting 5 bytes in the initialization of ========================================== After decoding the member header and obtaining the dictionary size, the -range decoder is initialized and then the LZMA decoder enters a loop (See +range decoder is initialized and then the LZMA decoder enters a loop (see 'decode_member' in the source) where it invokes the range decoder with the appropriate contexts to decode the different coding sequences (matches, repeated matches, and literal bytes), until the "End Of Stream" marker is @@ -996,8 +1006,8 @@ decoded. Once the "End Of Stream" marker has been decoded, the decompressor reads and decodes the member trailer, and verifies that the three integrity -factors (CRC, data size, and member size) match those calculated by the -LZMA decoder. +factors stored there (CRC, data size, and member size) match those computed +from the data. File: lzip.info, Node: Trailing data, Next: Examples, Prev: Stream format, Up: Top @@ -1074,7 +1084,7 @@ show the compression ratio. lzip -v file -Example 3: Like example 1 but the created 'file.lz' is multimember with a +Example 3: Like example 2 but the created 'file.lz' is multimember with a member size of 1 MiB. The compression ratio is not shown. lzip -b 1MiB file @@ -1092,15 +1102,7 @@ status. lzip -tv file.lz -Example 6: Compress a whole device in /dev/sdc and send the output to -'file.lz'. - - lzip -c /dev/sdc > file.lz - or - lzip /dev/sdc -o file.lz - - -Example 7: The right way of concatenating the decompressed output of two or +Example 6: The right way of concatenating the decompressed output of two or more compressed files. *Note Trailing data::. Don't do this @@ -1109,18 +1111,26 @@ more compressed files. *Note Trailing data::. lzip -cd file1.lz file2.lz file3.lz -Example 8: Decompress 'file.lz' partially until 10 KiB of decompressed data +Example 7: Decompress 'file.lz' partially until 10 KiB of decompressed data are produced. lzip -cd file.lz | dd bs=1024 count=10 -Example 9: Decompress 'file.lz' partially from decompressed byte at offset +Example 8: Decompress 'file.lz' partially from decompressed byte at offset 10000 to decompressed byte at offset 14999 (5000 bytes are produced). lzip -cd file.lz | dd bs=1000 skip=10 count=5 +Example 9: Compress a whole device in /dev/sdc and send the output to +'file.lz'. + + lzip -c /dev/sdc > file.lz + or + lzip /dev/sdc -o file.lz + + Example 10: Create a multivolume compressed tar archive with a volume size of 1440 KiB. @@ -1160,7 +1170,7 @@ Appendix A Reference source code ******************************** /* Lzd - Educational decompressor for the lzip format - Copyright (C) 2013-2021 Antonio Diaz Diaz. + Copyright (C) 2013-2022 Antonio Diaz Diaz. This program is free software. Redistribution and use in source and binary forms, with or without modification, are permitted provided @@ -1190,7 +1200,7 @@ Appendix A Reference source code #include <cstring> #include <stdint.h> #include <unistd.h> -#if defined(__MSVCRT__) || defined(__OS2__) || defined(__DJGPP__) +#if defined __MSVCRT__ || defined __OS2__ || defined __DJGPP__ #include <fcntl.h> #include <io.h> #endif @@ -1580,7 +1590,7 @@ int main( const int argc, const char * const argv[] ) "See the lzip manual for an explanation of the code.\n" "\nUsage: %s [-d] < file.lz > file\n" "Lzd decompresses from standard input to standard output.\n" - "\nCopyright (C) 2021 Antonio Diaz Diaz.\n" + "\nCopyright (C) 2022 Antonio Diaz Diaz.\n" "License 2-clause BSD.\n" "This is free software: you are free to change and redistribute it.\n" "There is NO WARRANTY, to the extent permitted by law.\n" @@ -1590,7 +1600,7 @@ int main( const int argc, const char * const argv[] ) return 0; } -#if defined(__MSVCRT__) || defined(__OS2__) || defined(__DJGPP__) +#if defined __MSVCRT__ || defined __OS2__ || defined __DJGPP__ setmode( STDIN_FILENO, O_BINARY ); setmode( STDOUT_FILENO, O_BINARY ); #endif @@ -1672,23 +1682,23 @@ Concept index Tag Table: -Node: Top208 -Node: Introduction1202 -Node: Output6930 -Node: Invoking lzip8525 -Ref: --trailing-error9314 -Node: Quality assurance18307 -Node: File format27264 -Ref: coded-dict-size28554 -Node: Algorithm29690 -Node: Stream format33090 -Ref: what-is-coded35459 -Node: Trailing data44328 -Node: Examples46589 -Ref: concat-example48189 -Node: Problems49252 -Node: Reference source code49784 -Node: Concept index64648 +Node: Top203 +Node: Introduction1198 +Node: Output6972 +Node: Invoking lzip8567 +Ref: --trailing-error9356 +Node: Quality assurance18682 +Node: Algorithm27705 +Node: File format31109 +Ref: coded-dict-size32538 +Node: Stream format33773 +Ref: what-is-coded36169 +Node: Trailing data45097 +Node: Examples47358 +Ref: concat-example48800 +Node: Problems50021 +Node: Reference source code50553 +Node: Concept index65411 End Tag Table diff --git a/doc/lzip.texi b/doc/lzip.texi index b22337e..144b525 100644 --- a/doc/lzip.texi +++ b/doc/lzip.texi @@ -6,10 +6,10 @@ @finalout @c %**end of header -@set UPDATED 4 January 2021 -@set VERSION 1.22 +@set UPDATED 24 January 2022 +@set VERSION 1.23 -@dircategory Data Compression +@dircategory Compression @direntry * Lzip: (lzip). LZMA lossless data compressor @end direntry @@ -40,8 +40,8 @@ This manual is for Lzip (version @value{VERSION}, @value{UPDATED}). * Output:: Meaning of lzip's output * Invoking lzip:: Command line interface * Quality assurance:: Design, development, and testing of lzip -* File format:: Detailed format of the compressed file * Algorithm:: How lzip compresses the data +* File format:: Detailed format of the compressed file * Stream format:: Format of the LZMA stream in lzip files * Trailing data:: Extra data appended to the file * Examples:: A small tutorial with examples @@ -51,7 +51,7 @@ This manual is for Lzip (version @value{VERSION}, @value{UPDATED}). @end menu @sp 1 -Copyright @copyright{} 2008-2021 Antonio Diaz Diaz. +Copyright @copyright{} 2008-2022 Antonio Diaz Diaz. This manual is free documentation: you have unlimited permission to copy, distribute, and modify it. @@ -65,13 +65,14 @@ distribute, and modify it. @uref{http://www.nongnu.org/lzip/lzip.html,,Lzip} is a lossless data compressor with a user interface similar to the one of gzip or bzip2. Lzip uses a simplified form of the 'Lempel-Ziv-Markov -chain-Algorithm' (LZMA) stream format, chosen to maximize safety and -interoperability. Lzip can compress about as fast as gzip @w{(lzip -0)} or -compress most files more than bzip2 @w{(lzip -9)}. Decompression speed is -intermediate between gzip and bzip2. Lzip is better than gzip and bzip2 from -a data recovery perspective. Lzip has been designed, written, and tested -with great care to replace gzip and bzip2 as the standard general-purpose -compressed format for unix-like systems. +chain-Algorithm' (LZMA) stream format and provides a 3 factor integrity +checking to maximize interoperability and optimize safety. Lzip can compress +about as fast as gzip @w{(lzip -0)} or compress most files more than bzip2 +@w{(lzip -9)}. Decompression speed is intermediate between gzip and bzip2. +Lzip is better than gzip and bzip2 from a data recovery perspective. Lzip +has been designed, written, and tested with great care to replace gzip and +bzip2 as the standard general-purpose compressed format for unix-like +systems. For compressing/decompressing large files on multiprocessor machines @uref{http://www.nongnu.org/lzip/manual/plzip_manual.html,,plzip} can be @@ -81,8 +82,8 @@ much faster than lzip at the cost of a slightly reduced compression ratio. @end ifnothtml For creation and manipulation of compressed tar archives -@uref{http://www.nongnu.org/lzip/manual/tarlz_manual.html,,tarlz} can be -more efficient than using tar and plzip because tarlz is able to keep the +@uref{http://www.nongnu.org/lzip/manual/tarlz_manual.html,,tarlz} can be more +efficient than using tar and plzip because tarlz is able to keep the alignment between tar members and lzip members. @ifnothtml @xref{Top,tarlz manual,,tarlz}. @@ -123,7 +124,7 @@ the beginning is a thing of the past. The member trailer stores the 32-bit CRC of the original data, the size of the original data, and the size of the member. These values, together -with the end-of-stream marker, provide a 3 factor integrity checking +with the "End Of Stream" marker, provide a 3 factor integrity checking which guarantees that the decompressed version of the data is identical to the original. This guards against corruption of the compressed data, and against undetected bugs in lzip (hopefully very unlikely). The @@ -159,9 +160,9 @@ file from that of the compressed file as follows: @item anyothername @tab becomes @tab anyothername.out @end multitable -(De)compressing a file is much like copying or moving it; therefore lzip +(De)compressing a file is much like copying or moving it. Therefore lzip preserves the access and modification dates, permissions, and, when -possible, ownership of the file just as @samp{cp -p} does. (If the user ID or +possible, ownership of the file just as @w{@samp{cp -p}} does. (If the user ID or the group ID can't be duplicated, the file permission bits S_ISUID and S_ISGID are cleared). @@ -299,10 +300,12 @@ and @samp{-S}. @samp{-c} has no effect when testing or listing. @item -d @itemx --decompress -Decompress the files specified. If a file does not exist or can't be -opened, lzip continues decompressing the rest of the files. If a file -fails to decompress, or is a terminal, lzip exits immediately without -decompressing the rest of the files. +Decompress the files specified. If a file does not exist, can't be opened, +or the destination file already exists and @samp{--force} has not been +specified, lzip continues decompressing the rest of the files and exits with +error status 1. If a file fails to decompress, or is a terminal, lzip exits +immediately with error status 2 without decompressing the rest of the files. +A terminal is considered an uncompressed file, and therefore invalid. @item -f @itemx --force @@ -327,10 +330,11 @@ size, the number of members in the file, and the amount of trailing data (if any) are also printed. With @samp{-vv}, the positions and sizes of each member in multimember files are also printed. -@samp{-lq} can be used to verify quickly (without decompressing) the -structural integrity of the files specified. (Use @samp{--test} to verify -the data integrity). @samp{-alq} additionally verifies that none of the -files specified contain trailing data. +If any file is damaged, does not exist, can't be opened, or is not regular, +the final exit status will be @w{> 0}. @samp{-lq} can be used to verify +quickly (without decompressing) the structural integrity of the files +specified. (Use @samp{--test} to verify the data integrity). @samp{-alq} +additionally verifies that none of the files specified contain trailing data. @item -m @var{bytes} @itemx --match-length=@var{bytes} @@ -473,9 +477,9 @@ Table of SI and binary prefixes (unit multipliers): @sp 1 Exit status: 0 for a normal exit, 1 for environmental problems (file not -found, invalid flags, I/O errors, etc), 2 to indicate a corrupt or -invalid input file, 3 for an internal consistency error (eg, bug) which -caused lzip to panic. +found, invalid flags, I/O errors, etc), 2 to indicate a corrupt or invalid +input file, 3 for an internal consistency error (e.g., bug) which caused +lzip to panic. @node Quality assurance @@ -629,11 +633,12 @@ and may limit the number of members or the total uncompressed size. @table @samp @item Accurate and robust error detection -The lzip format provides 3 factor integrity checking and the decompressors -report mismatches in each factor separately. This way if just one byte in -one factor fails but the other two factors match the data, it probably means -that the data are intact and the corruption just affects the mismatching -factor (CRC or data size) in the check sequence. +The lzip format provides 3 factor integrity checking, and the decompressors +report mismatches in each factor separately. This method detects most false +positives for corruption. If just one byte in one factor fails but the other +two factors match the data, it probably means that the data are intact and +the corruption just affects the mismatching factor (CRC, data size, or +member size) in the member trailer. @item Multiple implementations @@ -672,84 +677,6 @@ into the design of gzip. Both bzip2 and lzip are free from this flaw. @end table -@node File format -@chapter File format -@cindex file format - -Perfection is reached, not when there is no longer anything to add, but -when there is no longer anything to take away.@* ---- Antoine de Saint-Exupery - -@sp 1 -In the diagram below, a box like this: - -@verbatim -+---+ -| | <-- the vertical bars might be missing -+---+ -@end verbatim - -represents one byte; a box like this: - -@verbatim -+==============+ -| | -+==============+ -@end verbatim - -represents a variable number of bytes. - -@sp 1 -A lzip file consists of a series of "members" (compressed data sets). -The members simply appear one after another in the file, with no -additional information before, between, or after them. - -Each member has the following structure: - -@verbatim -+--+--+--+--+----+----+=============+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -| ID string | VN | DS | LZMA stream | CRC32 | Data size | Member size | -+--+--+--+--+----+----+=============+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -@end verbatim - -All multibyte values are stored in little endian order. - -@table @samp -@item ID string (the "magic" bytes) -A four byte string, identifying the lzip format, with the value "LZIP" -(0x4C, 0x5A, 0x49, 0x50). - -@item VN (version number, 1 byte) -Just in case something needs to be modified in the future. 1 for now. - -@anchor{coded-dict-size} -@item DS (coded dictionary size, 1 byte) -The dictionary size is calculated by taking a power of 2 (the base size) -and subtracting from it a fraction between 0/16 and 7/16 of the base size.@* -Bits 4-0 contain the base 2 logarithm of the base size (12 to 29).@* -Bits 7-5 contain the numerator of the fraction (0 to 7) to subtract -from the base size to obtain the dictionary size.@* -Example: 0xD3 = 2^19 - 6 * 2^15 = 512 KiB - 6 * 32 KiB = 320 KiB@* -Valid values for dictionary size range from 4 KiB to 512 MiB. - -@item LZMA stream -The LZMA stream, finished by an end of stream marker. Uses default values -for encoder properties. @xref{Stream format}, for a complete description. - -@item CRC32 (4 bytes) -Cyclic Redundancy Check (CRC) of the uncompressed original data. - -@item Data size (8 bytes) -Size of the uncompressed original data. - -@item Member size (8 bytes) -Total size of the member, including header and trailer. This field acts -as a distributed index, allows the verification of stream integrity, and -facilitates safe recovery of undamaged members from multimember files. - -@end table - - @node Algorithm @chapter Algorithm @cindex algorithm @@ -766,7 +693,7 @@ of finding coding sequences of minimum size than the one currently used by lzip could be developed, and the resulting sequence could also be coded using the LZMA coding scheme. -Lzip currently implements two variants of the LZMA algorithm; fast +Lzip currently implements two variants of the LZMA algorithm: fast (used by option @samp{-0}) and normal (used by all other compression levels). The high compression of LZMA comes from combining two basic, well-proven @@ -778,7 +705,7 @@ contexts according to what the bits are used for. Lzip is a two stage compressor. The first stage is a Lempel-Ziv coder, which reduces redundancy by translating chunks of data to their corresponding distance-length pairs. The second stage is a range encoder -that uses a different probability model for each type of data; +that uses a different probability model for each type of data: distances, lengths, literal bytes, etc. Here is how it works, step by step: @@ -825,32 +752,112 @@ encoding), Igor Pavlov (for putting all the above together in LZMA), and Julian Seward (for bzip2's CLI). +@node File format +@chapter File format +@cindex file format + +Perfection is reached, not when there is no longer anything to add, but +when there is no longer anything to take away.@* +--- Antoine de Saint-Exupery + +@sp 1 +In the diagram below, a box like this: + +@verbatim ++---+ +| | <-- the vertical bars might be missing ++---+ +@end verbatim + +represents one byte; a box like this: + +@verbatim ++==============+ +| | ++==============+ +@end verbatim + +represents a variable number of bytes. + +@sp 1 +A lzip file consists of a series of independent "members" (compressed data +sets). The members simply appear one after another in the file, with no +additional information before, between, or after them. Each member can +encode in compressed form up to @w{16 EiB - 1 byte} of uncompressed data. +The size of a multimember file is unlimited. + +Each member has the following structure: + +@verbatim ++--+--+--+--+----+----+=============+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| ID string | VN | DS | LZMA stream | CRC32 | Data size | Member size | ++--+--+--+--+----+----+=============+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +@end verbatim + +All multibyte values are stored in little endian order. + +@table @samp +@item ID string (the "magic" bytes) +A four byte string, identifying the lzip format, with the value "LZIP" +(0x4C, 0x5A, 0x49, 0x50). + +@item VN (version number, 1 byte) +Just in case something needs to be modified in the future. 1 for now. + +@anchor{coded-dict-size} +@item DS (coded dictionary size, 1 byte) +The dictionary size is calculated by taking a power of 2 (the base size) +and subtracting from it a fraction between 0/16 and 7/16 of the base size.@* +Bits 4-0 contain the base 2 logarithm of the base size (12 to 29).@* +Bits 7-5 contain the numerator of the fraction (0 to 7) to subtract +from the base size to obtain the dictionary size.@* +Example: 0xD3 = 2^19 - 6 * 2^15 = 512 KiB - 6 * 32 KiB = 320 KiB@* +Valid values for dictionary size range from 4 KiB to 512 MiB. + +@item LZMA stream +The LZMA stream, finished by an "End Of Stream" marker. Uses default values +for encoder properties. @xref{Stream format}, for a complete description. + +@item CRC32 (4 bytes) +Cyclic Redundancy Check (CRC) of the original uncompressed data. + +@item Data size (8 bytes) +Size of the original uncompressed data. + +@item Member size (8 bytes) +Total size of the member, including header and trailer. This field acts +as a distributed index, allows the verification of stream integrity, and +facilitates the safe recovery of undamaged members from multimember files. +Member size should be limited to @w{2 PiB} to prevent the data size field +from overflowing. + +@end table + + @node Stream format @chapter Format of the LZMA stream in lzip files @cindex format of the LZMA stream -Lzip uses a simplified form of the LZMA stream format chosen to maximize -safety and interoperability. - The LZMA algorithm has three parameters, called "special LZMA properties", to adjust it for some kinds of binary data. These -parameters are; @samp{literal_context_bits} (with a default value of 3), +parameters are: @samp{literal_context_bits} (with a default value of 3), @samp{literal_pos_state_bits} (with a default value of 0), and @samp{pos_state_bits} (with a default value of 2). As a general purpose compressor, lzip only uses the default values for these parameters. In particular @samp{literal_pos_state_bits} has been optimized away and does not even appear in the code. -Lzip finishes the LZMA stream with an "End Of Stream" (EOS) marker -(the distance-length pair 0xFFFFFFFFU, 2), which in conjunction with the +Lzip finishes the LZMA stream with an "End Of Stream" (EOS) marker (the +distance-length pair @w{0xFFFFFFFFU, 2}), which in conjunction with the @samp{member size} field in the member trailer allows the verification of -stream integrity. The LZMA stream in lzip files always has these two -features (default properties and EOS marker) and is referred to in this -document as LZMA-302eos. The EOS marker is the only marker allowed in -lzip files. +stream integrity. The EOS marker is the only marker allowed in lzip files. +The LZMA stream in lzip files always has these two features (default +properties and EOS marker) and is referred to in this document as +LZMA-302eos. This simplified form of the LZMA stream format has been chosen +to maximize interoperability and safety. The second stage of LZMA is a range encoder that uses a different -probability model for each type of symbol; distances, lengths, literal +probability model for each type of symbol: distances, lengths, literal bytes, etc. Range encoding conceptually encodes all the symbols of the message into one number. Unlike Huffman coding, which assigns to each symbol a bit-pattern and concatenates all the bit-patterns together, @@ -858,16 +865,16 @@ range encoding can compress one symbol to less than one bit. Therefore the compressed data produced by a range encoder can't be split in pieces that could be described individually. -It seems that the only way of describing the LZMA-302eos stream is -describing the algorithm that decodes it. And given the many details +It seems that the only way of describing the LZMA-302eos stream is to +describe the algorithm that decodes it. And given the many details about the range decoder that need to be described accurately, the source -code of a real decoder seems the only appropriate reference to use. +code of a real decompressor seems the only appropriate reference to use. What follows is a description of the decoding algorithm for LZMA-302eos streams using as reference the source code of "lzd", an educational -decompressor for lzip files which can be downloaded from the lzip -download directory. The source code of lzd is included in appendix A. -@xref{Reference source code}. +decompressor for lzip files which can be downloaded from the lzip download +directory. Lzd is written in C++11 and its source code is included in +appendix A. @xref{Reference source code}. @sp 1 @section What is coded @@ -905,7 +912,7 @@ Lengths (the @samp{len} in the table above) are coded as follows: @end multitable @sp 1 -The coding of distances is a little more complicated, so I'll begin +The coding of distances is a little more complicated, so I'll begin by explaining a simpler version of the encoding. Imagine you need to encode a number from 0 to @w{2^32 - 1}, and you want to @@ -915,7 +922,7 @@ which you may find by making a bit scan from the left (from the MSB). A position of 0 means that the number is 0 (no bit is set), 1 means the LSB is the first bit set (the number is 1), and 32 means the MSB is set (i.e., the number is @w{>= 0x80000000}). Then, if the position is @w{>= 2}, you encode -the remaining @w{position - 1} bits. Let's call these bits "direct_bits" +the remaining @w{position - 1} bits. Let's call these bits "direct bits" because they are coded directly by value instead of indirectly by position. The inconvenient of this simple method is that it needs 6 bits to encode the @@ -975,10 +982,10 @@ of 3. The resulting value is in the range 0 to 3. @end table -In the following table, @samp{!literal} is any sequence except a literal -byte. @samp{rep} is any one of @samp{rep0}, @samp{rep1}, @samp{rep2}, or -@samp{rep3}. The types of previous sequences corresponding to each state -are: +The types of previous sequences corresponding to each state are shown in the +following table. @samp{!literal} is any sequence except a literal byte. +@samp{rep} is any one of @samp{rep0}, @samp{rep1}, @samp{rep2}, or +@samp{rep3}. The last type in each line is the most recent. @multitable {State} {rep or (!literal, shortrep), literal, literal} @headitem State @tab Types of previous sequences @@ -1053,9 +1060,9 @@ The LZMA stream is consumed one byte at a time by the range decoder. variable number of decoded bits, depending on how well these bits agree with their context. (See @samp{decode_bit} in the source). -The range decoder state consists of two unsigned 32-bit variables; +The range decoder state consists of two unsigned 32-bit variables: @samp{range} (representing the most significant part of the range size -not yet decoded), and @samp{code} (representing the current point within +not yet decoded) and @samp{code} (representing the current point within @samp{range}). @samp{range} is initialized to @w{2^32 - 1}, and @samp{code} is initialized to 0. @@ -1069,14 +1076,15 @@ the source). After decoding the member header and obtaining the dictionary size, the range decoder is initialized and then the LZMA decoder enters a loop -(See @samp{decode_member} in the source) where it invokes the range +(see @samp{decode_member} in the source) where it invokes the range decoder with the appropriate contexts to decode the different coding sequences (matches, repeated matches, and literal bytes), until the "End Of Stream" marker is decoded. Once the "End Of Stream" marker has been decoded, the decompressor reads and decodes the member trailer, and verifies that the three integrity factors -(CRC, data size, and member size) match those calculated by the LZMA decoder. +stored there (CRC, data size, and member size) match those computed from the +data. @node Trailing data @@ -1165,7 +1173,7 @@ lzip -v file @sp 1 @noindent -Example 3: Like example 1 but the created @samp{file.lz} is multimember with +Example 3: Like example 2 but the created @samp{file.lz} is multimember with a member size of @w{1 MiB}. The compression ratio is not shown. @example @@ -1191,20 +1199,9 @@ lzip -tv file.lz @end example @sp 1 -@noindent -Example 6: Compress a whole device in /dev/sdc and send the output to -@samp{file.lz}. - -@example - lzip -c /dev/sdc > file.lz -or - lzip /dev/sdc -o file.lz -@end example - -@sp 1 @anchor{concat-example} @noindent -Example 7: The right way of concatenating the decompressed output of two or +Example 6: The right way of concatenating the decompressed output of two or more compressed files. @xref{Trailing data}. @example @@ -1216,7 +1213,7 @@ Do this instead @sp 1 @noindent -Example 8: Decompress @samp{file.lz} partially until @w{10 KiB} of +Example 7: Decompress @samp{file.lz} partially until @w{10 KiB} of decompressed data are produced. @example @@ -1225,7 +1222,7 @@ lzip -cd file.lz | dd bs=1024 count=10 @sp 1 @noindent -Example 9: Decompress @samp{file.lz} partially from decompressed byte at +Example 8: Decompress @samp{file.lz} partially from decompressed byte at offset 10000 to decompressed byte at offset 14999 (5000 bytes are produced). @example @@ -1234,6 +1231,17 @@ lzip -cd file.lz | dd bs=1000 skip=10 count=5 @sp 1 @noindent +Example 9: Compress a whole device in /dev/sdc and send the output to +@samp{file.lz}. + +@example + lzip -c /dev/sdc > file.lz +or + lzip /dev/sdc -o file.lz +@end example + +@sp 1 +@noindent Example 10: Create a multivolume compressed tar archive with a volume size of @w{1440 KiB}. @@ -1281,7 +1289,7 @@ find by running @w{@samp{lzip --version}}. @verbatim /* Lzd - Educational decompressor for the lzip format - Copyright (C) 2013-2021 Antonio Diaz Diaz. + Copyright (C) 2013-2022 Antonio Diaz Diaz. This program is free software. Redistribution and use in source and binary forms, with or without modification, are permitted provided @@ -1311,7 +1319,7 @@ find by running @w{@samp{lzip --version}}. #include <cstring> #include <stdint.h> #include <unistd.h> -#if defined(__MSVCRT__) || defined(__OS2__) || defined(__DJGPP__) +#if defined __MSVCRT__ || defined __OS2__ || defined __DJGPP__ #include <fcntl.h> #include <io.h> #endif @@ -1701,7 +1709,7 @@ int main( const int argc, const char * const argv[] ) "See the lzip manual for an explanation of the code.\n" "\nUsage: %s [-d] < file.lz > file\n" "Lzd decompresses from standard input to standard output.\n" - "\nCopyright (C) 2021 Antonio Diaz Diaz.\n" + "\nCopyright (C) 2022 Antonio Diaz Diaz.\n" "License 2-clause BSD.\n" "This is free software: you are free to change and redistribute it.\n" "There is NO WARRANTY, to the extent permitted by law.\n" @@ -1711,7 +1719,7 @@ int main( const int argc, const char * const argv[] ) return 0; } -#if defined(__MSVCRT__) || defined(__OS2__) || defined(__DJGPP__) +#if defined __MSVCRT__ || defined __OS2__ || defined __DJGPP__ setmode( STDIN_FILENO, O_BINARY ); setmode( STDOUT_FILENO, O_BINARY ); #endif @@ -1,5 +1,5 @@ /* Lzip - LZMA lossless data compressor - Copyright (C) 2008-2021 Antonio Diaz Diaz. + Copyright (C) 2008-2022 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -167,7 +167,7 @@ void LZ_encoder::update_distance_prices() } -/* Returns the number of bytes advanced (ahead). +/* Return the number of bytes advanced (ahead). trials[0]..trials[ahead-1] contain the steps to encode. ( trials[0].dis4 == -1 ) means literal. A match/rep longer or equal than match_len_limit finishes the sequence. @@ -1,5 +1,5 @@ /* Lzip - LZMA lossless data compressor - Copyright (C) 2008-2021 Antonio Diaz Diaz. + Copyright (C) 2008-2022 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/encoder_base.cc b/encoder_base.cc index eb23532..a239d1f 100644 --- a/encoder_base.cc +++ b/encoder_base.cc @@ -1,5 +1,5 @@ /* Lzip - LZMA lossless data compressor - Copyright (C) 2008-2021 Antonio Diaz Diaz. + Copyright (C) 2008-2022 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -104,8 +104,7 @@ Matchfinder_base::Matchfinder_base( const int before_size_, pos_limit = buffer_size; if( !at_stream_end ) pos_limit -= after_size; unsigned size = 1 << std::max( 16, real_bits( dictionary_size - 1 ) - 2 ); - if( dictionary_size > 1 << 26 ) // 64 MiB - size >>= 1; + if( dictionary_size > 1 << 26 ) size >>= 1; // 64 MiB key4_mask = size - 1; // increases with dictionary size size += num_prev_positions23; num_prev_positions = size; @@ -133,8 +132,7 @@ void Matchfinder_base::reset() { dictionary_size = std::max( (int)min_dictionary_size, stream_pos ); int size = 1 << std::max( 16, real_bits( dictionary_size - 1 ) - 2 ); - if( dictionary_size > 1 << 26 ) // 64 MiB - size >>= 1; + if( dictionary_size > 1 << 26 ) size >>= 1; // 64 MiB key4_mask = size - 1; size += num_prev_positions23; num_prev_positions = size; diff --git a/encoder_base.h b/encoder_base.h index 9d45565..b3dd9e6 100644 --- a/encoder_base.h +++ b/encoder_base.h @@ -1,5 +1,5 @@ /* Lzip - LZMA lossless data compressor - Copyright (C) 2008-2021 Antonio Diaz Diaz. + Copyright (C) 2008-2022 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -302,7 +302,8 @@ public: if( !bit ) { range = bound; - bm.probability += (bit_model_total - bm.probability) >> bit_model_move_bits; + bm.probability += + ( bit_model_total - bm.probability ) >> bit_model_move_bits; } else { diff --git a/fast_encoder.cc b/fast_encoder.cc index f4598b8..06d7a05 100644 --- a/fast_encoder.cc +++ b/fast_encoder.cc @@ -1,5 +1,5 @@ /* Lzip - LZMA lossless data compressor - Copyright (C) 2008-2021 Antonio Diaz Diaz. + Copyright (C) 2008-2022 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/fast_encoder.h b/fast_encoder.h index 5f8aa38..c41f9e4 100644 --- a/fast_encoder.h +++ b/fast_encoder.h @@ -1,5 +1,5 @@ /* Lzip - LZMA lossless data compressor - Copyright (C) 2008-2021 Antonio Diaz Diaz. + Copyright (C) 2008-2022 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -1,5 +1,5 @@ /* Lzip - LZMA lossless data compressor - Copyright (C) 2008-2021 Antonio Diaz Diaz. + Copyright (C) 2008-2022 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -73,38 +73,35 @@ int list_files( const std::vector< std::string > & filenames, set_retval( retval, lzip_index.retval() ); continue; } - if( verbosity >= 0 ) + if( verbosity < 0 ) continue; + const unsigned long long udata_size = lzip_index.udata_size(); + const unsigned long long cdata_size = lzip_index.cdata_size(); + total_comp += cdata_size; total_uncomp += udata_size; ++files; + const long members = lzip_index.members(); + if( first_post ) { - const unsigned long long udata_size = lzip_index.udata_size(); - const unsigned long long cdata_size = lzip_index.cdata_size(); - total_comp += cdata_size; total_uncomp += udata_size; ++files; - const long members = lzip_index.members(); - if( first_post ) - { - first_post = false; - if( verbosity >= 1 ) std::fputs( " dict memb trail ", stdout ); - std::fputs( " uncompressed compressed saved name\n", stdout ); - } - if( verbosity >= 1 ) - std::printf( "%s %5ld %6lld ", - format_ds( lzip_index.dictionary_size() ), members, - lzip_index.file_size() - cdata_size ); - list_line( udata_size, cdata_size, input_filename ); + first_post = false; + if( verbosity >= 1 ) std::fputs( " dict memb trail ", stdout ); + std::fputs( " uncompressed compressed saved name\n", stdout ); + } + if( verbosity >= 1 ) + std::printf( "%s %5ld %6lld ", format_ds( lzip_index.dictionary_size() ), + members, lzip_index.file_size() - cdata_size ); + list_line( udata_size, cdata_size, input_filename ); - if( verbosity >= 2 && members > 1 ) + if( verbosity >= 2 && members > 1 ) + { + std::fputs( " member data_pos data_size member_pos member_size\n", stdout ); + for( long i = 0; i < members; ++i ) { - std::fputs( " member data_pos data_size member_pos member_size\n", stdout ); - for( long i = 0; i < members; ++i ) - { - const Block & db = lzip_index.dblock( i ); - const Block & mb = lzip_index.mblock( i ); - std::printf( "%6ld %14llu %14llu %14llu %14llu\n", - i + 1, db.pos(), db.size(), mb.pos(), mb.size() ); - } - first_post = true; // reprint heading after list of members + const Block & db = lzip_index.dblock( i ); + const Block & mb = lzip_index.mblock( i ); + std::printf( "%6ld %14llu %14llu %14llu %14llu\n", + i + 1, db.pos(), db.size(), mb.pos(), mb.size() ); } - std::fflush( stdout ); + first_post = true; // reprint heading after list of members } + std::fflush( stdout ); } if( verbosity >= 0 && files > 1 ) { @@ -1,5 +1,5 @@ /* Lzip - LZMA lossless data compressor - Copyright (C) 2008-2021 Antonio Diaz Diaz. + Copyright (C) 2008-2022 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -30,11 +30,7 @@ public: static const int next[states] = { 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 4, 5 }; st = next[st]; } - bool is_char_set_char() - { - if( st < 7 ) { st -= ( st < 4 ) ? st : 3; return true; } - else { st -= ( st < 10 ) ? 3 : 6; return false; } - } + bool is_char_set_char() { set_char(); return st < 4; } void set_char_rep() { st = 8; } void set_match() { st = ( st < 7 ) ? 7 : 10; } void set_rep() { st = ( st < 7 ) ? 8 : 11; } @@ -176,6 +172,7 @@ public: void update_byte( uint32_t & crc, const uint8_t byte ) const { crc = data[(crc^byte)&0xFF] ^ ( crc >> 8 ); } + // about as fast as it is possible without messing with endianness void update_buf( uint32_t & crc, const uint8_t * const buffer, const int size ) const { @@ -221,6 +218,7 @@ struct Lzip_header if( data[i] != lzip_magic[i] ) return false; return ( sz > 0 ); } + bool verify_corrupt() const // detect corrupt header { int matches = 0; diff --git a/lzip_index.cc b/lzip_index.cc index 693ef20..b64fb36 100644 --- a/lzip_index.cc +++ b/lzip_index.cc @@ -1,5 +1,5 @@ /* Lzip - LZMA lossless data compressor - Copyright (C) 2008-2021 Antonio Diaz Diaz. + Copyright (C) 2008-2022 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/lzip_index.h b/lzip_index.h index 2ee13ad..442512d 100644 --- a/lzip_index.h +++ b/lzip_index.h @@ -1,5 +1,5 @@ /* Lzip - LZMA lossless data compressor - Copyright (C) 2008-2021 Antonio Diaz Diaz. + Copyright (C) 2008-2022 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -1,5 +1,5 @@ /* Lzip - LZMA lossless data compressor - Copyright (C) 2008-2021 Antonio Diaz Diaz. + Copyright (C) 2008-2022 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,7 +18,7 @@ Exit status: 0 for a normal exit, 1 for environmental problems (file not found, invalid flags, I/O errors, etc), 2 to indicate a corrupt or invalid input file, 3 for an internal consistency error - (eg, bug) which caused lzip to panic. + (e.g., bug) which caused lzip to panic. */ #define _FILE_OFFSET_BITS 64 @@ -39,9 +39,9 @@ #include <unistd.h> #include <utime.h> #include <sys/stat.h> -#if defined(__MSVCRT__) || defined(__OS2__) || defined(__DJGPP__) +#if defined __MSVCRT__ || defined __OS2__ || defined __DJGPP__ #include <io.h> -#if defined(__MSVCRT__) +#if defined __MSVCRT__ #define fchmod(x,y) 0 #define fchown(x,y,z) 0 #define strtoull std::strtoul @@ -54,7 +54,7 @@ #define S_IWOTH 0 #endif #endif -#if defined(__DJGPP__) +#if defined __DJGPP__ #define S_ISSOCK(x) 0 #define S_ISVTX 0 #endif @@ -75,12 +75,17 @@ #error "Environments where CHAR_BIT != 8 are not supported." #endif +#if ( defined SIZE_MAX && SIZE_MAX < UINT_MAX ) || \ + ( defined SSIZE_MAX && SSIZE_MAX < INT_MAX ) +#error "Environments where 'size_t' is narrower than 'int' are not supported." +#endif + int verbosity = 0; namespace { const char * const program_name = "lzip"; -const char * const program_year = "2021"; +const char * const program_year = "2022"; const char * invocation_name = program_name; // default value const struct { const char * from; const char * to; } known_extensions[] = { @@ -107,13 +112,14 @@ void show_help() { std::printf( "Lzip is a lossless data compressor with a user interface similar to the one\n" "of gzip or bzip2. Lzip uses a simplified form of the 'Lempel-Ziv-Markov\n" - "chain-Algorithm' (LZMA) stream format, chosen to maximize safety and\n" - "interoperability. Lzip can compress about as fast as gzip (lzip -0) or\n" - "compress most files more than bzip2 (lzip -9). Decompression speed is\n" - "intermediate between gzip and bzip2. Lzip is better than gzip and bzip2 from\n" - "a data recovery perspective. Lzip has been designed, written, and tested\n" - "with great care to replace gzip and bzip2 as the standard general-purpose\n" - "compressed format for unix-like systems.\n" + "chain-Algorithm' (LZMA) stream format and provides a 3 factor integrity\n" + "checking to maximize interoperability and optimize safety. Lzip can compress\n" + "about as fast as gzip (lzip -0) or compress most files more than bzip2\n" + "(lzip -9). Decompression speed is intermediate between gzip and bzip2.\n" + "Lzip is better than gzip and bzip2 from a data recovery perspective. Lzip\n" + "has been designed, written, and tested with great care to replace gzip and\n" + "bzip2 as the standard general-purpose compressed format for unix-like\n" + "systems.\n" "\nUsage: %s [options] [files]\n", invocation_name ); std::printf( "\nOptions:\n" " -h, --help display this help and exit\n" @@ -151,7 +157,7 @@ void show_help() "'tar -xf foo.tar.lz' or 'lzip -cd foo.tar.lz | tar -xf -'.\n" "\nExit status: 0 for a normal exit, 1 for environmental problems (file\n" "not found, invalid flags, I/O errors, etc), 2 to indicate a corrupt or\n" - "invalid input file, 3 for an internal consistency error (eg, bug) which\n" + "invalid input file, 3 for an internal consistency error (e.g., bug) which\n" "caused lzip to panic.\n" "\nThe ideas embodied in lzip are due to (at least) the following people:\n" "Abraham Lempel and Jacob Ziv (for the LZ algorithm), Andrey Markov (for the\n" @@ -176,16 +182,14 @@ void show_version() void Pretty_print::operator()( const char * const msg ) const { - if( verbosity >= 0 ) + if( verbosity < 0 ) return; + if( first_post ) { - if( first_post ) - { - first_post = false; - std::fputs( padded_name.c_str(), stderr ); - if( !msg ) std::fflush( stderr ); - } - if( msg ) std::fprintf( stderr, "%s\n", msg ); + first_post = false; + std::fputs( padded_name.c_str(), stderr ); + if( !msg ) std::fflush( stderr ); } + if( msg ) std::fprintf( stderr, "%s\n", msg ); } @@ -224,16 +228,53 @@ void show_header( const unsigned dictionary_size ) namespace { -unsigned long long getnum( const char * const ptr, +// separate large numbers >= 100_000 in groups of 3 digits using '_' +const char * format_num3( unsigned long long num ) + { + const char * const si_prefix = "kMGTPEZY"; + const char * const binary_prefix = "KMGTPEZY"; + enum { buffers = 8, bufsize = 4 * sizeof (long long) }; + static char buffer[buffers][bufsize]; // circle of static buffers for printf + static int current = 0; + + char * const buf = buffer[current++]; current %= buffers; + char * p = buf + bufsize - 1; // fill the buffer backwards + *p = 0; // terminator + if( num > 1024 ) + { + char prefix = 0; // try binary first, then si + for( int i = 0; i < 8 && num >= 1024 && num % 1024 == 0; ++i ) + { num /= 1024; prefix = binary_prefix[i]; } + if( prefix ) *(--p) = 'i'; + else + for( int i = 0; i < 8 && num >= 1000 && num % 1000 == 0; ++i ) + { num /= 1000; prefix = si_prefix[i]; } + if( prefix ) *(--p) = prefix; + } + const bool split = num >= 100000; + + for( int i = 0; ; ) + { + *(--p) = num % 10 + '0'; num /= 10; if( num == 0 ) break; + if( split && ++i >= 3 ) { i = 0; *(--p) = '_'; } + } + return p; + } + + +unsigned long long getnum( const char * const arg, + const char * const option_name, const unsigned long long llimit, const unsigned long long ulimit ) { char * tail; errno = 0; - unsigned long long result = strtoull( ptr, &tail, 0 ); - if( tail == ptr ) + unsigned long long result = strtoull( arg, &tail, 0 ); + if( tail == arg ) { - show_error( "Bad or missing numerical argument.", 0, true ); + if( verbosity >= 0 ) + std::fprintf( stderr, "%s: Bad or missing numerical argument in " + "option '%s'.\n", program_name, option_name ); std::exit( 1 ); } @@ -255,7 +296,9 @@ unsigned long long getnum( const char * const ptr, } if( exponent <= 0 ) { - show_error( "Bad multiplier in numerical argument.", 0, true ); + if( verbosity >= 0 ) + std::fprintf( stderr, "%s: Bad multiplier in numerical argument of " + "option '%s'.\n", program_name, option_name ); std::exit( 1 ); } for( int i = 0; i < exponent; ++i ) @@ -267,21 +310,24 @@ unsigned long long getnum( const char * const ptr, if( !errno && ( result < llimit || result > ulimit ) ) errno = ERANGE; if( errno ) { - show_error( "Numerical argument out of limits." ); + if( verbosity >= 0 ) + std::fprintf( stderr, "%s: Numerical argument out of limits [%s,%s] " + "in option '%s'.\n", program_name, format_num3( llimit ), + format_num3( ulimit ), option_name ); std::exit( 1 ); } return result; } -int get_dict_size( const char * const arg ) +int get_dict_size( const char * const arg, const char * const option_name ) { char * tail; const long bits = std::strtol( arg, &tail, 0 ); if( bits >= min_dictionary_bits && bits <= max_dictionary_bits && *tail == 0 ) return 1 << bits; - return getnum( arg, min_dictionary_size, max_dictionary_size ); + return getnum( arg, option_name, min_dictionary_size, max_dictionary_size ); } @@ -447,7 +493,7 @@ bool check_tty_in( const char * const input_filename, const int infd, isatty( infd ) ) // for example /dev/tty { show_file_error( input_filename, "I won't read compressed data from a terminal." ); - close( infd ); set_retval( retval, 1 ); + close( infd ); set_retval( retval, 2 ); if( program_mode != m_test ) cleanup_and_fail( retval ); return false; } return true; @@ -619,9 +665,10 @@ int decompress( const unsigned long long cfile_size, const int infd, const Pretty_print & pp, const bool ignore_trailing, const bool loose_trailing, const bool testing ) { - int retval = 0; unsigned long long partial_file_pos = 0; Range_decoder rdec( infd ); + int retval = 0; + for( bool first_member = true; ; first_member = false ) { Lzip_header header; @@ -800,7 +847,6 @@ int main( const int argc, const char * const argv[] ) unsigned long long member_size = max_member_size; unsigned long long volume_size = 0; std::string default_output_filename; - std::vector< std::string > filenames; Mode program_mode = m_compress; bool force = false; bool ignore_trailing = true; @@ -854,6 +900,7 @@ int main( const int argc, const char * const argv[] ) { const int code = parser.code( argind ); if( !code ) break; // no more options + const char * const pn = parser.parsed_name( argind ).c_str(); const std::string & sarg = parser.argument( argind ); const char * const arg = sarg.c_str(); switch( code ) @@ -863,7 +910,7 @@ int main( const int argc, const char * const argv[] ) zero = ( code == '0' ); encoder_options = option_mapping[code-'0']; break; case 'a': ignore_trailing = false; break; - case 'b': member_size = getnum( arg, 100000, max_member_size ); break; + case 'b': member_size = getnum( arg, pn, 100000, max_member_size ); break; case 'c': to_stdout = true; break; case 'd': set_mode( program_mode, m_decompress ); break; case 'f': force = true; break; @@ -872,15 +919,15 @@ int main( const int argc, const char * const argv[] ) case 'k': keep_input_files = true; break; case 'l': set_mode( program_mode, m_list ); break; case 'm': encoder_options.match_len_limit = - getnum( arg, min_match_len_limit, max_match_len ); + getnum( arg, pn, min_match_len_limit, max_match_len ); zero = false; break; case 'n': break; case 'o': if( sarg == "-" ) to_stdout = true; else { default_output_filename = sarg; } break; case 'q': verbosity = -1; break; - case 's': encoder_options.dictionary_size = get_dict_size( arg ); + case 's': encoder_options.dictionary_size = get_dict_size( arg, pn ); zero = false; break; - case 'S': volume_size = getnum( arg, 100000, max_volume_size ); break; + case 'S': volume_size = getnum( arg, pn, 100000, max_volume_size ); break; case 't': set_mode( program_mode, m_test ); break; case 'v': if( verbosity < 4 ) ++verbosity; break; case 'V': show_version(); return 0; @@ -889,11 +936,12 @@ int main( const int argc, const char * const argv[] ) } } // end process options -#if defined(__MSVCRT__) || defined(__OS2__) || defined(__DJGPP__) +#if defined __MSVCRT__ || defined __OS2__ || defined __DJGPP__ setmode( STDIN_FILENO, O_BINARY ); setmode( STDOUT_FILENO, O_BINARY ); #endif + std::vector< std::string > filenames; bool filenames_given = false; for( ; argind < parser.arguments(); ++argind ) { diff --git a/testsuite/check.sh b/testsuite/check.sh index 3b887b9..916ed4e 100755 --- a/testsuite/check.sh +++ b/testsuite/check.sh @@ -1,6 +1,6 @@ #! /bin/sh # check script for Lzip - LZMA lossless data compressor -# Copyright (C) 2008-2021 Antonio Diaz Diaz. +# Copyright (C) 2008-2022 Antonio Diaz Diaz. # # This script is free software: you have unlimited permission # to copy, distribute, and modify it. @@ -100,6 +100,7 @@ done printf "LZIP\001-.............................." | "${LZIP}" -t 2> /dev/null printf "LZIP\002-.............................." | "${LZIP}" -t 2> /dev/null printf "LZIP\001+.............................." | "${LZIP}" -t 2> /dev/null +rm -f out || framework_failure printf "\ntesting decompression..." @@ -123,17 +124,22 @@ lines=$("${LZIP}" -tvv "${in_em}" 2>&1 | wc -l) || test_failed $LINENO lines=$("${LZIP}" -lvv "${in_em}" | wc -l) || test_failed $LINENO [ "${lines}" -eq 11 ] || test_failed $LINENO "${lines}" +"${LZIP}" -cd "${fox_lz}" > fox || test_failed $LINENO cat "${in_lz}" > copy.lz || framework_failure "${LZIP}" -dk copy.lz || test_failed $LINENO cmp in copy || test_failed $LINENO -printf "to be overwritten" > copy || framework_failure -"${LZIP}" -d copy.lz 2> /dev/null +cat fox > copy || framework_failure +cat "${in_lz}" > out.lz || framework_failure +rm -f out || framework_failure +"${LZIP}" -d copy.lz out.lz 2> /dev/null # skip copy, decompress out [ $? = 1 ] || test_failed $LINENO +cmp fox copy || test_failed $LINENO +cmp in out || test_failed $LINENO "${LZIP}" -df copy.lz || test_failed $LINENO [ ! -e copy.lz ] || test_failed $LINENO cmp in copy || test_failed $LINENO +rm -f copy out || framework_failure -rm -f copy || framework_failure cat "${in_lz}" > copy.lz || framework_failure "${LZIP}" -d -S100k copy.lz || test_failed $LINENO # ignore -S [ ! -e copy.lz ] || test_failed $LINENO @@ -167,7 +173,7 @@ rm -f copy anyothername.out || framework_failure [ $? = 1 ] || test_failed $LINENO "${LZIP}" -cdq in "${in_lz}" > copy [ $? = 2 ] || test_failed $LINENO -cat copy in | cmp in - || test_failed $LINENO +cat copy in | cmp in - || test_failed $LINENO # copy must be empty "${LZIP}" -cdq nx_file.lz "${in_lz}" > copy [ $? = 1 ] || test_failed $LINENO cmp in copy || test_failed $LINENO @@ -375,7 +381,6 @@ for i in fox_v2.lz fox_s11.lz fox_de20.lz \ [ $? = 2 ] || test_failed $LINENO $i done -"${LZIP}" -cd "${fox_lz}" > fox || test_failed $LINENO for i in fox_bcrc.lz fox_crc0.lz fox_das46.lz fox_mes81.lz ; do "${LZIP}" -cdq "${testdir}"/$i > out [ $? = 2 ] || test_failed $LINENO $i |