diff options
-rw-r--r-- | ChangeLog | 19 | ||||
-rw-r--r-- | INSTALL | 4 | ||||
-rw-r--r-- | Makefile.in | 9 | ||||
-rw-r--r-- | NEWS | 19 | ||||
-rw-r--r-- | README | 16 | ||||
-rw-r--r-- | carg_parser.c | 2 | ||||
-rw-r--r-- | carg_parser.h | 2 | ||||
-rwxr-xr-x | configure | 14 | ||||
-rw-r--r-- | decoder.c | 51 | ||||
-rw-r--r-- | decoder.h | 20 | ||||
-rw-r--r-- | doc/lunzip.1 | 25 | ||||
-rw-r--r-- | lzip.h | 19 | ||||
-rw-r--r-- | main.c | 191 | ||||
-rwxr-xr-x | testsuite/check.sh | 91 |
14 files changed, 315 insertions, 167 deletions
@@ -1,8 +1,17 @@ -2015-07-07 Antonio Diaz Diaz <antonio@gnu.org> +2016-05-12 Antonio Diaz Diaz <antonio@gnu.org> - * Version 1.8-pre1 released. + * Version 1.8 released. + * main.c: Added new option '-a, --trailing-error'. * main.c (main): With '-u', verify that output file is regular. - * Error messages synced with lzip-1.17. + * main.c (decompress): Print up to 6 bytes of trailing data + when '-vvvv' is specified. + * decoder.c (LZd_verify_trailer): Removed test of final code. + * main.c (main): Delete '--output' file if infd is a terminal. + * main.c (main): Don't use stdin more than once. + * Error messages synced with lzip-1.18. + * configure: Avoid warning on some shells when testing for gcc. + * testsuite/check.sh: A POSIX shell is required to run the tests. + * testsuite/check.sh: Don't check error messages. 2015-05-27 Antonio Diaz Diaz <antonio@gnu.org> @@ -24,7 +33,7 @@ 2013-09-17 Antonio Diaz Diaz <antonio@gnu.org> * Version 1.4 released. - * main.c (show_header): Do not show header version. + * main.c (show_header): Don't show header version. * Minor fixes. 2013-06-18 Antonio Diaz Diaz <antonio@gnu.org> @@ -60,7 +69,7 @@ * Created from the decompression code of clzip 1.1. -Copyright (C) 2010-2015 Antonio Diaz Diaz. +Copyright (C) 2010-2016 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,7 @@ Requirements ------------ You will need a C compiler. -I use gcc 4.9.1 and 4.1.2, but the code should compile with any +I use gcc 5.3.0 and 4.1.2, but the code should compile with any standards compliant compiler. Gcc is available at http://gcc.gnu.org. @@ -61,7 +61,7 @@ After running 'configure', you can run 'make' and 'make install' as explained above. -Copyright (C) 2010-2015 Antonio Diaz Diaz. +Copyright (C) 2010-2016 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 1ec2aaa..e4ff366 100644 --- a/Makefile.in +++ b/Makefile.in @@ -5,6 +5,7 @@ INSTALL_PROGRAM = $(INSTALL) -m 755 INSTALL_DATA = $(INSTALL) -m 644 INSTALL_DIR = $(INSTALL) -d -m 755 SHELL = /bin/sh +CAN_RUN_INSTALLINFO = $(SHELL) -c "install-info --version" > /dev/null 2>&1 objs = carg_parser.o decoder.o main.o @@ -66,7 +67,9 @@ install-info : if [ ! -d "$(DESTDIR)$(infodir)" ] ; then $(INSTALL_DIR) "$(DESTDIR)$(infodir)" ; fi -rm -f "$(DESTDIR)$(infodir)/$(pkgname).info"* $(INSTALL_DATA) $(VPATH)/doc/$(pkgname).info "$(DESTDIR)$(infodir)/$(pkgname).info" - -install-info --info-dir="$(DESTDIR)$(infodir)" "$(DESTDIR)$(infodir)/$(pkgname).info" + -if $(CAN_RUN_INSTALLINFO) ; then \ + install-info --info-dir="$(DESTDIR)$(infodir)" "$(DESTDIR)$(infodir)/$(pkgname).info" ; \ + fi install-info-compress : install-info lzip -v -9 "$(DESTDIR)$(infodir)/$(pkgname).info" @@ -89,7 +92,9 @@ uninstall-bin : -rm -f "$(DESTDIR)$(bindir)/$(progname)" uninstall-info : - -install-info --info-dir="$(DESTDIR)$(infodir)" --remove "$(DESTDIR)$(infodir)/$(pkgname).info" + -if $(CAN_RUN_INSTALLINFO) ; then \ + install-info --info-dir="$(DESTDIR)$(infodir)" --remove "$(DESTDIR)$(infodir)/$(pkgname).info" ; \ + fi -rm -f "$(DESTDIR)$(infodir)/$(pkgname).info"* uninstall-man : @@ -1,7 +1,24 @@ Changes in version 1.8: +The option "-a, --trailing-error", which makes lunzip exit with error +status 2 if any remaining input is detected after decompressing the last +member, has been added. + Lunzip now verifies that the output file is regular when "low memory" mode is requested. +Up to 6 bytes of trailing data are printed if "-vvvv" is specified. + +The test of the value remaining in the range decoder has been removed. +(After extensive testing it has been found useless to detect corruption +in the decompressed data. Eliminating it reduces the number of false +positives for corruption and makes error detection more accurate). + +When decompressing, the file specified with the '--output' option is now +deleted if the input is a terminal. + Some error messages have been adjusted to be identical to those of -lzip-1.17. +lzip-1.18. + +A harmless check failure on Windows, caused by the failed comparison of +a message in text mode, has been fixed. @@ -2,7 +2,7 @@ Description Lunzip is a decompressor for the lzip format. It is written in C and its small size makes it well suited for embedded devices or software -installers that need to decompress files but do not need compression +installers that need to decompress files but don't need compression capabilities. Lunzip is fully compatible with lzip-1.4 or newer. The lzip file format is designed for data sharing and long-term @@ -40,12 +40,12 @@ little memory as 50 kB, irrespective of the dictionary size used to compress the file. To activate it, specify the size of the output buffer with the "--buffer-size" option and lunzip will use the decompressed file as dictionary for distances beyond the buffer size. Of course, the -smaller the output buffer size used in relation to the dictionary size, -the more accesses to disk are needed and the slower the decompression -is. This "low memory" mode only works when decompressing to a regular -file and is intended for systems without enough memory (RAM + swap) to -keep the whole dictionary at once. It has been tested on a laptop with a -486 processor and 4 MiB of RAM. +smaller the buffer size used in relation to the dictionary size, the +more accesses to disk are needed and the slower the decompression is. +This "low memory" mode only works when decompressing to a regular file +and is intended for systems without enough memory (RAM + swap) to keep +the whole dictionary at once. It has been tested on a laptop with a 486 +processor and 4 MiB of RAM. The amount of memory required by lunzip to decompress a file is about 46 kB larger than the dictionary size used to compress that file, unless @@ -83,7 +83,7 @@ range encoding), Igor Pavlov (for putting all the above together in LZMA), and Julian Seward (for bzip2's CLI). -Copyright (C) 2010-2015 Antonio Diaz Diaz. +Copyright (C) 2010-2016 Antonio Diaz Diaz. This file is free documentation: you have unlimited permission to copy, distribute and modify it. diff --git a/carg_parser.c b/carg_parser.c index 8d74ea6..3d4e89f 100644 --- a/carg_parser.c +++ b/carg_parser.c @@ -1,5 +1,5 @@ /* Arg_parser - POSIX/GNU command line argument parser. (C version) - Copyright (C) 2006-2015 Antonio Diaz Diaz. + Copyright (C) 2006-2016 Antonio Diaz Diaz. This library is free software. Redistribution and use in source and binary forms, with or without modification, are permitted provided diff --git a/carg_parser.h b/carg_parser.h index ed4d9c5..e918942 100644 --- a/carg_parser.h +++ b/carg_parser.h @@ -1,5 +1,5 @@ /* Arg_parser - POSIX/GNU command line argument parser. (C version) - Copyright (C) 2006-2015 Antonio Diaz Diaz. + Copyright (C) 2006-2016 Antonio Diaz Diaz. This library is free software. Redistribution and use in source and binary forms, with or without modification, are permitted provided @@ -1,12 +1,12 @@ #! /bin/sh # configure script for Lunzip - Decompressor for the lzip format -# Copyright (C) 2010-2015 Antonio Diaz Diaz. +# Copyright (C) 2010-2016 Antonio Diaz Diaz. # # This configure script is free software: you have unlimited permission # to copy, distribute and modify it. pkgname=lunzip -pkgversion=1.8-pre1 +pkgversion=1.8 progname=lunzip srctrigger=doc/${progname}.1 @@ -26,8 +26,8 @@ CFLAGS='-Wall -W -O2' LDFLAGS= # checking whether we are using GNU C. -${CC} --version > /dev/null 2>&1 -if [ $? != 0 ] ; then +if /bin/sh -c "${CC} --version" > /dev/null 2>&1 ; then true +else CC=cc CFLAGS='-W -O2' fi @@ -139,7 +139,7 @@ if [ -z "${no_create}" ] ; then rm -f config.status cat > config.status << EOF #! /bin/sh -# This file was generated automatically by configure. Do not edit. +# This file was generated automatically by configure. Don't edit. # Run this file to recreate the current configuration. # # This script is free software: you have unlimited permission @@ -165,8 +165,8 @@ echo "LDFLAGS = ${LDFLAGS}" rm -f Makefile cat > Makefile << EOF # Makefile for Lunzip - Decompressor for the lzip format -# Copyright (C) 2010-2015 Antonio Diaz Diaz. -# This file was generated automatically by configure. Do not edit. +# Copyright (C) 2010-2016 Antonio Diaz Diaz. +# This file was generated automatically by configure. Don't edit. # # This Makefile is free software: you have unlimited permission # to copy, distribute and modify it. @@ -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 @@ -38,10 +38,11 @@ void Pp_show_msg( struct Pretty_print * const pp, const char * const msg ) { if( pp->first_post ) { - int i, len = pp->longest_name - strlen( pp->name ); + unsigned i; pp->first_post = false; fprintf( stderr, " %s: ", pp->name ); - for( i = 0; i < len; ++i ) fputc( ' ', stderr ); + for( i = strlen( pp->name ); i < pp->longest_name; ++i ) + fputc( ' ', stderr ); if( !msg ) fflush( stderr ); } if( msg ) fprintf( stderr, "%s\n", msg ); @@ -120,7 +121,8 @@ void LZd_flush_data( struct LZ_decoder * const d ) writeblock( d->outfd, d->buffer + d->stream_pos, size ) != size ) { show_error( "Write error", errno, false ); cleanup_and_fail( 1 ); } if( d->pos >= d->buffer_size ) - { d->partial_data_pos += d->pos; d->pos = 0; } + { d->partial_data_pos += d->pos; d->pos = 0; + if( d->partial_data_pos >= d->dictionary_size ) d->pos_wrapped = true; } d->stream_pos = d->pos; } } @@ -130,13 +132,11 @@ static bool LZd_verify_trailer( struct LZ_decoder * const d, struct Pretty_print * const pp ) { File_trailer trailer; - const unsigned long long member_size = Rd_member_position( d->rdec ) + Ft_size; - unsigned long long trailer_data_size; - unsigned long long trailer_member_size; - unsigned trailer_crc; + int size = Rd_read_data( d->rdec, trailer, Ft_size ); + const unsigned long long data_size = LZd_data_position( d ); + const unsigned long long member_size = Rd_member_position( d->rdec ); bool error = false; - int size = Rd_read_data( d->rdec, trailer, Ft_size ); if( size < Ft_size ) { error = true; @@ -149,52 +149,44 @@ static bool LZd_verify_trailer( struct LZ_decoder * const d, while( size < Ft_size ) trailer[size++] = 0; } - if( d->rdec->code != 0 ) - { - error = true; - Pp_show_msg( pp, "Range decoder final code is not zero." ); - } - trailer_crc = Ft_get_data_crc( trailer ); - if( trailer_crc != LZd_crc( d ) ) + if( Ft_get_data_crc( trailer ) != LZd_crc( d ) ) { error = true; if( verbosity >= 0 ) { Pp_show_msg( pp, 0 ); fprintf( stderr, "CRC mismatch; trailer says %08X, data CRC is %08X\n", - trailer_crc, LZd_crc( d ) ); + Ft_get_data_crc( trailer ), LZd_crc( d ) ); } } - trailer_data_size = Ft_get_data_size( trailer ); - if( trailer_data_size != LZd_data_position( d ) ) + if( Ft_get_data_size( trailer ) != data_size ) { error = true; if( verbosity >= 0 ) { Pp_show_msg( pp, 0 ); fprintf( stderr, "Data size mismatch; trailer says %llu, data size is %llu (0x%llX)\n", - trailer_data_size, LZd_data_position( d ), LZd_data_position( d ) ); + Ft_get_data_size( trailer ), data_size, data_size ); } } - trailer_member_size = Ft_get_member_size( trailer ); - if( trailer_member_size != member_size ) + if( Ft_get_member_size( trailer ) != member_size ) { error = true; if( verbosity >= 0 ) { Pp_show_msg( pp, 0 ); fprintf( stderr, "Member size mismatch; trailer says %llu, member size is %llu (0x%llX)\n", - trailer_member_size, member_size, member_size ); + Ft_get_member_size( trailer ), member_size, member_size ); } } - if( !error && verbosity >= 2 && LZd_data_position( d ) > 0 && member_size > 0 ) + if( !error && verbosity >= 2 && data_size > 0 && member_size > 0 ) fprintf( stderr, "%6.3f:1, %6.3f bits/byte, %5.2f%% saved. ", - (double)LZd_data_position( d ) / member_size, - ( 8.0 * member_size ) / LZd_data_position( d ), - 100.0 * ( 1.0 - ( (double)member_size / LZd_data_position( d ) ) ) ); + (double)data_size / member_size, + ( 8.0 * member_size ) / data_size, + 100.0 * ( 1.0 - ( (double)member_size / data_size ) ) ); if( !error && verbosity >= 4 ) fprintf( stderr, "data CRC %08X, data size %9llu, member size %8llu. ", - trailer_crc, trailer_data_size, trailer_member_size ); + LZd_crc( d ), data_size, member_size ); return !error; } @@ -308,7 +300,8 @@ int LZd_decode_member( struct LZ_decoder * const d, } rep3 = rep2; rep2 = rep1; rep1 = rep0_saved; state = St_set_match( state ); - if( rep0 >= d->dictionary_size || rep0 >= LZd_data_position( d ) ) + if( rep0 >= d->dictionary_size || + ( rep0 >= LZd_data_position( d ) && !d->pos_wrapped ) ) { LZd_flush_data( d ); return 1; } } copy_block( d, rep0, len ); @@ -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 @@ -60,7 +60,8 @@ static inline void Rd_reset_member_position( struct Range_decoder * const rdec ) static inline uint8_t Rd_get_byte( struct Range_decoder * const rdec ) { - if( Rd_finished( rdec ) ) return 0xAA; /* make code != 0 */ + /* 0xFF avoids decoder error if member is truncated at EOS marker */ + if( Rd_finished( rdec ) ) return 0xFF; return rdec->buffer[rdec->pos++]; } @@ -238,6 +239,7 @@ struct LZ_decoder int stream_pos; /* first byte not yet written to file */ uint32_t crc; int outfd; /* output file descriptor */ + bool pos_wrapped; Bit_model bm_literal[1<<literal_context_bits][0x300]; Bit_model bm_match[states][pos_states]; @@ -288,10 +290,15 @@ static inline void LZd_copy_block( struct LZ_decoder * const d, const int distance, int len ) { int i = d->pos - distance - 1; - if( i < 0 ) i += d->buffer_size; - if( len < d->buffer_size - max( d->pos, i ) && len <= abs( d->pos - i ) ) + bool fast; + if( i < 0 ) + { i += d->buffer_size; + fast = ( len <= d->buffer_size - i && len <= i - d->pos ); } + else + fast = ( len < d->buffer_size - d->pos && len <= d->pos - i ); + if( fast ) /* no wrap, no overlap */ { - memcpy( d->buffer + d->pos, d->buffer + i, len ); /* no wrap, no overlap */ + memcpy( d->buffer + d->pos, d->buffer + i, len ); d->pos += len; } else for( ; len > 0; --len ) @@ -330,13 +337,14 @@ static inline bool LZd_init( struct LZ_decoder * const d, d->partial_data_pos = 0; d->rdec = rde; d->dictionary_size = dict_size; - d->buffer_size = min( buffer_size, max( 65536, dict_size ) ); + d->buffer_size = min( buffer_size, dict_size ); d->buffer = (uint8_t *)malloc( d->buffer_size ); if( !d->buffer ) return false; d->pos = 0; d->stream_pos = 0; d->crc = 0xFFFFFFFFU; d->outfd = ofd; + d->pos_wrapped = false; Bm_array_init( d->bm_literal[0], (1 << literal_context_bits) * 0x300 ); Bm_array_init( d->bm_match[0], states * pos_states ); diff --git a/doc/lunzip.1 b/doc/lunzip.1 index 7a35eb5..53b3faf 100644 --- a/doc/lunzip.1 +++ b/doc/lunzip.1 @@ -1,5 +1,5 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.46.1. -.TH LUNZIP "1" "July 2015" "lunzip 1.8-pre1" "User Commands" +.TH LUNZIP "1" "May 2016" "lunzip 1.8" "User Commands" .SH NAME lunzip \- decompressor for the lzip format .SH SYNOPSIS @@ -8,7 +8,7 @@ lunzip \- decompressor for the lzip format .SH DESCRIPTION Lunzip is a decompressor for the lzip format. It is written in C and its small size makes it well suited for embedded devices or software -installers that need to decompress files but do not need compression +installers that need to decompress files but don't need compression capabilities. Lunzip is fully compatible with lzip\-1.4 or newer. .PP Lunzip provides a 'low memory' mode able to decompress any file using as @@ -16,9 +16,11 @@ little memory as 50 kB, irrespective of the dictionary size used to compress the file. To activate it, specify the size of the output buffer with the '\-\-buffer\-size' option and lunzip will use the decompressed file as dictionary for distances beyond the buffer size. Of course, the -smaller the output buffer size used in relation to the dictionary size, -the more accesses to disk are needed and the slower the decompression is. -This 'low memory' mode only works when decompressing to a regular file. +smaller the buffer size used in relation to the dictionary size, the +more accesses to disk are needed and the slower the decompression is. +This 'low memory' mode only works when decompressing to a regular file +and is intended for systems without enough memory (RAM + swap) to keep +the whole dictionary at once. .SH OPTIONS .TP \fB\-h\fR, \fB\-\-help\fR @@ -27,8 +29,11 @@ display this help and exit \fB\-V\fR, \fB\-\-version\fR output version information and exit .TP +\fB\-a\fR, \fB\-\-trailing\-error\fR +exit with error status if trailing data +.TP \fB\-c\fR, \fB\-\-stdout\fR -send output to standard output +write to standard output, keep input files .TP \fB\-d\fR, \fB\-\-decompress\fR decompress (this is the default) @@ -40,7 +45,7 @@ overwrite existing output files keep (don't delete) input files .TP \fB\-o\fR, \fB\-\-output=\fR<file> -if reading stdin, place the output into <file> +if reading standard input, write to <file> .TP \fB\-q\fR, \fB\-\-quiet\fR suppress all messages @@ -54,8 +59,8 @@ set output buffer size in bytes \fB\-v\fR, \fB\-\-verbose\fR be verbose (a 2nd \fB\-v\fR gives more) .PP -If no file names are given, lunzip decompresses from standard input to -standard output. +If no file names are given, or if a file is '\-', lunzip decompresses +from standard input to standard output. Numbers may be followed by a multiplier: k = kB = 10^3 = 1000, Ki = KiB = 2^10 = 1024, M = 10^6, Mi = 2^20, G = 10^9, Gi = 2^30, etc... .PP @@ -68,7 +73,7 @@ Report bugs to lzip\-bug@nongnu.org .br Lunzip home page: http://www.nongnu.org/lzip/lunzip.html .SH COPYRIGHT -Copyright \(co 2015 Antonio Diaz Diaz. +Copyright \(co 2016 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. @@ -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 @@ -50,6 +50,7 @@ enum { max_dictionary_bits = 29, max_dictionary_size = 1 << max_dictionary_bits, literal_context_bits = 3, + literal_pos_state_bits = 0, /* not used */ pos_state_bits = 2, pos_states = 1 << pos_state_bits, pos_state_mask = pos_states - 1, @@ -90,8 +91,8 @@ typedef int Bit_model; static inline void Bm_init( Bit_model * const probability ) { *probability = bit_model_total / 2; } -static inline void Bm_array_init( Bit_model * const p, const int size ) - { int i = 0; while( i < size ) p[i++] = bit_model_total / 2; } +static inline void Bm_array_init( Bit_model bm[], const int size ) + { int i; for( i = 0; i < size; ++i ) Bm_init( &bm[i] ); } struct Len_model { @@ -121,7 +122,8 @@ struct Pretty_print }; static inline void Pp_init( struct Pretty_print * const pp, - const char * const filenames[], const int num_filenames ) + const char * const filenames[], + const int num_filenames, const int verbosity ) { unsigned stdin_name_len; int i; @@ -131,6 +133,7 @@ static inline void Pp_init( struct Pretty_print * const pp, pp->first_post = false; stdin_name_len = strlen( pp->stdin_name ); + if( verbosity <= 0 ) return; for( i = 0; i < num_filenames; ++i ) { const char * const s = filenames[i]; @@ -191,6 +194,14 @@ enum { Fh_size = 6 }; static inline bool Fh_verify_magic( const File_header data ) { return ( memcmp( data, magic_string, 4 ) == 0 ); } +/* detect truncated header */ +static inline bool Fh_verify_prefix( const File_header data, const int size ) + { + int i; for( i = 0; i < size && i < 4; ++i ) + if( data[i] != magic_string[i] ) return false; + return ( size > 0 ); + } + static inline uint8_t Fh_version( const File_header data ) { return data[4]; } @@ -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 ); diff --git a/testsuite/check.sh b/testsuite/check.sh index f647d6c..f5b2088 100755 --- a/testsuite/check.sh +++ b/testsuite/check.sh @@ -1,6 +1,6 @@ #! /bin/sh # check script for Lunzip - Decompressor for the lzip format -# Copyright (C) 2010-2015 Antonio Diaz Diaz. +# Copyright (C) 2010-2016 Antonio Diaz Diaz. # # This script is free software: you have unlimited permission # to copy, distribute and modify it. @@ -17,9 +17,16 @@ if [ ! -f "${LZIP}" ] || [ ! -x "${LZIP}" ] ; then exit 1 fi +if [ -e "${LZIP}" ] 2> /dev/null ; then true +else + echo "$0: a POSIX shell is required to run the tests" + echo "Try bash -c \"$0 $1 $2\"" + exit 1 +fi + if [ -d tmp ] ; then rm -rf tmp ; fi mkdir tmp -cd "${objdir}"/tmp +cd "${objdir}"/tmp || framework_failure cat "${testdir}"/test.txt > in || framework_failure in_lz="${testdir}"/test.txt.lz @@ -27,21 +34,20 @@ fail=0 printf "testing lunzip-%s..." "$2" -"${LZIP}" -cqu-1 "${in_lz}" > /dev/null -if [ $? = 1 ] ; then printf . ; else printf - ; fail=1 ; fi -"${LZIP}" -cqu0 "${in_lz}" > /dev/null -if [ $? = 1 ] ; then printf . ; else printf - ; fail=1 ; fi -"${LZIP}" -cqu4095 "${in_lz}" > /dev/null -if [ $? = 1 ] ; then printf . ; else printf - ; fail=1 ; fi -"${LZIP}" -cqu513MiB "${in_lz}" > /dev/null -if [ $? = 1 ] ; then printf . ; else printf - ; fail=1 ; fi -printf " in: Bad magic number (file not in lzip format).\n" > msg -"${LZIP}" -t in 2> out -if [ $? = 2 ] && cmp out msg ; then printf . ; else printf - ; fail=1 ; fi -printf " (stdin): Bad magic number (file not in lzip format).\n" > msg -"${LZIP}" -t < in 2> out -if [ $? = 2 ] && cmp out msg ; then printf . ; else printf - ; fail=1 ; fi -rm -f out msg +cat "${testdir}"/test.txt.lz > uin.lz || framework_failure +"${LZIP}" -dfkqu-1 uin.lz +if [ $? = 1 ] && [ ! -e uin ] ; then printf . ; else printf - ; fail=1 ; fi +"${LZIP}" -dfkqu0 uin.lz +if [ $? = 1 ] && [ ! -e uin ] ; then printf . ; else printf - ; fail=1 ; fi +"${LZIP}" -dfkqu4095 uin.lz +if [ $? = 1 ] && [ ! -e uin ] ; then printf . ; else printf - ; fail=1 ; fi +"${LZIP}" -dfkqu513MiB uin.lz +if [ $? = 1 ] && [ ! -e uin ] ; then printf . ; else printf - ; fail=1 ; fi +rm -f uin.lz +"${LZIP}" -tq in +if [ $? = 2 ] ; then printf . ; else printf - ; fail=1 ; fi +"${LZIP}" -tq < in +if [ $? = 2 ] ; then printf . ; else printf - ; fail=1 ; fi "${LZIP}" -cdq in if [ $? = 2 ] ; then printf . ; else printf - ; fail=1 ; fi "${LZIP}" -cdq < in @@ -51,26 +57,53 @@ if [ $? = 2 ] ; then printf . ; else printf - ; fail=1 ; fi dd if="${in_lz}" bs=1 count=20 2> /dev/null | "${LZIP}" -tq if [ $? = 2 ] ; then printf . ; else printf - ; fail=1 ; fi -"${LZIP}" -t "${in_lz}" || fail=1 +printf "\ntesting decompression..." + +"${LZIP}" -t "${in_lz}" +if [ $? = 0 ] ; then printf . ; else printf - ; fail=1 ; fi "${LZIP}" -cd "${in_lz}" > copy || fail=1 cmp in copy || fail=1 printf . +rm -f copy cat "${in_lz}" > copy.lz || framework_failure -printf "to be overwritten" > copy || framework_failure -"${LZIP}" -df copy.lz || fail=1 +"${LZIP}" -dk copy.lz || fail=1 cmp in copy || fail=1 -printf . +printf "to be overwritten" > copy || framework_failure +"${LZIP}" -dq copy.lz +if [ $? = 1 ] ; then printf . ; else printf - ; fail=1 ; fi +"${LZIP}" -df copy.lz +if [ $? = 0 ] && [ ! -e copy.lz ] && cmp in copy ; then + printf . ; else printf - ; fail=1 ; fi printf "to be overwritten" > copy || framework_failure "${LZIP}" -df -o copy < "${in_lz}" || fail=1 cmp in copy || fail=1 printf . +rm -f copy cat "${in_lz}" > anyothername || framework_failure -"${LZIP}" -d anyothername || fail=1 -cmp in anyothername.out || fail=1 -printf . +"${LZIP}" -d -o copy - anyothername - < "${in_lz}" +if [ $? = 0 ] && cmp in copy && cmp in anyothername.out ; then + printf . ; else printf - ; fail=1 ; fi +rm -f copy anyothername.out + +"${LZIP}" -tq in "${in_lz}" +if [ $? = 2 ] ; then printf . ; else printf - ; fail=1 ; fi +"${LZIP}" -tq foo.lz "${in_lz}" +if [ $? = 1 ] ; then printf . ; else printf - ; fail=1 ; fi +"${LZIP}" -cdq in "${in_lz}" > copy +if [ $? = 2 ] && cat copy in | cmp in - ; then printf . ; else printf - ; fail=1 ; fi +"${LZIP}" -cdq foo.lz "${in_lz}" > copy +if [ $? = 1 ] && cmp in copy ; then printf . ; else printf - ; fail=1 ; fi +rm -f copy +cat "${in_lz}" > copy.lz || framework_failure +"${LZIP}" -dq in copy.lz +if [ $? = 2 ] && [ -e copy.lz ] && [ ! -e copy ] && [ ! -e in.out ] ; then + printf . ; else printf - ; fail=1 ; fi +"${LZIP}" -dq foo.lz copy.lz +if [ $? = 1 ] && [ ! -e copy.lz ] && [ ! -e foo ] && cmp in copy ; then + printf . ; else printf - ; fail=1 ; fi cat in in > in2 || framework_failure cat "${in_lz}" "${in_lz}" > copy2.lz || framework_failure @@ -80,12 +113,20 @@ cmp in2 copy2 || fail=1 printf . printf "garbage" >> copy2.lz || framework_failure +rm -f copy2 +"${LZIP}" -atq copy2.lz +if [ $? = 2 ] ; then printf . ; else printf - ; fail=1 ; fi +"${LZIP}" -atq < copy2.lz +if [ $? = 2 ] ; then printf . ; else printf - ; fail=1 ; fi +"${LZIP}" -adkq copy2.lz +if [ $? = 2 ] && [ ! -e copy2 ] ; then printf . ; else printf - ; fail=1 ; fi +"${LZIP}" -adkq -o copy2 < copy2.lz +if [ $? = 2 ] && [ ! -e copy2 ] ; then printf . ; else printf - ; fail=1 ; fi printf "to be overwritten" > copy2 || framework_failure "${LZIP}" -df copy2.lz || fail=1 cmp in2 copy2 || fail=1 printf . -rm -f copy for i in 12 4096 4Ki 29 512KiB ; do printf "to be overwritten" > copy || framework_failure "${LZIP}" -df -u$i -o copy < "${in_lz}" || fail=1 |