"Fossies" - the Fresh Open Source Software Archive

Member "lzip-1.22-rc2/main.cc" (9 Jun 2020, 36720 Bytes) of package /linux/misc/lzip-1.22-rc2.tar.lz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "main.cc" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 1.22-rc1_vs_1.22-rc2.

    1 /* Lzip - LZMA lossless data compressor
    2    Copyright (C) 2008-2020 Antonio Diaz Diaz.
    3 
    4    This program is free software: you can redistribute it and/or modify
    5    it under the terms of the GNU General Public License as published by
    6    the Free Software Foundation, either version 2 of the License, or
    7    (at your option) any later version.
    8 
    9    This program is distributed in the hope that it will be useful,
   10    but WITHOUT ANY WARRANTY; without even the implied warranty of
   11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   12    GNU General Public License for more details.
   13 
   14    You should have received a copy of the GNU General Public License
   15    along with this program.  If not, see <http://www.gnu.org/licenses/>.
   16 */
   17 /*
   18    Exit status: 0 for a normal exit, 1 for environmental problems
   19    (file not found, invalid flags, I/O errors, etc), 2 to indicate a
   20    corrupt or invalid input file, 3 for an internal consistency error
   21    (eg, bug) which caused lzip to panic.
   22 */
   23 
   24 #define _FILE_OFFSET_BITS 64
   25 
   26 #include <algorithm>
   27 #include <cctype>
   28 #include <cerrno>
   29 #include <climits>
   30 #include <csignal>
   31 #include <cstdio>
   32 #include <cstdlib>
   33 #include <cstring>
   34 #include <new>
   35 #include <string>
   36 #include <vector>
   37 #include <fcntl.h>
   38 #include <stdint.h>
   39 #include <unistd.h>
   40 #include <utime.h>
   41 #include <sys/stat.h>
   42 #if defined(__MSVCRT__) || defined(__OS2__) || defined(__DJGPP__)
   43 #include <io.h>
   44 #if defined(__MSVCRT__)
   45 #define fchmod(x,y) 0
   46 #define fchown(x,y,z) 0
   47 #define strtoull std::strtoul
   48 #define SIGHUP SIGTERM
   49 #define S_ISSOCK(x) 0
   50 #ifndef S_IRGRP
   51 #define S_IRGRP 0
   52 #define S_IWGRP 0
   53 #define S_IROTH 0
   54 #define S_IWOTH 0
   55 #endif
   56 #endif
   57 #if defined(__DJGPP__)
   58 #define S_ISSOCK(x) 0
   59 #define S_ISVTX 0
   60 #endif
   61 #endif
   62 
   63 #include "arg_parser.h"
   64 #include "lzip.h"
   65 #include "decoder.h"
   66 #include "encoder_base.h"
   67 #include "encoder.h"
   68 #include "fast_encoder.h"
   69 
   70 #ifndef O_BINARY
   71 #define O_BINARY 0
   72 #endif
   73 
   74 #if CHAR_BIT != 8
   75 #error "Environments where CHAR_BIT != 8 are not supported."
   76 #endif
   77 
   78 int verbosity = 0;
   79 
   80 namespace {
   81 
   82 const char * const program_name = "lzip";
   83 const char * const program_year = "2020";
   84 const char * invocation_name = program_name;        // default value
   85 
   86 const struct { const char * from; const char * to; } known_extensions[] = {
   87   { ".lz",  ""     },
   88   { ".tlz", ".tar" },
   89   { 0,      0      } };
   90 
   91 struct Lzma_options
   92   {
   93   int dictionary_size;      // 4 KiB .. 512 MiB
   94   int match_len_limit;      // 5 .. 273
   95   };
   96 
   97 enum Mode { m_compress, m_decompress, m_list, m_test };
   98 
   99 /* Variables used in signal handler context.
  100    They are not declared volatile because the handler never returns. */
  101 std::string output_filename;
  102 int outfd = -1;
  103 bool delete_output_on_interrupt = false;
  104 
  105 
  106 void show_help()
  107   {
  108   std::printf( "Lzip is a lossless data compressor with a user interface similar to the one\n"
  109                "of gzip or bzip2. Lzip uses a simplified form of the 'Lempel-Ziv-Markov\n"
  110                "chain-Algorithm' (LZMA) stream format, chosen to maximize safety and\n"
  111                "interoperability. Lzip can compress about as fast as gzip (lzip -0) or\n"
  112                "compress most files more than bzip2 (lzip -9). Decompression speed is\n"
  113                "intermediate between gzip and bzip2. Lzip is better than gzip and bzip2 from\n"
  114                "a data recovery perspective. Lzip has been designed, written, and tested\n"
  115                "with great care to replace gzip and bzip2 as the standard general-purpose\n"
  116                "compressed format for unix-like systems.\n"
  117                "\nUsage: %s [options] [files]\n", invocation_name );
  118   std::printf( "\nOptions:\n"
  119                "  -h, --help                     display this help and exit\n"
  120                "  -V, --version                  output version information and exit\n"
  121                "  -a, --trailing-error           exit with error status if trailing data\n"
  122                "  -b, --member-size=<bytes>      set member size limit in bytes\n"
  123                "  -c, --stdout                   write to standard output, keep input files\n"
  124                "  -d, --decompress               decompress\n"
  125                "  -f, --force                    overwrite existing output files\n"
  126                "  -F, --recompress               force re-compression of compressed files\n"
  127                "  -k, --keep                     keep (don't delete) input files\n"
  128                "  -l, --list                     print (un)compressed file sizes\n"
  129                "  -m, --match-length=<bytes>     set match length limit in bytes [36]\n"
  130                "  -o, --output=<file>            write to <file>[.lz], keep input files\n"
  131                "  -q, --quiet                    suppress all messages\n"
  132                "  -s, --dictionary-size=<bytes>  set dictionary size limit in bytes [8 MiB]\n"
  133                "  -S, --volume-size=<bytes>      set volume size limit in bytes\n"
  134                "  -t, --test                     test compressed file integrity\n"
  135                "  -v, --verbose                  be verbose (a 2nd -v gives more)\n"
  136                "  -0 .. -9                       set compression level [default 6]\n"
  137                "      --fast                     alias for -0\n"
  138                "      --best                     alias for -9\n"
  139                "      --loose-trailing           allow trailing data seeming corrupt header\n"
  140                "\nIf no file names are given, or if a file is '-', lzip compresses or\n"
  141                "decompresses from standard input to standard output.\n"
  142                "Numbers may be followed by a multiplier: k = kB = 10^3 = 1000,\n"
  143                "Ki = KiB = 2^10 = 1024, M = 10^6, Mi = 2^20, G = 10^9, Gi = 2^30, etc...\n"
  144                "Dictionary sizes 12 to 29 are interpreted as powers of two, meaning 2^12\n"
  145                "to 2^29 bytes.\n"
  146                "\nThe bidimensional parameter space of LZMA can't be mapped to a linear\n"
  147                "scale optimal for all files. If your files are large, very repetitive,\n"
  148                "etc, you may need to use the options --dictionary-size and --match-length\n"
  149                "directly to achieve optimal performance.\n"
  150                "\nTo extract all the files from archive 'foo.tar.lz', use the commands\n"
  151                "'tar -xf foo.tar.lz' or 'lzip -cd foo.tar.lz | tar -xf -'.\n"
  152                "\nExit status: 0 for a normal exit, 1 for environmental problems (file\n"
  153                "not found, invalid flags, I/O errors, etc), 2 to indicate a corrupt or\n"
  154                "invalid input file, 3 for an internal consistency error (eg, bug) which\n"
  155                "caused lzip to panic.\n"
  156                "\nThe ideas embodied in lzip are due to (at least) the following people:\n"
  157                "Abraham Lempel and Jacob Ziv (for the LZ algorithm), Andrey Markov (for the\n"
  158                "definition of Markov chains), G.N.N. Martin (for the definition of range\n"
  159                "encoding), Igor Pavlov (for putting all the above together in LZMA), and\n"
  160                "Julian Seward (for bzip2's CLI).\n"
  161                "\nReport bugs to lzip-bug@nongnu.org\n"
  162                "Lzip home page: http://www.nongnu.org/lzip/lzip.html\n" );
  163   }
  164 
  165 
  166 void show_version()
  167   {
  168   std::printf( "%s %s\n", program_name, PROGVERSION );
  169   std::printf( "Copyright (C) %s Antonio Diaz Diaz.\n", program_year );
  170   std::printf( "License GPLv2+: GNU GPL version 2 or later <http://gnu.org/licenses/gpl.html>\n"
  171                "This is free software: you are free to change and redistribute it.\n"
  172                "There is NO WARRANTY, to the extent permitted by law.\n" );
  173   }
  174 
  175 } // end namespace
  176 
  177 void Pretty_print::operator()( const char * const msg ) const
  178   {
  179   if( verbosity >= 0 )
  180     {
  181     if( first_post )
  182       {
  183       first_post = false;
  184       std::fputs( padded_name.c_str(), stderr );
  185       if( !msg ) std::fflush( stderr );
  186       }
  187     if( msg ) std::fprintf( stderr, "%s\n", msg );
  188     }
  189   }
  190 
  191 
  192 const char * bad_version( const unsigned version )
  193   {
  194   static char buf[80];
  195   snprintf( buf, sizeof buf, "Version %u member format not supported.",
  196             version );
  197   return buf;
  198   }
  199 
  200 
  201 const char * format_ds( const unsigned dictionary_size )
  202   {
  203   enum { bufsize = 16, factor = 1024 };
  204   static char buf[bufsize];
  205   const char * const prefix[8] =
  206     { "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi", "Yi" };
  207   const char * p = "";
  208   const char * np = "  ";
  209   unsigned num = dictionary_size;
  210   bool exact = ( num % factor == 0 );
  211 
  212   for( int i = 0; i < 8 && ( num > 9999 || ( exact && num >= factor ) ); ++i )
  213     { num /= factor; if( num % factor != 0 ) exact = false;
  214       p = prefix[i]; np = ""; }
  215   snprintf( buf, bufsize, "%s%4u %sB", np, num, p );
  216   return buf;
  217   }
  218 
  219 
  220 void show_header( const unsigned dictionary_size )
  221   {
  222   std::fprintf( stderr, "dict %s, ", format_ds( dictionary_size ) );
  223   }
  224 
  225 namespace {
  226 
  227 unsigned long long getnum( const char * const ptr,
  228                            const unsigned long long llimit,
  229                            const unsigned long long ulimit )
  230   {
  231   char * tail;
  232   errno = 0;
  233   unsigned long long result = strtoull( ptr, &tail, 0 );
  234   if( tail == ptr )
  235     {
  236     show_error( "Bad or missing numerical argument.", 0, true );
  237     std::exit( 1 );
  238     }
  239 
  240   if( !errno && tail[0] )
  241     {
  242     const unsigned factor = ( tail[1] == 'i' ) ? 1024 : 1000;
  243     int exponent = 0;               // 0 = bad multiplier
  244     switch( tail[0] )
  245       {
  246       case 'Y': exponent = 8; break;
  247       case 'Z': exponent = 7; break;
  248       case 'E': exponent = 6; break;
  249       case 'P': exponent = 5; break;
  250       case 'T': exponent = 4; break;
  251       case 'G': exponent = 3; break;
  252       case 'M': exponent = 2; break;
  253       case 'K': if( factor == 1024 ) exponent = 1; break;
  254       case 'k': if( factor == 1000 ) exponent = 1; break;
  255       }
  256     if( exponent <= 0 )
  257       {
  258       show_error( "Bad multiplier in numerical argument.", 0, true );
  259       std::exit( 1 );
  260       }
  261     for( int i = 0; i < exponent; ++i )
  262       {
  263       if( ulimit / factor >= result ) result *= factor;
  264       else { errno = ERANGE; break; }
  265       }
  266     }
  267   if( !errno && ( result < llimit || result > ulimit ) ) errno = ERANGE;
  268   if( errno )
  269     {
  270     show_error( "Numerical argument out of limits." );
  271     std::exit( 1 );
  272     }
  273   return result;
  274   }
  275 
  276 
  277 int get_dict_size( const char * const arg )
  278   {
  279   char * tail;
  280   const long bits = std::strtol( arg, &tail, 0 );
  281   if( bits >= min_dictionary_bits &&
  282       bits <= max_dictionary_bits && *tail == 0 )
  283     return 1 << bits;
  284   return getnum( arg, min_dictionary_size, max_dictionary_size );
  285   }
  286 
  287 
  288 void set_mode( Mode & program_mode, const Mode new_mode )
  289   {
  290   if( program_mode != m_compress && program_mode != new_mode )
  291     {
  292     show_error( "Only one operation can be specified.", 0, true );
  293     std::exit( 1 );
  294     }
  295   program_mode = new_mode;
  296   }
  297 
  298 
  299 int extension_index( const std::string & name )
  300   {
  301   for( int eindex = 0; known_extensions[eindex].from; ++eindex )
  302     {
  303     const std::string ext( known_extensions[eindex].from );
  304     if( name.size() > ext.size() &&
  305         name.compare( name.size() - ext.size(), ext.size(), ext ) == 0 )
  306       return eindex;
  307     }
  308   return -1;
  309   }
  310 
  311 
  312 void set_c_outname( const std::string & name, const bool filenames_given,
  313                     const bool force_ext, const bool multifile )
  314   {
  315   /* zupdate < 1.9 depends on lzip adding the extension '.lz' to name when
  316      reading from standard input. */
  317   output_filename = name;
  318   if( multifile ) output_filename += "00001";
  319   if( force_ext || multifile ||
  320       ( !filenames_given && extension_index( output_filename ) < 0 ) )
  321     output_filename += known_extensions[0].from;
  322   }
  323 
  324 
  325 void set_d_outname( const std::string & name, const int eindex )
  326   {
  327   if( eindex >= 0 )
  328     {
  329     const std::string from( known_extensions[eindex].from );
  330     if( name.size() > from.size() )
  331       {
  332       output_filename.assign( name, 0, name.size() - from.size() );
  333       output_filename += known_extensions[eindex].to;
  334       return;
  335       }
  336     }
  337   output_filename = name; output_filename += ".out";
  338   if( verbosity >= 1 )
  339     std::fprintf( stderr, "%s: Can't guess original name for '%s' -- using '%s'\n",
  340                   program_name, name.c_str(), output_filename.c_str() );
  341   }
  342 
  343 } // end namespace
  344 
  345 int open_instream( const char * const name, struct stat * const in_statsp,
  346                    const bool one_to_one, const bool reg_only )
  347   {
  348   int infd = open( name, O_RDONLY | O_BINARY );
  349   if( infd < 0 )
  350     show_file_error( name, "Can't open input file", errno );
  351   else
  352     {
  353     const int i = fstat( infd, in_statsp );
  354     const mode_t mode = in_statsp->st_mode;
  355     const bool can_read = ( i == 0 && !reg_only &&
  356                             ( S_ISBLK( mode ) || S_ISCHR( mode ) ||
  357                               S_ISFIFO( mode ) || S_ISSOCK( mode ) ) );
  358     if( i != 0 || ( !S_ISREG( mode ) && ( !can_read || one_to_one ) ) )
  359       {
  360       if( verbosity >= 0 )
  361         std::fprintf( stderr, "%s: Input file '%s' is not a regular file%s.\n",
  362                       program_name, name, ( can_read && one_to_one ) ?
  363                       ",\n      and neither '-c' nor '-o' were specified" : "" );
  364       close( infd );
  365       infd = -1;
  366       }
  367     }
  368   return infd;
  369   }
  370 
  371 namespace {
  372 
  373 int open_instream2( const char * const name, struct stat * const in_statsp,
  374                     const Mode program_mode, const int eindex,
  375                     const bool one_to_one, const bool recompress )
  376   {
  377   if( program_mode == m_compress && !recompress && eindex >= 0 )
  378     {
  379     if( verbosity >= 0 )
  380       std::fprintf( stderr, "%s: Input file '%s' already has '%s' suffix.\n",
  381                     program_name, name, known_extensions[eindex].from );
  382     return -1;
  383     }
  384   return open_instream( name, in_statsp, one_to_one, false );
  385   }
  386 
  387 
  388 bool open_outstream( const bool force, const bool protect )
  389   {
  390   const mode_t usr_rw = S_IRUSR | S_IWUSR;
  391   const mode_t all_rw = usr_rw | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
  392   const mode_t outfd_mode = protect ? usr_rw : all_rw;
  393   int flags = O_CREAT | O_WRONLY | O_BINARY;
  394   if( force ) flags |= O_TRUNC; else flags |= O_EXCL;
  395 
  396   outfd = open( output_filename.c_str(), flags, outfd_mode );
  397   if( outfd >= 0 ) delete_output_on_interrupt = true;
  398   else if( verbosity >= 0 )
  399     {
  400     if( errno == EEXIST )
  401       std::fprintf( stderr, "%s: Output file '%s' already exists, skipping.\n",
  402                     program_name, output_filename.c_str() );
  403     else
  404       std::fprintf( stderr, "%s: Can't create output file '%s': %s\n",
  405                     program_name, output_filename.c_str(), std::strerror( errno ) );
  406     }
  407   return ( outfd >= 0 );
  408   }
  409 
  410 
  411 bool check_tty_in( const char * const input_filename, const int infd,
  412                    const Mode program_mode )
  413   {
  414   if( ( program_mode == m_decompress || program_mode == m_test ) &&
  415       isatty( infd ) )              // for example /dev/tty
  416     { show_file_error( input_filename,
  417                        "I won't read compressed data from a terminal." );
  418       return false; }
  419   return true;
  420   }
  421 
  422 bool check_tty_out( const Mode program_mode )
  423   {
  424   if( program_mode == m_compress && isatty( outfd ) )
  425     { show_file_error( output_filename.size() ?
  426                        output_filename.c_str() : "(stdout)",
  427                        "I won't write compressed data to a terminal." );
  428       return false; }
  429   return true;
  430   }
  431 
  432 
  433 void set_signals( void (*action)(int) )
  434   {
  435   std::signal( SIGHUP, action );
  436   std::signal( SIGINT, action );
  437   std::signal( SIGTERM, action );
  438   }
  439 
  440 
  441 void cleanup_and_fail( const int retval )
  442   {
  443   set_signals( SIG_IGN );           // ignore signals
  444   if( delete_output_on_interrupt )
  445     {
  446     delete_output_on_interrupt = false;
  447     if( verbosity >= 0 )
  448       std::fprintf( stderr, "%s: Deleting output file '%s', if it exists.\n",
  449                     program_name, output_filename.c_str() );
  450     if( outfd >= 0 ) { close( outfd ); outfd = -1; }
  451     if( std::remove( output_filename.c_str() ) != 0 && errno != ENOENT )
  452       show_error( "WARNING: deletion of output file (apparently) failed." );
  453     }
  454   std::exit( retval );
  455   }
  456 
  457 
  458 extern "C" void signal_handler( int )
  459   {
  460   show_error( "Control-C or similar caught, quitting." );
  461   cleanup_and_fail( 1 );
  462   }
  463 
  464 
  465 // Set permissions, owner, and times.
  466 void close_and_set_permissions( const struct stat * const in_statsp )
  467   {
  468   bool warning = false;
  469   if( in_statsp )
  470     {
  471     const mode_t mode = in_statsp->st_mode;
  472     // fchown will in many cases return with EPERM, which can be safely ignored.
  473     if( fchown( outfd, in_statsp->st_uid, in_statsp->st_gid ) == 0 )
  474       { if( fchmod( outfd, mode ) != 0 ) warning = true; }
  475     else
  476       if( errno != EPERM ||
  477           fchmod( outfd, mode & ~( S_ISUID | S_ISGID | S_ISVTX ) ) != 0 )
  478         warning = true;
  479     }
  480   if( close( outfd ) != 0 )
  481     {
  482     show_error( "Error closing output file", errno );
  483     cleanup_and_fail( 1 );
  484     }
  485   outfd = -1;
  486   delete_output_on_interrupt = false;
  487   if( in_statsp )
  488     {
  489     struct utimbuf t;
  490     t.actime = in_statsp->st_atime;
  491     t.modtime = in_statsp->st_mtime;
  492     if( utime( output_filename.c_str(), &t ) != 0 ) warning = true;
  493     }
  494   if( warning && verbosity >= 1 )
  495     show_error( "Can't change output file attributes." );
  496   }
  497 
  498 
  499 bool next_filename()
  500   {
  501   const unsigned name_len = output_filename.size();
  502   const unsigned ext_len = std::strlen( known_extensions[0].from );
  503   if( name_len >= ext_len + 5 )             // "*00001.lz"
  504     for( int i = name_len - ext_len - 1, j = 0; j < 5; --i, ++j )
  505       {
  506       if( output_filename[i] < '9' ) { ++output_filename[i]; return true; }
  507       else output_filename[i] = '0';
  508       }
  509   return false;
  510   }
  511 
  512 
  513 int compress( const unsigned long long cfile_size,
  514               const unsigned long long member_size,
  515               const unsigned long long volume_size, const int infd,
  516               const Lzma_options & encoder_options, const Pretty_print & pp,
  517               const struct stat * const in_statsp, const bool zero )
  518   {
  519   int retval = 0;
  520   LZ_encoder_base * encoder = 0;        // polymorphic encoder
  521   if( verbosity >= 1 ) pp();
  522 
  523   if( zero )
  524     encoder = new FLZ_encoder( infd, outfd );
  525   else
  526     {
  527     Lzip_header header;
  528     if( header.dictionary_size( encoder_options.dictionary_size ) &&
  529         encoder_options.match_len_limit >= min_match_len_limit &&
  530         encoder_options.match_len_limit <= max_match_len )
  531       encoder = new LZ_encoder( header.dictionary_size(),
  532                                 encoder_options.match_len_limit, infd, outfd );
  533     else internal_error( "invalid argument to encoder." );
  534     }
  535 
  536   unsigned long long in_size = 0, out_size = 0, partial_volume_size = 0;
  537   while( true )     // encode one member per iteration
  538     {
  539     const unsigned long long size = ( volume_size > 0 ) ?
  540       std::min( member_size, volume_size - partial_volume_size ) : member_size;
  541     show_cprogress( cfile_size, in_size, encoder, &pp );    // init
  542     if( !encoder->encode_member( size ) )
  543       { pp( "Encoder error." ); retval = 1; break; }
  544     in_size += encoder->data_position();
  545     out_size += encoder->member_position();
  546     if( encoder->data_finished() ) break;
  547     if( volume_size > 0 )
  548       {
  549       partial_volume_size += encoder->member_position();
  550       if( partial_volume_size >= volume_size - min_dictionary_size )
  551         {
  552         partial_volume_size = 0;
  553         if( delete_output_on_interrupt )
  554           {
  555           close_and_set_permissions( in_statsp );
  556           if( !next_filename() )
  557             { pp( "Too many volume files." ); retval = 1; break; }
  558           if( !open_outstream( true, in_statsp ) ) { retval = 1; break; }
  559           }
  560         }
  561       }
  562     encoder->reset();
  563     }
  564 
  565   if( retval == 0 && verbosity >= 1 )
  566     {
  567     if( in_size == 0 || out_size == 0 )
  568       std::fputs( " no data compressed.\n", stderr );
  569     else
  570       std::fprintf( stderr, "%6.3f:1, %5.2f%% ratio, %5.2f%% saved, "
  571                             "%llu in, %llu out.\n",
  572                     (double)in_size / out_size,
  573                     ( 100.0 * out_size ) / in_size,
  574                     100.0 - ( ( 100.0 * out_size ) / in_size ),
  575                     in_size, out_size );
  576     }
  577   delete encoder;
  578   return retval;
  579   }
  580 
  581 
  582 unsigned char xdigit( const unsigned value )
  583   {
  584   if( value <= 9 ) return '0' + value;
  585   if( value <= 15 ) return 'A' + value - 10;
  586   return 0;
  587   }
  588 
  589 
  590 bool show_trailing_data( const uint8_t * const data, const int size,
  591                          const Pretty_print & pp, const bool all,
  592                          const int ignore_trailing )    // -1 = show
  593   {
  594   if( verbosity >= 4 || ignore_trailing <= 0 )
  595     {
  596     std::string msg;
  597     if( !all ) msg = "first bytes of ";
  598     msg += "trailing data = ";
  599     for( int i = 0; i < size; ++i )
  600       {
  601       msg += xdigit( data[i] >> 4 );
  602       msg += xdigit( data[i] & 0x0F );
  603       msg += ' ';
  604       }
  605     msg += '\'';
  606     for( int i = 0; i < size; ++i )
  607       { if( std::isprint( data[i] ) ) msg += data[i]; else msg += '.'; }
  608     msg += '\'';
  609     pp( msg.c_str() );
  610     if( ignore_trailing == 0 ) show_file_error( pp.name(), trailing_msg );
  611     }
  612   return ( ignore_trailing > 0 );
  613   }
  614 
  615 
  616 int decompress( const unsigned long long cfile_size, const int infd,
  617                 const Pretty_print & pp, const bool ignore_trailing,
  618                 const bool loose_trailing, const bool testing )
  619   {
  620   int retval = 0;
  621   unsigned long long partial_file_pos = 0;
  622   Range_decoder rdec( infd );
  623   for( bool first_member = true; ; first_member = false )
  624     {
  625     Lzip_header header;
  626     rdec.reset_member_position();
  627     const int size = rdec.read_data( header.data, Lzip_header::size );
  628     if( rdec.finished() )           // End Of File
  629       {
  630       if( first_member )
  631         { show_file_error( pp.name(), "File ends unexpectedly at member header." );
  632           retval = 2; }
  633       else if( header.verify_prefix( size ) )
  634         { pp( "Truncated header in multimember file." );
  635           show_trailing_data( header.data, size, pp, true, -1 );
  636           retval = 2; }
  637       else if( size > 0 && !show_trailing_data( header.data, size, pp,
  638                                                 true, ignore_trailing ) )
  639         retval = 2;
  640       break;
  641       }
  642     if( !header.verify_magic() )
  643       {
  644       if( first_member )
  645         { show_file_error( pp.name(), bad_magic_msg ); retval = 2; }
  646       else if( !loose_trailing && header.verify_corrupt() )
  647         { pp( corrupt_mm_msg );
  648           show_trailing_data( header.data, size, pp, false, -1 );
  649           retval = 2; }
  650       else if( !show_trailing_data( header.data, size, pp, false, ignore_trailing ) )
  651         retval = 2;
  652       break;
  653       }
  654     if( !header.verify_version() )
  655       { pp( bad_version( header.version() ) ); retval = 2; break; }
  656     const unsigned dictionary_size = header.dictionary_size();
  657     if( !isvalid_ds( dictionary_size ) )
  658       { pp( bad_dict_msg ); retval = 2; break; }
  659 
  660     if( verbosity >= 2 || ( verbosity == 1 && first_member ) ) pp();
  661 
  662     LZ_decoder decoder( rdec, dictionary_size, outfd );
  663     show_dprogress( cfile_size, partial_file_pos, &rdec, &pp ); // init
  664     const int result = decoder.decode_member( pp );
  665     partial_file_pos += rdec.member_position();
  666     if( result != 0 )
  667       {
  668       if( verbosity >= 0 && result <= 2 )
  669         {
  670         pp();
  671         std::fprintf( stderr, "%s at pos %llu\n", ( result == 2 ) ?
  672                       "File ends unexpectedly" : "Decoder error",
  673                       partial_file_pos );
  674         }
  675       retval = 2; break;
  676       }
  677     if( verbosity >= 2 )
  678       { std::fputs( testing ? "ok\n" : "done\n", stderr ); pp.reset(); }
  679     }
  680   if( verbosity == 1 && retval == 0 )
  681     std::fputs( testing ? "ok\n" : "done\n", stderr );
  682   return retval;
  683   }
  684 
  685 } // end namespace
  686 
  687 
  688 void show_error( const char * const msg, const int errcode, const bool help )
  689   {
  690   if( verbosity < 0 ) return;
  691   if( msg && msg[0] )
  692     std::fprintf( stderr, "%s: %s%s%s\n", program_name, msg,
  693                   ( errcode > 0 ) ? ": " : "",
  694                   ( errcode > 0 ) ? std::strerror( errcode ) : "" );
  695   if( help )
  696     std::fprintf( stderr, "Try '%s --help' for more information.\n",
  697                   invocation_name );
  698   }
  699 
  700 
  701 void show_file_error( const char * const filename, const char * const msg,
  702                       const int errcode )
  703   {
  704   if( verbosity >= 0 )
  705     std::fprintf( stderr, "%s: %s: %s%s%s\n", program_name, filename, msg,
  706                   ( errcode > 0 ) ? ": " : "",
  707                   ( errcode > 0 ) ? std::strerror( errcode ) : "" );
  708   }
  709 
  710 
  711 void internal_error( const char * const msg )
  712   {
  713   if( verbosity >= 0 )
  714     std::fprintf( stderr, "%s: internal error: %s\n", program_name, msg );
  715   std::exit( 3 );
  716   }
  717 
  718 
  719 void show_cprogress( const unsigned long long cfile_size,
  720                      const unsigned long long partial_size,
  721                      const Matchfinder_base * const m,
  722                      const Pretty_print * const p )
  723   {
  724   static unsigned long long csize = 0;      // file_size / 100
  725   static unsigned long long psize = 0;
  726   static const Matchfinder_base * mb = 0;
  727   static const Pretty_print * pp = 0;
  728   static bool enabled = true;
  729 
  730   if( !enabled ) return;
  731   if( p )                   // initialize static vars
  732     {
  733     if( verbosity < 2 || !isatty( STDERR_FILENO ) ) { enabled = false; return; }
  734     csize = cfile_size; psize = partial_size; mb = m; pp = p;
  735     }
  736   if( mb && pp )
  737     {
  738     const unsigned long long pos = psize + mb->data_position();
  739     if( csize > 0 )
  740       std::fprintf( stderr, "%4llu%%  %.1f MB\r", pos / csize, pos / 1000000.0 );
  741     else
  742       std::fprintf( stderr, "  %.1f MB\r", pos / 1000000.0 );
  743     pp->reset(); (*pp)();           // restore cursor position
  744     }
  745   }
  746 
  747 
  748 void show_dprogress( const unsigned long long cfile_size,
  749                      const unsigned long long partial_size,
  750                      const Range_decoder * const d,
  751                      const Pretty_print * const p )
  752   {
  753   static unsigned long long csize = 0;      // file_size / 100
  754   static unsigned long long psize = 0;
  755   static const Range_decoder * rdec = 0;
  756   static const Pretty_print * pp = 0;
  757   static int counter = 0;
  758   static bool enabled = true;
  759 
  760   if( !enabled ) return;
  761   if( p )                   // initialize static vars
  762     {
  763     if( verbosity < 2 || !isatty( STDERR_FILENO ) ) { enabled = false; return; }
  764     csize = cfile_size; psize = partial_size; rdec = d; pp = p; counter = 0;
  765     }
  766   if( rdec && pp && --counter <= 0 )
  767     {
  768     const unsigned long long pos = psize + rdec->member_position();
  769     counter = 7;        // update display every 114688 bytes
  770     if( csize > 0 )
  771       std::fprintf( stderr, "%4llu%%  %.1f MB\r", pos / csize, pos / 1000000.0 );
  772     else
  773       std::fprintf( stderr, "  %.1f MB\r", pos / 1000000.0 );
  774     pp->reset(); (*pp)();           // restore cursor position
  775     }
  776   }
  777 
  778 
  779 int main( const int argc, const char * const argv[] )
  780   {
  781   /* Mapping from gzip/bzip2 style 1..9 compression modes
  782      to the corresponding LZMA compression modes. */
  783   const Lzma_options option_mapping[] =
  784     {
  785     { 1 << 16,  16 },       // -0
  786     { 1 << 20,   5 },       // -1
  787     { 3 << 19,   6 },       // -2
  788     { 1 << 21,   8 },       // -3
  789     { 3 << 20,  12 },       // -4
  790     { 1 << 22,  20 },       // -5
  791     { 1 << 23,  36 },       // -6
  792     { 1 << 24,  68 },       // -7
  793     { 3 << 23, 132 },       // -8
  794     { 1 << 25, 273 } };     // -9
  795   Lzma_options encoder_options = option_mapping[6]; // default = "-6"
  796   const unsigned long long max_member_size = 0x0008000000000000ULL; /* 2 PiB */
  797   const unsigned long long max_volume_size = 0x4000000000000000ULL; /* 4 EiB */
  798   unsigned long long member_size = max_member_size;
  799   unsigned long long volume_size = 0;
  800   std::string default_output_filename;
  801   std::vector< std::string > filenames;
  802   Mode program_mode = m_compress;
  803   bool force = false;
  804   bool ignore_trailing = true;
  805   bool keep_input_files = false;
  806   bool loose_trailing = false;
  807   bool recompress = false;
  808   bool to_stdout = false;
  809   bool zero = false;
  810   if( argc > 0 ) invocation_name = argv[0];
  811 
  812   enum { opt_lt = 256 };
  813   const Arg_parser::Option options[] =
  814     {
  815     { '0', "fast",              Arg_parser::no  },
  816     { '1',  0,                  Arg_parser::no  },
  817     { '2',  0,                  Arg_parser::no  },
  818     { '3',  0,                  Arg_parser::no  },
  819     { '4',  0,                  Arg_parser::no  },
  820     { '5',  0,                  Arg_parser::no  },
  821     { '6',  0,                  Arg_parser::no  },
  822     { '7',  0,                  Arg_parser::no  },
  823     { '8',  0,                  Arg_parser::no  },
  824     { '9', "best",              Arg_parser::no  },
  825     { 'a', "trailing-error",    Arg_parser::no  },
  826     { 'b', "member-size",       Arg_parser::yes },
  827     { 'c', "stdout",            Arg_parser::no  },
  828     { 'd', "decompress",        Arg_parser::no  },
  829     { 'f', "force",             Arg_parser::no  },
  830     { 'F', "recompress",        Arg_parser::no  },
  831     { 'h', "help",              Arg_parser::no  },
  832     { 'k', "keep",              Arg_parser::no  },
  833     { 'l', "list",              Arg_parser::no  },
  834     { 'm', "match-length",      Arg_parser::yes },
  835     { 'n', "threads",           Arg_parser::yes },
  836     { 'o', "output",            Arg_parser::yes },
  837     { 'q', "quiet",             Arg_parser::no  },
  838     { 's', "dictionary-size",   Arg_parser::yes },
  839     { 'S', "volume-size",       Arg_parser::yes },
  840     { 't', "test",              Arg_parser::no  },
  841     { 'v', "verbose",           Arg_parser::no  },
  842     { 'V', "version",           Arg_parser::no  },
  843     { opt_lt, "loose-trailing", Arg_parser::no  },
  844     {  0 , 0,                   Arg_parser::no  } };
  845 
  846   const Arg_parser parser( argc, argv, options );
  847   if( parser.error().size() )               // bad option
  848     { show_error( parser.error().c_str(), 0, true ); return 1; }
  849 
  850   int argind = 0;
  851   for( ; argind < parser.arguments(); ++argind )
  852     {
  853     const int code = parser.code( argind );
  854     if( !code ) break;                  // no more options
  855     const std::string & sarg = parser.argument( argind );
  856     const char * const arg = sarg.c_str();
  857     switch( code )
  858       {
  859       case '0': case '1': case '2': case '3': case '4':
  860       case '5': case '6': case '7': case '8': case '9':
  861                 zero = ( code == '0' );
  862                 encoder_options = option_mapping[code-'0']; break;
  863       case 'a': ignore_trailing = false; break;
  864       case 'b': member_size = getnum( arg, 100000, max_member_size ); break;
  865       case 'c': to_stdout = true; break;
  866       case 'd': set_mode( program_mode, m_decompress ); break;
  867       case 'f': force = true; break;
  868       case 'F': recompress = true; break;
  869       case 'h': show_help(); return 0;
  870       case 'k': keep_input_files = true; break;
  871       case 'l': set_mode( program_mode, m_list ); break;
  872       case 'm': encoder_options.match_len_limit =
  873                   getnum( arg, min_match_len_limit, max_match_len );
  874                 zero = false; break;
  875       case 'n': break;
  876       case 'o': if( sarg == "-" ) to_stdout = true;
  877                 else { default_output_filename = sarg; } break;
  878       case 'q': verbosity = -1; break;
  879       case 's': encoder_options.dictionary_size = get_dict_size( arg );
  880                 zero = false; break;
  881       case 'S': volume_size = getnum( arg, 100000, max_volume_size ); break;
  882       case 't': set_mode( program_mode, m_test ); break;
  883       case 'v': if( verbosity < 4 ) ++verbosity; break;
  884       case 'V': show_version(); return 0;
  885       case opt_lt: loose_trailing = true; break;
  886       default : internal_error( "uncaught option." );
  887       }
  888     } // end process options
  889 
  890 #if defined(__MSVCRT__) || defined(__OS2__) || defined(__DJGPP__)
  891   setmode( STDIN_FILENO, O_BINARY );
  892   setmode( STDOUT_FILENO, O_BINARY );
  893 #endif
  894 
  895   bool filenames_given = false;
  896   for( ; argind < parser.arguments(); ++argind )
  897     {
  898     filenames.push_back( parser.argument( argind ) );
  899     if( filenames.back() != "-" ) filenames_given = true;
  900     }
  901   if( filenames.empty() ) filenames.push_back("-");
  902 
  903   if( program_mode == m_list )
  904     return list_files( filenames, ignore_trailing, loose_trailing );
  905 
  906   if( program_mode == m_compress )
  907     {
  908     if( volume_size > 0 && !to_stdout && default_output_filename.size() &&
  909         filenames.size() > 1 )
  910       { show_error( "Only can compress one file when using '-o' and '-S'.",
  911                     0, true ); return 1; }
  912     dis_slots.init();
  913     prob_prices.init();
  914     }
  915   else volume_size = 0;
  916   if( program_mode == m_test ) to_stdout = false;   // apply overrides
  917   if( program_mode == m_test || to_stdout ) default_output_filename.clear();
  918 
  919   if( to_stdout && program_mode != m_test ) // check tty only once
  920     { outfd = STDOUT_FILENO; if( !check_tty_out( program_mode ) ) return 1; }
  921   else outfd = -1;
  922 
  923   const bool to_file = !to_stdout && program_mode != m_test &&
  924                        default_output_filename.size();
  925   if( !to_stdout && program_mode != m_test && ( filenames_given || to_file ) )
  926     set_signals( signal_handler );
  927 
  928   Pretty_print pp( filenames );
  929 
  930   int failed_tests = 0;
  931   int retval = 0;
  932   const bool one_to_one = !to_stdout && program_mode != m_test && !to_file;
  933   bool stdin_used = false;
  934   for( unsigned i = 0; i < filenames.size(); ++i )
  935     {
  936     std::string input_filename;
  937     int infd;
  938     struct stat in_stats;
  939 
  940     if( filenames[i] == "-" )
  941       {
  942       if( stdin_used ) continue; else stdin_used = true;
  943       infd = STDIN_FILENO;
  944       if( one_to_one ) { outfd = STDOUT_FILENO; output_filename.clear(); }
  945       }
  946     else
  947       {
  948       const int eindex = extension_index( input_filename = filenames[i] );
  949       infd = open_instream2( input_filename.c_str(), &in_stats, program_mode,
  950                              eindex, one_to_one, recompress );
  951       if( infd < 0 ) { set_retval( retval, 1 ); continue; }
  952       if( one_to_one )
  953         {
  954         if( program_mode == m_compress )
  955           set_c_outname( input_filename, true, true, volume_size > 0 );
  956         else set_d_outname( input_filename, eindex );
  957         if( !open_outstream( force, true ) )
  958           { set_retval( retval, 1 ); close( infd ); continue; }
  959         }
  960       }
  961 
  962     pp.set_name( input_filename );
  963     if( !check_tty_in( pp.name(), infd, program_mode ) )
  964       {
  965       set_retval( retval, 1 );
  966       if( program_mode == m_test ) { close( infd ); continue; }
  967       cleanup_and_fail( retval );
  968       }
  969     if( one_to_one && !check_tty_out( program_mode ) )
  970       { set_retval( retval, 1 ); return retval; }   // don't delete a tty
  971 
  972     if( to_file && outfd < 0 )      // open outfd after verifying infd
  973       {
  974       if( program_mode == m_compress ) set_c_outname( default_output_filename,
  975                                        filenames_given, false, volume_size > 0 );
  976       else output_filename = default_output_filename;
  977       if( !open_outstream( force, false ) || !check_tty_out( program_mode ) )
  978         return 1;   // check tty only once and don't try to delete a tty
  979       }
  980 
  981     const struct stat * const in_statsp =
  982       ( input_filename.size() && one_to_one ) ? &in_stats : 0;
  983     const unsigned long long cfile_size =
  984       ( input_filename.size() && S_ISREG( in_stats.st_mode ) ) ?
  985         ( in_stats.st_size + 99 ) / 100 : 0;
  986     int tmp;
  987     try {
  988       if( program_mode == m_compress )
  989         tmp = compress( cfile_size, member_size, volume_size, infd,
  990                         encoder_options, pp, in_statsp, zero );
  991       else
  992         tmp = decompress( cfile_size, infd, pp, ignore_trailing,
  993                           loose_trailing, program_mode == m_test );
  994       }
  995     catch( std::bad_alloc & )
  996       { pp( ( program_mode == m_compress ) ?
  997             "Not enough memory. Try a smaller dictionary size." :
  998             "Not enough memory." ); tmp = 1; }
  999     catch( Error & e ) { pp(); show_error( e.msg, errno ); tmp = 1; }
 1000     if( close( infd ) != 0 )
 1001       { show_file_error( pp.name(), "Error closing input file", errno );
 1002         set_retval( tmp, 1 ); }
 1003     set_retval( retval, tmp );
 1004     if( tmp )
 1005       { if( program_mode != m_test ) cleanup_and_fail( retval );
 1006         else ++failed_tests; }
 1007 
 1008     if( delete_output_on_interrupt && one_to_one )
 1009       close_and_set_permissions( in_statsp );
 1010     if( input_filename.size() && !keep_input_files && one_to_one &&
 1011         ( program_mode != m_compress || volume_size == 0 ) )
 1012       std::remove( input_filename.c_str() );
 1013     }
 1014   if( delete_output_on_interrupt ) close_and_set_permissions( 0 );  // -o
 1015   else if( outfd >= 0 && close( outfd ) != 0 )              // -c
 1016     {
 1017     show_error( "Error closing stdout", errno );
 1018     set_retval( retval, 1 );
 1019     }
 1020   if( failed_tests > 0 && verbosity >= 1 && filenames.size() > 1 )
 1021     std::fprintf( stderr, "%s: warning: %d %s failed the test.\n",
 1022                   program_name, failed_tests,
 1023                   ( failed_tests == 1 ) ? "file" : "files" );
 1024   return retval;
 1025   }