diff options
Diffstat (limited to '')
-rw-r--r-- | main.c | 191 |
1 files changed, 125 insertions, 66 deletions
@@ -1,5 +1,5 @@ /* Lunzip - Decompressor for the lzip format - Copyright (C) 2010-2015 Antonio Diaz Diaz. + Copyright (C) 2010-2016 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 @@ -23,6 +23,7 @@ #define _FILE_OFFSET_BITS 64 +#include <ctype.h> #include <errno.h> #include <fcntl.h> #include <limits.h> @@ -62,10 +63,11 @@ #error "Environments where CHAR_BIT != 8 are not supported." #endif +int verbosity = 0; const char * const Program_name = "Lunzip"; const char * const program_name = "lunzip"; -const char * const program_year = "2015"; +const char * const program_year = "2016"; const char * invocation_name = 0; struct { const char * from; const char * to; } const known_extensions[] = { @@ -75,10 +77,6 @@ struct { const char * from; const char * to; } const known_extensions[] = { char * output_filename = 0; int outfd = -1; -int verbosity = 0; -const mode_t usr_rw = S_IRUSR | S_IWUSR; -const mode_t all_rw = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; -mode_t outfd_mode = S_IRUSR | S_IWUSR; bool delete_output_on_interrupt = false; @@ -86,31 +84,34 @@ 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 do not need compression\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 output buffer size used in relation to the dictionary size,\n" - "the 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" + "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" - " -c, --stdout send output to standard output\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" - " -o, --output=<file> if reading stdin, place the output into <file>\n" + " -o, --output=<file> if reading standard input, write to <file>\n" " -q, --quiet suppress all messages\n" " -t, --test test compressed file integrity\n" " -u, --buffer-size=<bytes> set output buffer size in bytes\n" " -v, --verbose be verbose (a 2nd -v gives more)\n" - "If no file names are given, lunzip decompresses from standard input to\n" - "standard output.\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" "\nExit status: 0 for a normal exit, 1 for environmental problems (file\n" @@ -169,11 +170,10 @@ static unsigned long getnum( const char * const ptr, if( !errno && tail[0] ) { const int factor = ( tail[1] == 'i' ) ? 1024 : 1000; - int exponent = 0, i; - bool bad_multiplier = false; + int exponent = 0; /* 0 = bad multiplier */ + int i; switch( tail[0] ) { - case ' ': break; case 'Y': exponent = 8; break; case 'Z': exponent = 7; break; case 'E': exponent = 6; break; @@ -181,13 +181,10 @@ static unsigned long getnum( const char * const ptr, case 'T': exponent = 4; break; case 'G': exponent = 3; break; case 'M': exponent = 2; break; - case 'K': if( factor == 1024 ) exponent = 1; else bad_multiplier = true; - break; - case 'k': if( factor == 1000 ) exponent = 1; else bad_multiplier = true; - break; - default : bad_multiplier = true; + case 'K': if( factor == 1024 ) exponent = 1; break; + case 'k': if( factor == 1000 ) exponent = 1; break; } - if( bad_multiplier ) + if( exponent <= 0 ) { show_error( "Bad multiplier in numerical argument.", 0, true ); exit( 1 ); @@ -306,13 +303,17 @@ static void set_d_outname( const char * const name, const int i ) } -static bool open_outstream( const bool force ) +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 && verbosity >= 0 ) + 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", @@ -356,7 +357,11 @@ static void close_and_set_permissions( const struct stat * const in_statsp ) fchmod( outfd, mode & ~( S_ISUID | S_ISGID | S_ISVTX ) ) != 0 ) warning = true; } - if( close( outfd ) != 0 ) cleanup_and_fail( 1 ); + 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 ) @@ -371,8 +376,52 @@ static void close_and_set_permissions( const struct stat * const in_statsp ) } +static unsigned char xdigit( const int value ) + { + if( value >= 0 && value <= 9 ) return '0' + value; + if( value >= 10 && 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 bool ignore_trailing ) + { + if( verbosity >= 4 || !ignore_trailing ) + { + int i; + char buf[80]; + int len = snprintf( buf, sizeof buf, "%strailing data = ", + all ? "" : "first bytes of " ); + bool text = true; + for( i = 0; i < size; ++i ) + if( !isprint( data[i] ) ) { text = false; break; } + if( text ) + { + if( len > 0 && len < (int)sizeof buf ) + snprintf( buf + len, sizeof buf - len, "'%.*s'", size, (const char *)data ); + } + else + { + for( i = 0; i < size && len > 0 && len + 3 < (int)sizeof buf; ++i ) + { + if( i > 0 ) buf[len++] = ' '; + buf[len++] = xdigit( data[i] >> 4 ); + buf[len++] = xdigit( data[i] & 0x0F ); + buf[len] = 0; + } + } + Pp_show_msg( pp, buf ); + if( !ignore_trailing ) show_error( "Trailing data not allowed.", 0, false ); + } + return ignore_trailing; + } + + static int decompress( const int infd, struct Pretty_print * const pp, - const int buffer_size, const bool testing ) + const int buffer_size, + const bool ignore_trailing, const bool testing ) { unsigned long long partial_file_pos = 0; struct Range_decoder rdec; @@ -386,24 +435,30 @@ static int decompress( const int infd, struct Pretty_print * const pp, for( first_member = true; ; first_member = false ) { - int result; + int result, size; unsigned dictionary_size; File_header header; struct LZ_decoder decoder; Rd_reset_member_position( &rdec ); - Rd_read_data( &rdec, header, Fh_size ); + size = Rd_read_data( &rdec, header, Fh_size ); if( Rd_finished( &rdec ) ) /* End Of File */ { - if( first_member ) + if( first_member || Fh_verify_prefix( header, size ) ) { Pp_show_msg( pp, "File ends unexpectedly at member header." ); retval = 2; } + else if( size > 0 && !show_trailing_data( header, size, pp, + true, ignore_trailing ) ) + retval = 2; break; } if( !Fh_verify_magic( header ) ) { - if( !first_member ) break; /* trailing garbage */ - Pp_show_msg( pp, "Bad magic number (file not in lzip format)." ); - retval = 2; break; + if( first_member ) + { Pp_show_msg( pp, "Bad magic number (file not in lzip format)." ); + retval = 2; } + else if( !show_trailing_data( header, size, pp, false, ignore_trailing ) ) + retval = 2; + break; } if( !Fh_verify_version( header ) ) { @@ -424,8 +479,8 @@ static int decompress( const int infd, struct Pretty_print * const pp, if( !LZd_init( &decoder, &rdec, buffer_size, dictionary_size, outfd ) ) { - show_error( "Not enough memory. Try a smaller output buffer size.", 0, false ); - cleanup_and_fail( 1 ); + Pp_show_msg( pp, "Not enough memory. Try a smaller output buffer size." ); + retval = 1; break; } result = LZd_decode_member( &decoder, pp ); partial_file_pos += Rd_member_position( &rdec ); @@ -468,18 +523,16 @@ static void set_signals( void ) void show_error( const char * const msg, const int errcode, const bool help ) { - if( verbosity >= 0 ) + if( verbosity < 0 ) return; + if( msg && msg[0] ) { - if( msg && msg[0] ) - { - fprintf( stderr, "%s: %s", program_name, msg ); - if( errcode > 0 ) fprintf( stderr, ": %s", strerror( errcode ) ); - fputc( '\n', stderr ); - } - if( help ) - fprintf( stderr, "Try '%s --help' for more information.\n", - invocation_name ); + fprintf( stderr, "%s: %s", program_name, msg ); + if( errcode > 0 ) fprintf( stderr, ": %s", strerror( errcode ) ); + fputc( '\n', stderr ); } + if( help ) + fprintf( stderr, "Try '%s --help' for more information.\n", + invocation_name ); } @@ -504,13 +557,16 @@ int main( const int argc, const char * const argv[] ) int i; bool filenames_given = false; bool force = false; + bool ignore_trailing = true; bool keep_input_files = false; + bool stdin_used = false; bool testing = false; bool to_stdout = false; struct Pretty_print pp; const struct ap_Option options[] = { + { 'a', "trailing-error", ap_no }, { 'c', "stdout", ap_no }, { 'd', "decompress", ap_no }, { 'f', "force", ap_no }, @@ -542,6 +598,7 @@ int main( const int argc, const char * const argv[] ) if( !code ) break; /* no more options */ switch( code ) { + case 'a': ignore_trailing = false; break; case 'c': to_stdout = true; break; case 'd': testing = false; break; case 'f': force = true; break; @@ -578,7 +635,6 @@ int main( const int argc, const char * const argv[] ) if( buffer_size < max_dictionary_size ) { - struct stat st; bool from_stdin = false; if( to_stdout || testing ) { show_error( "'--buffer-size' is incompatible with '--stdout' and '--test'.", 0, false ); @@ -587,24 +643,15 @@ int main( const int argc, const char * const argv[] ) 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 stdin with a\n" - " reduced buffer size.", 0, false ); return 1; } - if( from_stdin && default_output_filename[0] && - stat( default_output_filename, &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, default_output_filename ); - return 1; - } + { 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 && !testing && ( filenames_given || default_output_filename[0] ) ) set_signals(); - Pp_init( &pp, filenames, num_filenames ); + Pp_init( &pp, filenames, num_filenames, verbosity ); output_filename = resize_buffer( output_filename, 1 ); for( i = 0; i < num_filenames; ++i ) @@ -616,6 +663,7 @@ int main( const int argc, const char * const argv[] ) if( !filenames[i][0] || strcmp( filenames[i], "-" ) == 0 ) { + if( stdin_used ) continue; else stdin_used = true; input_filename = ""; infd = STDIN_FILENO; if( !testing ) @@ -627,8 +675,7 @@ int main( const int argc, const char * const argv[] ) output_filename = resize_buffer( output_filename, strlen( default_output_filename ) + 1 ); strcpy( output_filename, default_output_filename ); - outfd_mode = all_rw; - if( !open_outstream( force ) ) + if( !open_outstream( force, true ) ) { if( retval < 1 ) retval = 1; close( infd ); infd = -1; @@ -648,8 +695,7 @@ int main( const int argc, const char * const argv[] ) else { set_d_outname( input_filename, extension_index( input_filename ) ); - outfd_mode = usr_rw; - if( !open_outstream( force ) ) + if( !open_outstream( force, false ) ) { if( retval < 1 ) retval = 1; close( infd ); infd = -1; @@ -662,14 +708,27 @@ int main( const int argc, const char * const argv[] ) if( isatty( infd ) ) { show_error( "I won't read compressed data from a terminal.", 0, true ); - return 1; + if( retval < 1 ) retval = 1; + 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 ); + } } - if( output_filename[0] && !to_stdout && !testing ) - delete_output_on_interrupt = true; in_statsp = input_filename[0] ? &in_stats : 0; Pp_set_name( &pp, input_filename ); - tmp = decompress( infd, &pp, buffer_size, testing ); + tmp = decompress( infd, &pp, buffer_size, ignore_trailing, testing ); if( tmp > retval ) retval = tmp; if( tmp && !testing ) cleanup_and_fail( retval ); |