summaryrefslogtreecommitdiffstats
path: root/main.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-01-23 05:36:41 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-01-23 05:36:48 +0000
commit00184d655a72ed5a71aa80449250255fb8ac2caa (patch)
treeedfe486934a7174a4a8504eeb8923243407ad2db /main.c
parentReleasing debian version 1.13-6. (diff)
downloadlunzip-00184d655a72ed5a71aa80449250255fb8ac2caa.tar.xz
lunzip-00184d655a72ed5a71aa80449250255fb8ac2caa.zip
Merging upstream version 1.14~rc1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'main.c')
-rw-r--r--main.c255
1 files changed, 145 insertions, 110 deletions
diff --git a/main.c b/main.c
index 73e29b4..6949e1d 100644
--- a/main.c
+++ b/main.c
@@ -1,5 +1,5 @@
/* Lunzip - Decompressor for the lzip format
- Copyright (C) 2010-2022 Antonio Diaz Diaz.
+ Copyright (C) 2010-2023 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
@@ -16,9 +16,9 @@
*/
/*
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
- (e.g., bug) which caused lunzip to panic.
+ (file not found, invalid command-line options, I/O errors, etc), 2 to
+ indicate a corrupt or invalid input file, 3 for an internal consistency
+ error (e.g., bug) which caused lunzip to panic.
*/
#define _FILE_OFFSET_BITS 64
@@ -26,10 +26,10 @@
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
-#include <limits.h>
+#include <limits.h> /* SSIZE_MAX */
#include <signal.h>
#include <stdbool.h>
-#include <stdint.h>
+#include <stdint.h> /* SIZE_MAX */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -76,7 +76,7 @@
int verbosity = 0;
static const char * const program_name = "lunzip";
-static const char * const program_year = "2022";
+static const char * const program_year = "2023";
static const char * invocation_name = "lunzip"; /* default value */
static const struct { const char * from; const char * to; } known_extensions[] = {
@@ -124,21 +124,23 @@ static void show_help( void )
" -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"
+ " --empty-error exit with error status if empty member in file\n"
+ " --marking-error exit with error status if 1st LZMA byte not 0\n"
" --loose-trailing allow trailing data seeming corrupt header\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"
+ "Buffer sizes 12 to 29 are interpreted as powers of two, meaning 2^12 to\n"
+ "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 (e.g., bug) which\n"
- "caused lunzip to panic.\n"
+ "\nExit status: 0 for a normal exit, 1 for environmental problems\n"
+ "(file not found, invalid command-line options, I/O errors, etc), 2 to\n"
+ "indicate a corrupt or invalid input file, 3 for an internal consistency\n"
+ "error (e.g., bug) which 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"
+ "Abraham Lempel and Jacob Ziv (for the LZ algorithm), Andrei 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"
@@ -243,16 +245,15 @@ const char * bad_version( const unsigned version )
const char * format_ds( const unsigned dictionary_size )
{
- enum { bufsize = 16, factor = 1024 };
+ enum { bufsize = 16, factor = 1024, n = 3 };
static char buf[bufsize];
- const char * const prefix[8] =
- { "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi", "Yi" };
+ const char * const prefix[n] = { "Ki", "Mi", "Gi" };
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 )
+ int i; for( i = 0; i < n && ( 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 );
@@ -266,12 +267,12 @@ void show_header( const unsigned dictionary_size )
}
-/* separate large numbers >= 100_000 in groups of 3 digits using '_' */
+/* separate numbers of 5 or more digits in groups of 3 digits using '_' */
static const char * format_num3( unsigned long long num )
{
- const char * const si_prefix = "kMGTPEZY";
- const char * const binary_prefix = "KMGTPEZY";
- enum { buffers = 8, bufsize = 4 * sizeof (long long) };
+ enum { buffers = 8, bufsize = 4 * sizeof num, n = 10 };
+ const char * const si_prefix = "kMGTPEZYRQ";
+ const char * const binary_prefix = "KMGTPEZYRQ";
static char buffer[buffers][bufsize]; /* circle of static buffers for printf */
static int current = 0;
int i;
@@ -281,15 +282,15 @@ static const char * format_num3( unsigned long long num )
if( num > 1024 )
{
char prefix = 0; /* try binary first, then si */
- for( i = 0; i < 8 && num >= 1024 && num % 1024 == 0; ++i )
+ for( i = 0; i < n && num != 0 && num % 1024 == 0; ++i )
{ num /= 1024; prefix = binary_prefix[i]; }
if( prefix ) *(--p) = 'i';
else
- for( i = 0; i < 8 && num >= 1000 && num % 1000 == 0; ++i )
+ for( i = 0; i < n && num != 0 && num % 1000 == 0; ++i )
{ num /= 1000; prefix = si_prefix[i]; }
if( prefix ) *(--p) = prefix;
}
- const bool split = num >= 100000;
+ const bool split = num >= 10000;
for( i = 0; ; )
{
@@ -300,6 +301,16 @@ static const char * format_num3( unsigned long long num )
}
+void show_option_error( const char * const arg, const char * const msg,
+ const char * const option_name )
+ {
+ if( verbosity >= 0 )
+ fprintf( stderr, "%s: '%s': %s option '%s'.\n",
+ program_name, arg, msg, option_name );
+ }
+
+
+/* Recognized formats: <num>k, <num>Ki, <num>[MGTPEZYRQ][i] */
static unsigned long getnum( const char * const arg,
const char * const option_name,
const unsigned long llimit,
@@ -307,14 +318,10 @@ static unsigned long getnum( const char * const arg,
{
char * tail;
errno = 0;
- unsigned long long result = strtoul( arg, &tail, 0 );
+ unsigned long result = strtoul( arg, &tail, 0 );
if( tail == arg )
- {
- if( verbosity >= 0 )
- fprintf( stderr, "%s: Bad or missing numerical argument in "
- "option '%s'.\n", program_name, option_name );
- exit( 1 );
- }
+ { show_option_error( arg, "Bad or missing numerical argument in",
+ option_name ); exit( 1 ); }
if( !errno && tail[0] )
{
@@ -323,6 +330,8 @@ static unsigned long getnum( const char * const arg,
int i;
switch( tail[0] )
{
+ case 'Q': exponent = 10; break;
+ case 'R': exponent = 9; break;
case 'Y': exponent = 8; break;
case 'Z': exponent = 7; break;
case 'E': exponent = 6; break;
@@ -334,12 +343,8 @@ static unsigned long getnum( const char * const arg,
case 'k': if( factor == 1000 ) exponent = 1; break;
}
if( exponent <= 0 )
- {
- if( verbosity >= 0 )
- fprintf( stderr, "%s: Bad multiplier in numerical argument of "
- "option '%s'.\n", program_name, option_name );
- exit( 1 );
- }
+ { show_option_error( arg, "Bad multiplier in numerical argument of",
+ option_name ); exit( 1 ); }
for( i = 0; i < exponent; ++i )
{
if( ulimit / factor >= result ) result *= factor;
@@ -350,8 +355,8 @@ static unsigned long getnum( const char * const arg,
if( errno )
{
if( verbosity >= 0 )
- fprintf( stderr, "%s: Numerical argument out of limits [%s,%s] "
- "in option '%s'.\n", program_name, format_num3( llimit ),
+ fprintf( stderr, "%s: '%s': Value out of limits [%s,%s] in "
+ "option '%s'.\n", program_name, arg, format_num3( llimit ),
format_num3( ulimit ), option_name );
exit( 1 );
}
@@ -417,7 +422,7 @@ static void set_d_outname( const char * const name, const int eindex )
strcpy( output_filename, name );
strcat( output_filename, ".out" );
if( verbosity >= 1 )
- fprintf( stderr, "%s: Can't guess original name for '%s' -- using '%s'\n",
+ fprintf( stderr, "%s: %s: Can't guess original name -- using '%s'\n",
program_name, name, output_filename );
}
@@ -438,9 +443,9 @@ int open_instream( const char * const name, struct stat * const in_statsp,
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",
+ fprintf( stderr, "%s: %s: Input file is not a regular file%s.\n",
program_name, name, ( can_read && one_to_one ) ?
- ",\n and neither '-c' nor '-o' were specified" : "" );
+ ",\n and neither '-c' nor '-o' were specified" : "" );
close( infd );
infd = -1;
}
@@ -449,6 +454,33 @@ int open_instream( const char * const name, struct stat * const in_statsp,
}
+static bool make_dirs( const char * const name )
+ {
+ int i = strlen( name );
+ while( i > 0 && name[i-1] != '/' ) --i; /* remove last component */
+ while( i > 0 && name[i-1] == '/' ) --i; /* remove slash(es) */
+ const int dirsize = i; /* size of dirname without trailing slash(es) */
+
+ for( i = 0; i < dirsize; ) /* if dirsize == 0, dirname is '/' or empty */
+ {
+ while( i < dirsize && name[i] == '/' ) ++i;
+ const int first = i;
+ while( i < dirsize && name[i] != '/' ) ++i;
+ if( first < i )
+ {
+ char partial[i+1]; memcpy( partial, name, i ); partial[i] = 0;
+ const mode_t mode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;
+ struct stat st;
+ if( stat( partial, &st ) == 0 )
+ { if( !S_ISDIR( st.st_mode ) ) { errno = ENOTDIR; return false; } }
+ else if( mkdir( partial, mode ) != 0 && errno != EEXIST )
+ return false; /* if EEXIST, another process created the dir */
+ }
+ }
+ return true;
+ }
+
+
static bool open_outstream( const bool force, const bool protect )
{
const mode_t usr_rw = S_IRUSR | S_IWUSR;
@@ -457,18 +489,21 @@ static bool open_outstream( const bool force, const bool protect )
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 )
- {
+ outfd = -1;
+ const int len = strlen( output_filename );
+ if( len > 0 && output_filename[len-1] == '/' ) errno = EISDIR;
+ else {
+ if( !protect && !make_dirs( output_filename ) )
+ { show_file_error( output_filename,
+ "Error creating intermediate directory", errno ); return false; }
+ outfd = open( output_filename, flags, outfd_mode );
+ if( outfd >= 0 ) { delete_output_on_interrupt = true; return true; }
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 ) );
+ { show_file_error( output_filename,
+ "Output file already exists, skipping.", 0 ); return false; }
}
- return ( outfd >= 0 );
+ show_file_error( output_filename, "Can't create output file", errno );
+ return false;
}
@@ -486,12 +521,10 @@ void cleanup_and_fail( const int retval )
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 );
+ show_file_error( output_filename, "Deleting output file, if it exists.", 0 );
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 );
+ show_error( "warning: deletion of output file failed", errno, false );
}
exit( retval );
}
@@ -534,10 +567,8 @@ static void close_and_set_permissions( const struct stat * const in_statsp )
warning = true;
}
if( close( outfd ) != 0 )
- {
- show_error( "Error closing output file", errno, false );
- cleanup_and_fail( 1 );
- }
+ { show_file_error( output_filename, "Error closing output file", errno );
+ cleanup_and_fail( 1 ); }
outfd = -1;
delete_output_on_interrupt = false;
if( in_statsp )
@@ -548,11 +579,12 @@ static void close_and_set_permissions( const struct stat * const in_statsp )
if( utime( output_filename, &t ) != 0 ) warning = true;
}
if( warning && verbosity >= 1 )
- show_error( "Can't change output file attributes.", 0, false );
+ show_file_error( output_filename,
+ "warning: can't change output file attributes", errno );
}
-static unsigned char xdigit( const unsigned value )
+static unsigned char xdigit( const unsigned value ) /* hex digit for 'value' */
{
if( value <= 9 ) return '0' + value;
if( value <= 15 ) return 'A' + value - 10;
@@ -584,14 +616,14 @@ static bool show_trailing_data( const uint8_t * const data, const int size,
Pp_show_msg( pp, buf );
if( ignore_trailing == 0 ) show_file_error( pp->name, trailing_msg, 0 );
}
- return ( ignore_trailing > 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 )
+ const struct Cl_options * const cl_opts,
+ struct Pretty_print * const pp,
+ const unsigned buffer_size, const bool testing )
{
unsigned long long partial_file_pos = 0;
struct Range_decoder rdec;
@@ -610,28 +642,25 @@ static int decompress( const unsigned long long cfile_size, const int infd,
if( first_member )
{ show_file_error( pp->name, "File ends unexpectedly at member header.", 0 );
retval = 2; }
- else if( Lh_verify_prefix( header, size ) )
+ else if( Lh_check_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;
+ show_trailing_data( header, size, pp, true, -1 ); retval = 2; }
+ else if( size > 0 && !show_trailing_data( header, size, pp, true,
+ cl_opts->ignore_trailing ) ) retval = 2;
break;
}
- if( !Lh_verify_magic( header ) )
+ if( !Lh_check_magic( header ) )
{
if( first_member )
{ show_file_error( pp->name, bad_magic_msg, 0 ); retval = 2; }
- else if( !loose_trailing && Lh_verify_corrupt( header ) )
+ else if( !cl_opts->loose_trailing && Lh_check_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;
+ show_trailing_data( header, size, pp, false, -1 ); retval = 2; }
+ else if( !show_trailing_data( header, size, pp, false,
+ cl_opts->ignore_trailing ) ) retval = 2;
break;
}
- if( !Lh_verify_version( header ) )
+ if( !Lh_check_version( header ) )
{ Pp_show_msg( pp, bad_version( Lh_version( header ) ) );
retval = 2; break; }
const unsigned dictionary_size = Lh_get_dictionary_size( header );
@@ -648,7 +677,7 @@ static int decompress( const unsigned long long cfile_size, const int infd,
retval = 1; break;
}
show_dprogress( cfile_size, partial_file_pos, &rdec, pp ); /* init */
- const int result = LZd_decode_member( &decoder, pp );
+ const int result = LZd_decode_member( &decoder, cl_opts, pp );
partial_file_pos += Rd_member_position( &rdec );
LZd_free( &decoder );
if( result != 0 )
@@ -660,6 +689,8 @@ static int decompress( const unsigned long long cfile_size, const int infd,
"File ends unexpectedly" : "Decoder error",
partial_file_pos );
}
+ else if( result == 5 ) Pp_show_msg( pp, empty_msg );
+ else if( result == 6 ) Pp_show_msg( pp, marking_msg );
retval = 2; break;
}
if( verbosity >= 2 )
@@ -740,32 +771,34 @@ int main( const int argc, const char * const argv[] )
unsigned buffer_size = max_dictionary_size;
enum Mode program_mode = m_compress;
int i;
+ struct Cl_options cl_opts; /* command-line options */
+ Cl_options_init( &cl_opts );
bool force = false;
- bool ignore_trailing = true;
bool keep_input_files = false;
- bool loose_trailing = false;
bool to_stdout = false;
if( argc > 0 ) invocation_name = argv[0];
- enum { opt_lt = 256 };
+ enum { opt_eer = 256, opt_lt, opt_mer };
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 } };
+ { '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_eer, "empty-error", ap_no },
+ { opt_lt, "loose-trailing", ap_no },
+ { opt_mer, "marking-error", ap_no },
+ { 0, 0, ap_no } };
CRC32_init();
@@ -785,7 +818,7 @@ int main( const int argc, const char * const argv[] )
const char * const arg = ap_argument( &parser, argind );
switch( code )
{
- case 'a': ignore_trailing = false; break;
+ case 'a': cl_opts.ignore_trailing = false; break;
case 'c': to_stdout = true; break;
case 'd': set_mode( &program_mode, m_decompress ); break;
case 'f': force = true; break;
@@ -800,8 +833,10 @@ int main( const int argc, const char * const argv[] )
case 'u': buffer_size = get_dict_size( arg, pn ); 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." );
+ case opt_eer: cl_opts.ignore_empty = false; break;
+ case opt_lt: cl_opts.loose_trailing = true; break;
+ case opt_mer: cl_opts.ignore_marking = false; break;
+ default: internal_error( "uncaught option." );
}
} /* end process options */
@@ -823,7 +858,7 @@ int main( const int argc, const char * const argv[] )
}
if( program_mode == m_list )
- return list_files( filenames, num_filenames, ignore_trailing, loose_trailing );
+ return list_files( filenames, num_filenames, &cl_opts );
if( program_mode == m_compress )
program_mode = m_decompress; /* default mode */
@@ -881,7 +916,7 @@ int main( const int argc, const char * const argv[] )
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( one_to_one ) /* open outfd after checking infd */
{
set_d_outname( input_filename, extension_index( input_filename ) );
if( !open_outstream( force, true ) )
@@ -889,7 +924,7 @@ int main( const int argc, const char * const argv[] )
}
}
- if( to_file && outfd < 0 ) /* open outfd after verifying infd */
+ if( to_file && outfd < 0 ) /* open outfd after checking infd */
{
output_filename = resize_buffer( output_filename,
strlen( default_output_filename ) + 1 );
@@ -903,8 +938,8 @@ int main( const int argc, const char * const argv[] )
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",
+ fprintf( stderr, "%s: %s: Output file is not a regular file,\n"
+ " and 'low memory' mode has been requested.\n",
program_name, output_filename );
set_retval( &retval, 1 );
return retval; /* don't try to delete a non-regular file */
@@ -916,8 +951,8 @@ int main( const int argc, const char * const argv[] )
const unsigned long long cfile_size =
( input_filename[0] && S_ISREG( in_stats.st_mode ) ) ?
( in_stats.st_size + 99 ) / 100 : 0;
- int tmp = decompress( cfile_size, infd, &pp, buffer_size, ignore_trailing,
- loose_trailing, program_mode == m_test );
+ int tmp = decompress( cfile_size, infd, &cl_opts, &pp, buffer_size,
+ program_mode == m_test );
if( close( infd ) != 0 )
{ show_file_error( pp.name, "Error closing input file", errno );
set_retval( &tmp, 1 ); }