/* Lunzip - Decompressor for the lzip format Copyright (C) 2010-2019 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 the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /* 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 lunzip to panic. */ #define _FILE_OFFSET_BITS 64 #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(__MSVCRT__) || defined(__OS2__) || defined(__DJGPP__) #include #if defined(__MSVCRT__) #define fchmod(x,y) 0 #define fchown(x,y,z) 0 #define SIGHUP SIGTERM #define S_ISSOCK(x) 0 #ifndef S_IRGRP #define S_IRGRP 0 #define S_IWGRP 0 #define S_IROTH 0 #define S_IWOTH 0 #endif #endif #if defined(__DJGPP__) #define S_ISSOCK(x) 0 #define S_ISVTX 0 #endif #endif #include "carg_parser.h" #include "lzip.h" #include "decoder.h" #ifndef O_BINARY #define O_BINARY 0 #endif #if CHAR_BIT != 8 #error "Environments where CHAR_BIT != 8 are not supported." #endif int verbosity = 0; const char * const program_name = "lunzip"; const char * const program_year = "2019"; const char * invocation_name = 0; const struct { const char * from; const char * to; } known_extensions[] = { { ".lz", "" }, { ".tlz", ".tar" }, { 0, 0 } }; enum Mode { m_compress, m_decompress, m_list, m_test }; /* Variables used in signal handler context. They are not declared volatile because the handler never returns. */ char * output_filename = 0; int outfd = -1; bool delete_output_on_interrupt = false; static void show_help( void ) { printf( "Lunzip is a decompressor for the lzip format. It is written in C and its\n" "small size makes it well suited for embedded devices or software\n" "installers that need to decompress files but don't need compression\n" "capabilities. Lunzip is fully compatible with lzip-1.4 or newer.\n" "\nLunzip provides a 'low memory' mode able to decompress any file using as\n" "little memory as 50 kB, irrespective of the dictionary size used to\n" "compress the file. To activate it, specify the size of the output buffer\n" "with the '--buffer-size' option and lunzip will use the decompressed\n" "file as dictionary for distances beyond the buffer size. Of course, the\n" "smaller the buffer size used in relation to the dictionary size, the\n" "more accesses to disk are needed and the slower the decompression is.\n" "This 'low memory' mode only works when decompressing to a regular file\n" "and is intended for systems without enough memory (RAM + swap) to keep\n" "the whole dictionary at once.\n" "\nUsage: %s [options] [files]\n", invocation_name ); printf( "\nOptions:\n" " -h, --help display this help and exit\n" " -V, --version output version information and exit\n" " -a, --trailing-error exit with error status if trailing data\n" " -c, --stdout write to standard output, keep input files\n" " -d, --decompress decompress (this is the default)\n" " -f, --force overwrite existing output files\n" " -k, --keep keep (don't delete) input files\n" " -l, --list print (un)compressed file sizes\n" " -o, --output= if reading standard input, write to \n" " -q, --quiet suppress all messages\n" " -t, --test test compressed file integrity\n" " -u, --buffer-size= set output buffer size in bytes\n" " -v, --verbose be verbose (a 2nd -v gives more)\n" " --loose-trailing allow trailing data seeming corrupt header\n" "If no file names are given, or if a file is '-', lunzip decompresses\n" "from standard input to standard output.\n" "Numbers may be followed by a multiplier: k = kB = 10^3 = 1000,\n" "Ki = KiB = 2^10 = 1024, M = 10^6, Mi = 2^20, G = 10^9, Gi = 2^30, etc...\n" "Buffer sizes 12 to 29 are interpreted as powers of two, meaning 2^12\n" "to 2^29 bytes.\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" "caused lunzip to panic.\n" "\nReport bugs to lzip-bug@nongnu.org\n" "Lunzip home page: http://www.nongnu.org/lzip/lunzip.html\n" ); } static void show_version( void ) { printf( "%s %s\n", program_name, PROGVERSION ); printf( "Copyright (C) %s Antonio Diaz Diaz.\n", program_year ); printf( "License GPLv2+: GNU GPL version 2 or later \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" ); } /* assure at least a minimum size for buffer 'buf' */ void * resize_buffer( void * buf, const unsigned min_size ) { if( buf ) buf = realloc( buf, min_size ); else buf = malloc( min_size ); if( !buf ) { show_error( "Not enough memory.", 0, false ); cleanup_and_fail( 1 ); } return buf; } void Pp_show_msg( struct Pretty_print * const pp, const char * const msg ) { if( verbosity >= 0 ) { if( pp->first_post ) { pp->first_post = false; fputs( pp->padded_name, stderr ); if( !msg ) fflush( stderr ); } if( msg ) fprintf( stderr, "%s\n", msg ); } } const char * bad_version( const unsigned version ) { static char buf[80]; snprintf( buf, sizeof buf, "Version %u member format not supported.", version ); return buf; } const char * format_ds( const unsigned dictionary_size ) { enum { bufsize = 16, factor = 1024 }; static char buf[bufsize]; const char * const prefix[8] = { "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi", "Yi" }; const char * p = ""; const char * np = " "; unsigned num = dictionary_size; bool exact = ( num % factor == 0 ); int i; for( i = 0; i < 8 && ( num > 9999 || ( exact && num >= factor ) ); ++i ) { num /= factor; if( num % factor != 0 ) exact = false; p = prefix[i]; np = ""; } snprintf( buf, bufsize, "%s%4u %sB", np, num, p ); return buf; } void show_header( const unsigned dictionary_size ) { fprintf( stderr, "dictionary %s, ", format_ds( dictionary_size ) ); } static unsigned long getnum( const char * const ptr, const unsigned long llimit, const unsigned long ulimit ) { unsigned long result; char * tail; errno = 0; result = strtoul( ptr, &tail, 0 ); if( tail == ptr ) { show_error( "Bad or missing numerical argument.", 0, true ); exit( 1 ); } if( !errno && tail[0] ) { const unsigned factor = ( tail[1] == 'i' ) ? 1024 : 1000; int exponent = 0; /* 0 = bad multiplier */ int i; switch( tail[0] ) { case 'Y': exponent = 8; break; case 'Z': exponent = 7; break; case 'E': exponent = 6; break; case 'P': exponent = 5; break; case 'T': exponent = 4; break; case 'G': exponent = 3; break; case 'M': exponent = 2; break; case 'K': if( factor == 1024 ) exponent = 1; break; case 'k': if( factor == 1000 ) exponent = 1; break; } if( exponent <= 0 ) { show_error( "Bad multiplier in numerical argument.", 0, true ); exit( 1 ); } for( i = 0; i < exponent; ++i ) { if( ulimit / factor >= result ) result *= factor; else { errno = ERANGE; break; } } } if( !errno && ( result < llimit || result > ulimit ) ) errno = ERANGE; if( errno ) { show_error( "Numerical argument out of limits.", 0, false ); exit( 1 ); } return result; } static int get_dict_size( const char * const arg ) { char * tail; const long bits = 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 ); } void set_mode( enum Mode * const program_modep, const enum Mode new_mode ) { if( *program_modep != m_compress && *program_modep != new_mode ) { show_error( "Only one operation can be specified.", 0, true ); exit( 1 ); } *program_modep = new_mode; } static int extension_index( const char * const name ) { int eindex; for( eindex = 0; known_extensions[eindex].from; ++eindex ) { const char * const ext = known_extensions[eindex].from; const unsigned name_len = strlen( name ); const unsigned ext_len = strlen( ext ); if( name_len > ext_len && strncmp( name + name_len - ext_len, ext, ext_len ) == 0 ) return eindex; } return -1; } static void set_d_outname( const char * const name, const int eindex ) { const unsigned name_len = strlen( name ); if( eindex >= 0 ) { const char * const from = known_extensions[eindex].from; const unsigned from_len = strlen( from ); if( name_len > from_len ) { output_filename = resize_buffer( output_filename, name_len + strlen( known_extensions[eindex].to ) + 1 ); strcpy( output_filename, name ); strcpy( output_filename + name_len - from_len, known_extensions[eindex].to ); return; } } output_filename = resize_buffer( output_filename, name_len + 4 + 1 ); strcpy( output_filename, name ); strcat( output_filename, ".out" ); if( verbosity >= 1 ) fprintf( stderr, "%s: Can't guess original name for '%s' -- using '%s'\n", program_name, name, output_filename ); } int open_instream( const char * const name, struct stat * const in_statsp, const bool no_ofile, const bool reg_only ) { int infd = open( name, O_RDONLY | O_BINARY ); if( infd < 0 ) show_file_error( name, "Can't open input file", errno ); else { const int i = fstat( infd, in_statsp ); const mode_t mode = in_statsp->st_mode; const bool can_read = ( i == 0 && !reg_only && ( S_ISBLK( mode ) || S_ISCHR( mode ) || S_ISFIFO( mode ) || S_ISSOCK( mode ) ) ); if( i != 0 || ( !S_ISREG( mode ) && ( !can_read || !no_ofile ) ) ) { if( verbosity >= 0 ) fprintf( stderr, "%s: Input file '%s' is not a regular file%s.\n", program_name, name, ( can_read && !no_ofile ) ? ",\n and '--stdout' was not specified" : "" ); close( infd ); infd = -1; } } return infd; } static bool open_outstream( const bool force, const bool from_stdin ) { const mode_t usr_rw = S_IRUSR | S_IWUSR; const mode_t all_rw = usr_rw | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; const mode_t outfd_mode = from_stdin ? all_rw : usr_rw; int flags = O_APPEND | O_CREAT | O_RDWR | O_BINARY; if( force ) flags |= O_TRUNC; else flags |= O_EXCL; outfd = open( output_filename, flags, outfd_mode ); if( outfd >= 0 ) delete_output_on_interrupt = true; else if( verbosity >= 0 ) { if( errno == EEXIST ) fprintf( stderr, "%s: Output file '%s' already exists, skipping.\n", program_name, output_filename ); else fprintf( stderr, "%s: Can't create output file '%s': %s\n", program_name, output_filename, strerror( errno ) ); } return ( outfd >= 0 ); } static void set_signals( void (*action)(int) ) { signal( SIGHUP, action ); signal( SIGINT, action ); signal( SIGTERM, action ); } void cleanup_and_fail( const int retval ) { set_signals( SIG_IGN ); /* ignore signals */ if( delete_output_on_interrupt ) { delete_output_on_interrupt = false; if( verbosity >= 0 ) fprintf( stderr, "%s: Deleting output file '%s', if it exists.\n", program_name, output_filename ); if( outfd >= 0 ) { close( outfd ); outfd = -1; } if( remove( output_filename ) != 0 && errno != ENOENT ) show_error( "WARNING: deletion of output file (apparently) failed.", 0, false ); } exit( retval ); } void signal_handler( int sig ) { if( sig ) {} /* keep compiler happy */ show_error( "Control-C or similar caught, quitting.", 0, false ); cleanup_and_fail( 1 ); } /* Set permissions, owner and times. */ static void close_and_set_permissions( const struct stat * const in_statsp ) { bool warning = false; if( in_statsp ) { const mode_t mode = in_statsp->st_mode; /* fchown will in many cases return with EPERM, which can be safely ignored. */ if( fchown( outfd, in_statsp->st_uid, in_statsp->st_gid ) == 0 ) { if( fchmod( outfd, mode ) != 0 ) warning = true; } else if( errno != EPERM || fchmod( outfd, mode & ~( S_ISUID | S_ISGID | S_ISVTX ) ) != 0 ) warning = true; } if( close( outfd ) != 0 ) { show_error( "Error closing output file", errno, false ); cleanup_and_fail( 1 ); } outfd = -1; delete_output_on_interrupt = false; if( in_statsp ) { struct utimbuf t; t.actime = in_statsp->st_atime; t.modtime = in_statsp->st_mtime; if( utime( output_filename, &t ) != 0 ) warning = true; } if( warning && verbosity >= 1 ) show_error( "Can't change output file attributes.", 0, false ); } static unsigned char xdigit( const unsigned value ) { if( value <= 9 ) return '0' + value; if( value <= 15 ) return 'A' + value - 10; return 0; } static bool show_trailing_data( const uint8_t * const data, const int size, struct Pretty_print * const pp, const bool all, const int ignore_trailing ) /* -1 = show */ { if( verbosity >= 4 || ignore_trailing <= 0 ) { int i; char buf[80]; unsigned len = max( 0, snprintf( buf, sizeof buf, "%strailing data = ", all ? "" : "first bytes of " ) ); for( i = 0; i < size && len + 2 < sizeof buf; ++i ) { buf[len++] = xdigit( data[i] >> 4 ); buf[len++] = xdigit( data[i] & 0x0F ); buf[len++] = ' '; } if( len < sizeof buf ) buf[len++] = '\''; for( i = 0; i < size && len < sizeof buf; ++i ) { if( isprint( data[i] ) ) buf[len++] = data[i]; else buf[len++] = '.'; } if( len < sizeof buf ) buf[len++] = '\''; if( len < sizeof buf ) buf[len] = 0; else buf[sizeof buf - 1] = 0; Pp_show_msg( pp, buf ); if( ignore_trailing == 0 ) show_file_error( pp->name, trailing_msg, 0 ); } return ( ignore_trailing > 0 ); } static int decompress( const unsigned long long cfile_size, const int infd, struct Pretty_print * const pp, const unsigned buffer_size, const bool ignore_trailing, const bool loose_trailing, const bool testing ) { unsigned long long partial_file_pos = 0; struct Range_decoder rdec; int retval = 0; bool first_member; if( !Rd_init( &rdec, infd ) ) { show_error( "Not enough memory.", 0, false ); cleanup_and_fail( 1 ); } for( first_member = true; ; first_member = false ) { int result, size; unsigned dictionary_size; Lzip_header header; struct LZ_decoder decoder; Rd_reset_member_position( &rdec ); size = Rd_read_data( &rdec, header, Lh_size ); if( Rd_finished( &rdec ) ) /* End Of File */ { if( first_member ) { show_file_error( pp->name, "File ends unexpectedly at member header.", 0 ); retval = 2; } else if( Lh_verify_prefix( header, size ) ) { Pp_show_msg( pp, "Truncated header in multimember file." ); show_trailing_data( header, size, pp, true, -1 ); retval = 2; } else if( size > 0 && !show_trailing_data( header, size, pp, true, ignore_trailing ) ) retval = 2; break; } if( !Lh_verify_magic( header ) ) { if( first_member ) { show_file_error( pp->name, bad_magic_msg, 0 ); retval = 2; } else if( !loose_trailing && Lh_verify_corrupt( header ) ) { Pp_show_msg( pp, corrupt_mm_msg ); show_trailing_data( header, size, pp, false, -1 ); retval = 2; } else if( !show_trailing_data( header, size, pp, false, ignore_trailing ) ) retval = 2; break; } if( !Lh_verify_version( header ) ) { Pp_show_msg( pp, bad_version( Lh_version( header ) ) ); retval = 2; break; } dictionary_size = Lh_get_dictionary_size( header ); if( !isvalid_ds( dictionary_size ) ) { Pp_show_msg( pp, bad_dict_msg ); retval = 2; break; } if( verbosity >= 2 || ( verbosity == 1 && first_member ) ) Pp_show_msg( pp, 0 ); if( !LZd_init( &decoder, &rdec, buffer_size, dictionary_size, outfd ) ) { Pp_show_msg( pp, "Not enough memory. Try a smaller output buffer size." ); retval = 1; break; } show_dprogress( cfile_size, partial_file_pos, &rdec, pp ); /* init */ result = LZd_decode_member( &decoder, pp ); partial_file_pos += Rd_member_position( &rdec ); LZd_free( &decoder ); if( result != 0 ) { if( verbosity >= 0 && result <= 2 ) { Pp_show_msg( pp, 0 ); fprintf( stderr, "%s at pos %llu\n", ( result == 2 ) ? "File ends unexpectedly" : "Decoder error", partial_file_pos ); } retval = 2; break; } if( verbosity >= 2 ) { fputs( testing ? "ok\n" : "done\n", stderr ); Pp_reset( pp ); } } Rd_free( &rdec ); if( verbosity == 1 && retval == 0 ) fputs( testing ? "ok\n" : "done\n", stderr ); return retval; } void show_error( const char * const msg, const int errcode, const bool help ) { if( verbosity < 0 ) return; if( msg && msg[0] ) fprintf( stderr, "%s: %s%s%s\n", program_name, msg, ( errcode > 0 ) ? ": " : "", ( errcode > 0 ) ? strerror( errcode ) : "" ); if( help ) fprintf( stderr, "Try '%s --help' for more information.\n", invocation_name ); } void show_file_error( const char * const filename, const char * const msg, const int errcode ) { if( verbosity >= 0 ) fprintf( stderr, "%s: %s: %s%s%s\n", program_name, filename, msg, ( errcode > 0 ) ? ": " : "", ( errcode > 0 ) ? strerror( errcode ) : "" ); } void internal_error( const char * const msg ) { if( verbosity >= 0 ) fprintf( stderr, "%s: internal error: %s\n", program_name, msg ); exit( 3 ); } void show_dprogress( const unsigned long long cfile_size, const unsigned long long partial_size, const struct Range_decoder * const d, struct Pretty_print * const p ) { static unsigned long long csize = 0; /* file_size / 100 */ static unsigned long long psize = 0; static const struct Range_decoder * rdec = 0; static struct Pretty_print * pp = 0; static int counter = 0; static bool enabled = true; if( !enabled ) return; if( p ) /* initialize static vars */ { if( verbosity < 2 || !isatty( STDERR_FILENO ) ) { enabled = false; return; } csize = cfile_size; psize = partial_size; rdec = d; pp = p; counter = 0; } if( rdec && pp && --counter <= 0 ) { const unsigned long long pos = psize + Rd_member_position( rdec ); counter = 7; /* update display every 114688 bytes */ if( csize > 0 ) fprintf( stderr, "%4llu%% %.1f MB\r", pos / csize, pos / 1000000.0 ); else fprintf( stderr, " %.1f MB\r", pos / 1000000.0 ); Pp_reset( pp ); Pp_show_msg( pp, 0 ); /* restore cursor position */ } } int main( const int argc, const char * const argv[] ) { const char * default_output_filename = ""; const char ** filenames = 0; int num_filenames = 0; unsigned buffer_size = max_dictionary_size; enum Mode program_mode = m_compress; int argind = 0; int failed_tests = 0; int retval = 0; int i; bool filenames_given = false; bool force = false; bool ignore_trailing = true; bool keep_input_files = false; bool loose_trailing = false; bool stdin_used = false; bool to_stdout = false; struct Pretty_print pp; enum { opt_lt = 256 }; const struct ap_Option options[] = { { 'a', "trailing-error", ap_no }, { 'c', "stdout", ap_no }, { 'd', "decompress", ap_no }, { 'f', "force", ap_no }, { 'h', "help", ap_no }, { 'k', "keep", ap_no }, { 'l', "list", ap_no }, { 'n', "threads", ap_yes }, { 'o', "output", ap_yes }, { 'q', "quiet", ap_no }, { 't', "test", ap_no }, { 'u', "buffer-size", ap_yes }, { 'v', "verbose", ap_no }, { 'V', "version", ap_no }, { opt_lt, "loose-trailing", ap_no }, { 0 , 0, ap_no } }; struct Arg_parser parser; invocation_name = argv[0]; CRC32_init(); if( !ap_init( &parser, argc, argv, options, 0 ) ) { show_error( "Not enough memory.", 0, false ); return 1; } if( ap_error( &parser ) ) /* bad option */ { show_error( ap_error( &parser ), 0, true ); return 1; } for( ; argind < ap_arguments( &parser ); ++argind ) { const int code = ap_code( &parser, argind ); const char * const arg = ap_argument( &parser, argind ); if( !code ) break; /* no more options */ switch( code ) { case 'a': ignore_trailing = false; break; case 'c': to_stdout = true; break; case 'd': set_mode( &program_mode, m_decompress ); break; case 'f': force = true; break; case 'h': show_help(); return 0; case 'k': keep_input_files = true; break; case 'l': set_mode( &program_mode, m_list ); break; case 'n': break; case 'o': default_output_filename = arg; break; case 'q': verbosity = -1; break; case 't': set_mode( &program_mode, m_test ); break; case 'u': buffer_size = get_dict_size( arg ); break; case 'v': if( verbosity < 4 ) ++verbosity; break; case 'V': show_version(); return 0; case opt_lt: loose_trailing = true; break; default : internal_error( "uncaught option." ); } } /* end process options */ #if defined(__MSVCRT__) || defined(__OS2__) || defined(__DJGPP__) setmode( STDIN_FILENO, O_BINARY ); setmode( STDOUT_FILENO, O_BINARY ); #endif num_filenames = max( 1, ap_arguments( &parser ) - argind ); filenames = resize_buffer( filenames, num_filenames * sizeof filenames[0] ); filenames[0] = "-"; for( i = 0; argind + i < ap_arguments( &parser ); ++i ) { filenames[i] = ap_argument( &parser, argind + i ); if( strcmp( filenames[i], "-" ) != 0 ) filenames_given = true; } if( program_mode == m_list ) return list_files( filenames, num_filenames, ignore_trailing, loose_trailing ); if( program_mode == m_test ) outfd = -1; else if( program_mode == m_compress ) program_mode = m_decompress; /* default mode */ if( buffer_size < max_dictionary_size ) { bool from_stdin = false; if( to_stdout || program_mode == m_test ) { show_error( "'--buffer-size' is incompatible with '--stdout' and '--test'.", 0, false ); return 1; } for( i = 0; i < num_filenames; ++i ) if( !filenames[i][0] || strcmp( filenames[i], "-" ) == 0 ) { from_stdin = true; break; } if( from_stdin && !default_output_filename[0] ) { show_error( "Output file must be specified when decompressing from standard input\n" " with a reduced buffer size.", 0, false ); return 1; } } if( !to_stdout && program_mode != m_test && ( filenames_given || default_output_filename[0] ) ) set_signals( signal_handler ); Pp_init( &pp, filenames, num_filenames ); output_filename = resize_buffer( output_filename, 1 ); for( i = 0; i < num_filenames; ++i ) { unsigned long long cfile_size; const char * input_filename = ""; int infd; int tmp; struct stat in_stats; const struct stat * in_statsp; output_filename[0] = 0; if( !filenames[i][0] || strcmp( filenames[i], "-" ) == 0 ) { if( stdin_used ) continue; else stdin_used = true; infd = STDIN_FILENO; if( program_mode != m_test ) { if( to_stdout || !default_output_filename[0] ) outfd = STDOUT_FILENO; else { output_filename = resize_buffer( output_filename, strlen( default_output_filename ) + 1 ); strcpy( output_filename, default_output_filename ); if( !open_outstream( force, true ) ) { if( retval < 1 ) retval = 1; close( infd ); continue; } } } } else { input_filename = filenames[i]; infd = open_instream( input_filename, &in_stats, to_stdout || program_mode == m_test, false ); if( infd < 0 ) { if( retval < 1 ) retval = 1; continue; } if( program_mode != m_test ) { if( to_stdout ) outfd = STDOUT_FILENO; else { set_d_outname( input_filename, extension_index( input_filename ) ); if( !open_outstream( force, false ) ) { if( retval < 1 ) retval = 1; close( infd ); continue; } } } } Pp_set_name( &pp, input_filename ); if( isatty( infd ) ) { show_file_error( pp.name, "I won't read compressed data from a terminal.", 0 ); if( retval < 1 ) retval = 1; if( program_mode == m_test ) { close( infd ); continue; } cleanup_and_fail( retval ); } if( delete_output_on_interrupt && buffer_size < max_dictionary_size ) { struct stat st; if( fstat( outfd, &st ) != 0 || !S_ISREG( st.st_mode ) ) { if( verbosity >= 0 ) fprintf( stderr, "%s: Output file '%s' is not a regular file,\n" " and 'low memory' mode has been requested.\n", program_name, output_filename ); if( retval < 1 ) retval = 1; cleanup_and_fail( retval ); } } in_statsp = input_filename[0] ? &in_stats : 0; cfile_size = ( in_statsp && S_ISREG( in_statsp->st_mode ) ) ? ( in_statsp->st_size + 99 ) / 100 : 0; tmp = decompress( cfile_size, infd, &pp, buffer_size, ignore_trailing, loose_trailing, program_mode == m_test ); if( close( infd ) != 0 ) { show_error( input_filename[0] ? "Error closing input file" : "Error closing stdin", errno, false ); if( tmp < 1 ) tmp = 1; } if( tmp > retval ) retval = tmp; if( tmp ) { if( program_mode != m_test ) cleanup_and_fail( retval ); else ++failed_tests; } if( delete_output_on_interrupt ) close_and_set_permissions( in_statsp ); if( input_filename[0] ) { if( !keep_input_files && !to_stdout && program_mode != m_test ) remove( input_filename ); } } if( outfd >= 0 && close( outfd ) != 0 ) { show_error( "Error closing stdout", errno, false ); if( retval < 1 ) retval = 1; } if( failed_tests > 0 && verbosity >= 1 && num_filenames > 1 ) fprintf( stderr, "%s: warning: %d %s failed the test.\n", program_name, failed_tests, ( failed_tests == 1 ) ? "file" : "files" ); free( output_filename ); free( filenames ); ap_free( &parser ); return retval; }