summaryrefslogtreecommitdiffstats
path: root/main.c
diff options
context:
space:
mode:
Diffstat (limited to 'main.c')
-rw-r--r--main.c295
1 files changed, 168 insertions, 127 deletions
diff --git a/main.c b/main.c
index 2ff4a15..b5e458e 100644
--- a/main.c
+++ b/main.c
@@ -1,24 +1,24 @@
-/* Lunzip - Decompressor for the lzip format
- Copyright (C) 2010-2019 Antonio Diaz Diaz.
+/* Lunzip - Decompressor for the lzip format
+ Copyright (C) 2010-2021 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 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.
+ 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 <http://www.gnu.org/licenses/>.
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
- 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.
+ 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
@@ -70,11 +70,11 @@
int verbosity = 0;
-const char * const program_name = "lunzip";
-const char * const program_year = "2019";
-const char * invocation_name = 0;
+static const char * const program_name = "lunzip";
+static const char * const program_year = "2021";
+static const char * invocation_name = "lunzip"; /* default value */
-const struct { const char * from; const char * to; } known_extensions[] = {
+static const struct { const char * from; const char * to; } known_extensions[] = {
{ ".lz", "" },
{ ".tlz", ".tar" },
{ 0, 0 } };
@@ -83,23 +83,23 @@ 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 char * output_filename = 0;
+static int outfd = -1;
+static 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"
+ printf( "Lunzip is a decompressor for the lzip format written in C. Its small size\n"
+ "makes it well suited for embedded devices or software installers that need\n"
+ "to decompress files but don't need compression capabilities. Lunzip is fully\n"
+ "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"
+ "with the option --buffer-size 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"
+ "larger the difference between the buffer size and 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"
@@ -114,22 +114,29 @@ static void show_help( void )
" -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=<file> if reading standard input, write to <file>\n"
+ " -o, --output=<file> write to <file>, keep input files\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"
" --loose-trailing allow trailing data seeming corrupt header\n"
- "If no file names are given, or if a file is '-', lunzip decompresses\n"
+ "\nIf 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"
+ "\nTo extract all the files from archive 'foo.tar.lz', use the commands\n"
+ "'tar -xf foo.tar.lz' or 'lunzip -cd foo.tar.lz | tar -xf -'.\n"
"\nExit status: 0 for a normal exit, 1 for environmental problems (file\n"
"not found, invalid flags, I/O errors, etc), 2 to indicate a corrupt or\n"
"invalid input file, 3 for an internal consistency error (eg, bug) which\n"
"caused lunzip to panic.\n"
+ "\nThe ideas embodied in lunzip are due to (at least) the following people:\n"
+ "Abraham Lempel and Jacob Ziv (for the LZ algorithm), Andrey Markov (for the\n"
+ "definition of Markov chains), G.N.N. Martin (for the definition of range\n"
+ "encoding), Igor Pavlov (for putting all the above together in LZMA), and\n"
+ "Julian Seward (for bzip2's CLI).\n"
"\nReport bugs to lzip-bug@nongnu.org\n"
"Lunzip home page: http://www.nongnu.org/lzip/lunzip.html\n" );
}
@@ -150,14 +157,63 @@ void * resize_buffer( void * buf, const unsigned min_size )
{
if( buf ) buf = realloc( buf, min_size );
else buf = malloc( min_size );
- if( !buf )
+ if( !buf ) { show_error( mem_msg, 0, false ); cleanup_and_fail( 1 ); }
+ return buf;
+ }
+
+
+struct Pretty_print
+ {
+ const char * name;
+ char * padded_name;
+ const char * stdin_name;
+ unsigned longest_name;
+ bool first_post;
+ };
+
+static void Pp_init( struct Pretty_print * const pp,
+ const char * const filenames[], const int num_filenames )
+ {
+ unsigned stdin_name_len;
+ int i;
+ pp->name = 0;
+ pp->padded_name = 0;
+ pp->stdin_name = "(stdin)";
+ pp->longest_name = 0;
+ pp->first_post = false;
+
+ if( verbosity <= 0 ) return;
+ stdin_name_len = strlen( pp->stdin_name );
+ for( i = 0; i < num_filenames; ++i )
{
- show_error( "Not enough memory.", 0, false );
- cleanup_and_fail( 1 );
+ const char * const s = filenames[i];
+ const unsigned len = (strcmp( s, "-" ) == 0) ? stdin_name_len : strlen( s );
+ if( pp->longest_name < len ) pp->longest_name = len;
}
- return buf;
+ if( pp->longest_name == 0 ) pp->longest_name = stdin_name_len;
+ }
+
+static void Pp_set_name( struct Pretty_print * const pp,
+ const char * const filename )
+ {
+ unsigned name_len, padded_name_len, i = 0;
+
+ if( filename && filename[0] && strcmp( filename, "-" ) != 0 )
+ pp->name = filename;
+ else pp->name = pp->stdin_name;
+ name_len = strlen( pp->name );
+ padded_name_len = max( name_len, pp->longest_name ) + 4;
+ pp->padded_name = resize_buffer( pp->padded_name, padded_name_len + 1 );
+ while( i < 2 ) pp->padded_name[i++] = ' ';
+ while( i < name_len + 2 ) { pp->padded_name[i] = pp->name[i-2]; ++i; }
+ pp->padded_name[i++] = ':';
+ while( i < padded_name_len ) pp->padded_name[i++] = ' ';
+ pp->padded_name[i] = 0;
+ pp->first_post = true;
}
+static void Pp_reset( struct Pretty_print * const pp )
+ { if( pp->name && pp->name[0] ) pp->first_post = true; }
void Pp_show_msg( struct Pretty_print * const pp, const char * const msg )
{
@@ -204,7 +260,7 @@ const char * format_ds( const unsigned dictionary_size )
void show_header( const unsigned dictionary_size )
{
- fprintf( stderr, "dictionary %s, ", format_ds( dictionary_size ) );
+ fprintf( stderr, "dict %s, ", format_ds( dictionary_size ) );
}
@@ -271,7 +327,7 @@ static int get_dict_size( const char * const arg )
}
-void set_mode( enum Mode * const program_modep, const enum Mode new_mode )
+static void set_mode( enum Mode * const program_modep, const enum Mode new_mode )
{
if( *program_modep != m_compress && *program_modep != new_mode )
{
@@ -324,7 +380,7 @@ static void set_d_outname( const char * const name, const int eindex )
int open_instream( const char * const name, struct stat * const in_statsp,
- const bool no_ofile, const bool reg_only )
+ const bool one_to_one, const bool reg_only )
{
int infd = open( name, O_RDONLY | O_BINARY );
if( infd < 0 )
@@ -336,13 +392,12 @@ int open_instream( const char * const name, struct stat * const in_statsp,
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( i != 0 || ( !S_ISREG( mode ) && ( !can_read || one_to_one ) ) )
{
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" : "" );
+ program_name, name, ( can_read && one_to_one ) ?
+ ",\n and neither '-c' nor '-o' were specified" : "" );
close( infd );
infd = -1;
}
@@ -351,11 +406,11 @@ int open_instream( const char * const name, struct stat * const in_statsp,
}
-static bool open_outstream( const bool force, const bool from_stdin )
+static bool open_outstream( const bool force, const bool protect )
{
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;
+ const mode_t outfd_mode = protect ? usr_rw : all_rw;
int flags = O_APPEND | O_CREAT | O_RDWR | O_BINARY;
if( force ) flags |= O_TRUNC; else flags |= O_EXCL;
@@ -399,7 +454,7 @@ void cleanup_and_fail( const int retval )
}
-void signal_handler( int sig )
+static void signal_handler( int sig )
{
if( sig ) {} /* keep compiler happy */
show_error( "Control-C or similar caught, quitting.", 0, false );
@@ -407,7 +462,20 @@ void signal_handler( int sig )
}
- /* Set permissions, owner and times. */
+static bool check_tty_in( const char * const input_filename, const int infd,
+ const enum Mode program_mode, int * const retval )
+ {
+ if( isatty( infd ) ) /* for example /dev/tty */
+ { show_file_error( input_filename,
+ "I won't read compressed data from a terminal.", 0 );
+ close( infd ); set_retval( retval, 1 );
+ if( program_mode != m_test ) cleanup_and_fail( *retval );
+ return false; }
+ return true;
+ }
+
+
+/* Set permissions, owner, and times. */
static void close_and_set_permissions( const struct stat * const in_statsp )
{
bool warning = false;
@@ -487,10 +555,7 @@ static int decompress( const unsigned long long cfile_size, const int infd,
int retval = 0;
bool first_member;
if( !Rd_init( &rdec, infd ) )
- {
- show_error( "Not enough memory.", 0, false );
- cleanup_and_fail( 1 );
- }
+ { show_error( mem_msg, 0, false ); cleanup_and_fail( 1 ); }
for( first_member = true; ; first_member = false )
{
@@ -589,7 +654,7 @@ void show_file_error( const char * const filename, const char * const msg,
}
-void internal_error( const char * const msg )
+static void internal_error( const char * const msg )
{
if( verbosity >= 0 )
fprintf( stderr, "%s: internal error: %s\n", program_name, msg );
@@ -631,7 +696,9 @@ void show_dprogress( const unsigned long long cfile_size,
int main( const int argc, const char * const argv[] )
{
const char * default_output_filename = "";
- const char ** filenames = 0;
+ static struct Arg_parser parser; /* static because valgrind complains */
+ static struct Pretty_print pp; /* and memory management in C sucks */
+ static const char ** filenames = 0;
int num_filenames = 0;
unsigned buffer_size = max_dictionary_size;
enum Mode program_mode = m_compress;
@@ -646,7 +713,6 @@ int main( const int argc, const char * const argv[] )
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[] =
@@ -668,13 +734,11 @@ int main( const int argc, const char * const argv[] )
{ opt_lt, "loose-trailing", ap_no },
{ 0 , 0, ap_no } };
- struct Arg_parser parser;
-
- invocation_name = argv[0];
+ if( argc > 0 ) invocation_name = argv[0];
CRC32_init();
if( !ap_init( &parser, argc, argv, options, 0 ) )
- { show_error( "Not enough memory.", 0, false ); return 1; }
+ { show_error( mem_msg, 0, false ); return 1; }
if( ap_error( &parser ) ) /* bad option */
{ show_error( ap_error( &parser ), 0, true ); return 1; }
@@ -693,7 +757,8 @@ int main( const int argc, const char * const argv[] )
case 'k': keep_input_files = true; break;
case 'l': set_mode( &program_mode, m_list ); break;
case 'n': break;
- case 'o': default_output_filename = arg; break;
+ case 'o': if( strcmp( arg, "-" ) == 0 ) to_stdout = true;
+ else { 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;
@@ -722,17 +787,17 @@ int main( const int argc, const char * const argv[] )
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( program_mode == m_compress )
+ program_mode = m_decompress; /* default mode */
+ if( program_mode == m_test ) to_stdout = false; /* apply overrides */
+ if( program_mode == m_test || to_stdout ) default_output_filename = "";
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; }
+ { 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; }
@@ -741,13 +806,19 @@ int main( const int argc, const char * const argv[] )
" with a reduced buffer size.", 0, false ); return 1; }
}
- if( !to_stdout && program_mode != m_test &&
- ( filenames_given || default_output_filename[0] ) )
+ output_filename = resize_buffer( output_filename, 1 );
+ output_filename[0] = 0;
+ if( to_stdout && program_mode != m_test ) outfd = STDOUT_FILENO;
+ else outfd = -1;
+
+ const bool to_file = !to_stdout && program_mode != m_test &&
+ default_output_filename[0];
+ if( !to_stdout && program_mode != m_test && ( filenames_given || to_file ) )
set_signals( signal_handler );
Pp_init( &pp, filenames, num_filenames );
- output_filename = resize_buffer( output_filename, 1 );
+ const bool one_to_one = !to_stdout && program_mode != m_test && !to_file;
for( i = 0; i < num_filenames; ++i )
{
unsigned long long cfile_size;
@@ -756,60 +827,35 @@ int main( const int argc, const char * const argv[] )
int tmp;
struct stat in_stats;
const struct stat * in_statsp;
- output_filename[0] = 0;
- if( !filenames[i][0] || strcmp( filenames[i], "-" ) == 0 )
+ Pp_set_name( &pp, filenames[i] );
+ if( 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;
- }
- }
- }
+ if( !check_tty_in( pp.name, infd, program_mode, &retval ) ) continue;
+ if( one_to_one ) { outfd = STDOUT_FILENO; output_filename[0] = 0; }
}
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 )
+ infd = open_instream( input_filename, &in_stats, one_to_one, false );
+ if( infd < 0 ) { set_retval( &retval, 1 ); continue; }
+ if( !check_tty_in( pp.name, infd, program_mode, &retval ) ) continue;
+ if( one_to_one ) /* open outfd after verifying infd */
{
- 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;
- }
- }
+ set_d_outname( input_filename, extension_index( input_filename ) );
+ if( !open_outstream( force, true ) )
+ { close( infd ); set_retval( &retval, 1 ); continue; }
}
}
- Pp_set_name( &pp, input_filename );
- if( isatty( infd ) )
+ if( to_file && outfd < 0 ) /* open outfd after verifying 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 );
+ output_filename = resize_buffer( output_filename,
+ strlen( default_output_filename ) + 1 );
+ strcpy( output_filename, default_output_filename );
+ if( !open_outstream( force, false ) ) return 1;
}
if( delete_output_on_interrupt && buffer_size < max_dictionary_size )
@@ -821,39 +867,34 @@ int main( const int argc, const char * const argv[] )
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 );
+ set_retval( &retval, 1 );
+ return retval; /* don't try to delete a non-regular file */
}
}
- 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;
+ in_statsp = ( input_filename[0] && one_to_one ) ? &in_stats : 0;
+ cfile_size = ( input_filename[0] && S_ISREG( in_stats.st_mode ) ) ?
+ ( in_stats.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;
+ { show_file_error( pp.name, "Error closing input file", errno );
+ set_retval( &tmp, 1 ); }
+ set_retval( &retval, tmp );
if( tmp )
{ if( program_mode != m_test ) cleanup_and_fail( retval );
else ++failed_tests; }
- if( delete_output_on_interrupt )
+ if( delete_output_on_interrupt && one_to_one )
close_and_set_permissions( in_statsp );
- if( input_filename[0] )
- {
- if( !keep_input_files && !to_stdout && program_mode != m_test )
- remove( input_filename );
- }
+ if( input_filename[0] && !keep_input_files && one_to_one )
+ remove( input_filename );
}
- if( outfd >= 0 && close( outfd ) != 0 )
+ if( delete_output_on_interrupt ) close_and_set_permissions( 0 ); /* -o */
+ else if( outfd >= 0 && close( outfd ) != 0 ) /* -c */
{
show_error( "Error closing stdout", errno, false );
- if( retval < 1 ) retval = 1;
+ set_retval( &retval, 1 );
}
if( failed_tests > 0 && verbosity >= 1 && num_filenames > 1 )
fprintf( stderr, "%s: warning: %d %s failed the test.\n",