1
0
Fork 0

Merging upstream version 1.19.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-02-21 11:27:58 +01:00
parent 5eba8c739c
commit 9abb114ebc
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
31 changed files with 1468 additions and 963 deletions

277
main.cc
View file

@ -1,5 +1,5 @@
/* Lziprecover - Data recovery tool for the lzip format
Copyright (C) 2009-2016 Antonio Diaz Diaz.
Copyright (C) 2009-2017 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
@ -73,10 +73,10 @@ namespace {
const char * const Program_name = "Lziprecover";
const char * const program_name = "lziprecover";
const char * const program_year = "2016";
const char * const program_year = "2017";
const char * invocation_name = 0;
struct { const char * from; const char * to; } const known_extensions[] = {
const struct { const char * from; const char * to; } known_extensions[] = {
{ ".lz", "" },
{ ".tlz", ".tar" },
{ 0, 0 } };
@ -99,6 +99,8 @@ void show_help()
"\nLziprecover can also produce a correct file by merging the good parts of\n"
"two or more damaged copies, extract data from damaged files, decompress\n"
"files and test integrity of files.\n"
"\nLziprecover provides random access to the data in multimember files; it\n"
"only decompresses the members containing the desired data.\n"
"\nLziprecover is not a replacement for regular backups, but a last line of\n"
"defense for the case where the backups are also damaged.\n"
"\nUsage: %s [options] [files]\n", invocation_name );
@ -113,7 +115,7 @@ void show_help()
" -f, --force overwrite existing output files\n"
" -i, --ignore-errors make '--range-decompress' ignore data errors\n"
" -k, --keep keep (don't delete) input files\n"
" -l, --list print total file sizes and ratios\n"
" -l, --list print (un)compressed file sizes\n"
" -m, --merge correct errors in file using several copies\n"
" -o, --output=<file> place the output into <file>\n"
" -q, --quiet suppress all messages\n"
@ -152,42 +154,64 @@ void show_version()
} // end namespace
const char * bad_version( const unsigned version )
{
static char buf[80];
snprintf( buf, sizeof buf, "Version %u member format not supported.",
version );
return buf;
}
const char * format_ds( const unsigned dictionary_size )
{
enum { bufsize = 16, factor = 1024 };
static char buf[bufsize];
const char * const prefix[8] =
{ "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi", "Yi" };
const char * p = "";
const char * np = " ";
unsigned num = dictionary_size;
bool exact = ( num % factor == 0 );
for( int i = 0; i < 8 && ( num > 9999 || ( exact && num >= factor ) ); ++i )
{ num /= factor; if( num % factor != 0 ) exact = false;
p = prefix[i]; np = ""; }
snprintf( buf, bufsize, "%s%4u %sB", np, num, p );
return buf;
}
void show_header( const unsigned dictionary_size, const int vlevel )
{
if( verbosity >= vlevel )
{
const char * const prefix[8] =
{ "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi", "Yi" };
enum { factor = 1024 };
const char * p = "";
const char * np = " ";
unsigned num = dictionary_size;
bool exact = ( num % factor == 0 );
for( int i = 0; i < 8 && ( num > 9999 || ( exact && num >= factor ) ); ++i )
{ num /= factor; if( num % factor != 0 ) exact = false;
p = prefix[i]; np = ""; }
std::fprintf( stderr, "dictionary size %s%4u %sB. ", np, num, p );
}
std::fprintf( stderr, "dictionary %s. ", format_ds( dictionary_size ) );
}
namespace {
// Returns the number of chars read, or 0 if error.
//
int parse_long_long( const char * const ptr, long long & value )
long long getnum( const char * const ptr, const long long llimit = -LLONG_MAX,
const long long ulimit = LLONG_MAX,
const char ** const tailp = 0 )
{
char * tail;
errno = 0;
value = strtoll( ptr, &tail, 0 );
if( tail == ptr || errno || value < 0 ) return 0;
int c = tail - ptr;
if( ptr[c] )
long long result = strtoll( ptr, &tail, 0 );
if( tail == ptr )
{
const int factor = ( ptr[c+1] == 'i' ) ? 1024 : 1000;
int exponent = 0;
switch( ptr[c] )
show_error( "Bad or missing numerical argument.", 0, true );
std::exit( 1 );
}
if( !errno && tail[0] )
{
char * const p = tail++;
int factor;
bool bsuf; // 'B' suffix is present
if( tail[0] == 'i' ) { ++tail; factor = 1024; } else factor = 1000;
if( tail[0] == 'B' ) { ++tail; bsuf = true; } else bsuf = false;
int exponent = -1; // -1 = bad multiplier
switch( *p )
{
case 'Y': exponent = 8; break;
case 'Z': exponent = 7; break;
@ -196,22 +220,30 @@ int parse_long_long( const char * const ptr, long long & value )
case 'T': exponent = 4; break;
case 'G': exponent = 3; break;
case 'M': exponent = 2; break;
case 'K': if( factor == 1024 ) exponent = 1; else return 0; break;
case 'k': if( factor == 1000 ) exponent = 1; else return 0; break;
case 'K': if( factor == 1024 ) exponent = 1; break;
case 'k': if( factor == 1000 ) exponent = 1; break;
case 'B': if( factor == 1000 && !bsuf ) exponent = 0; break;
default : if( tailp ) { tail = p; exponent = 0; } break;
}
if( exponent > 0 )
if( exponent < 0 )
{
++c;
if( ptr[c] == 'i' ) { ++c; if( value ) format_num( 0, 0, -1 ); }
if( ptr[c] == 'B' ) ++c;
for( int i = 0; i < exponent; ++i )
{
if( INT64_MAX / factor >= value ) value *= factor;
else return 0;
}
show_error( "Bad multiplier in numerical argument.", 0, true );
std::exit( 1 );
}
for( int i = 0; i < exponent; ++i )
{
if( LLONG_MAX / factor >= std::labs( result ) ) result *= factor;
else { errno = ERANGE; break; }
}
}
return c;
if( !errno && ( result < llimit || result > ulimit ) ) errno = ERANGE;
if( errno )
{
show_error( "Numerical argument out of limits." );
std::exit( 1 );
}
if( tailp ) *tailp = tail;
return result;
}
@ -219,17 +251,16 @@ int parse_long_long( const char * const ptr, long long & value )
//
void parse_range( const char * const ptr, Block & range )
{
long long value = 0;
const bool size_only = ( ptr[0] == ',' );
int c = size_only ? 0 : parse_long_long( ptr, value ); // pos
if( size_only || ( c && value >= 0 && value < INT64_MAX &&
( ptr[c] == 0 || ptr[c] == ',' || ptr[c] == '-' ) ) )
const char * tail = ptr;
long long value =
( ptr[0] == ',' ) ? 0 : getnum( ptr, 0, INT64_MAX - 1, &tail );
if( tail[0] == 0 || tail[0] == ',' || tail[0] == '-' )
{
range.pos( value );
if( ptr[c] == 0 ) { range.size( INT64_MAX - value ); return; }
const bool issize = ( ptr[c] == ',' );
c = parse_long_long( ptr + c + 1, value ); // size
if( c && value > 0 && ( issize || value > range.pos() ) )
if( tail[0] == 0 ) { range.size( INT64_MAX - value ); return; }
const bool issize = ( tail[0] == ',' );
value = getnum( tail + 1, 1, INT64_MAX ); // size
if( issize || value > range.pos() )
{
if( !issize ) value -= range.pos();
if( INT64_MAX - range.pos() >= value ) { range.size( value ); return; }
@ -240,21 +271,21 @@ void parse_range( const char * const ptr, Block & range )
}
// Recognized format: <pos>,<value>
// Recognized formats: <pos>,<value> <pos>,+<value> <pos>,f<value>
//
void parse_pos_value( const char * const ptr, long long & pos, uint8_t & value )
void parse_pos_value( const char * const ptr, Bad_byte & bad_byte )
{
long long val = 0;
int c = parse_long_long( ptr, val ); // pos
if( c && val >= 0 && val < INT64_MAX && ptr[c] == ',' )
const char * tail;
bad_byte.pos = getnum( ptr, 0, INT64_MAX, &tail );
if( tail[0] != ',' )
{
pos = val;
c = parse_long_long( ptr + c + 1, val ); // value
if( c && val >= 0 && val < 256 )
{ value = val; return; }
show_error( "Bad separator between <pos> and <val>.", 0, true );
std::exit( 1 );
}
show_error( "Bad file position or byte value.", 0, true );
std::exit( 1 );
if( tail[1] == '+' ) { ++tail; bad_byte.mode = Bad_byte::delta; }
else if( tail[1] == 'f' ) { ++tail; bad_byte.mode = Bad_byte::flip; }
else bad_byte.mode = Bad_byte::literal;
bad_byte.value = getnum( tail + 1, 0, 255 );
}
@ -281,12 +312,12 @@ void set_mode( Mode & program_mode, const Mode new_mode )
int extension_index( const std::string & name )
{
for( int i = 0; known_extensions[i].from; ++i )
for( int eindex = 0; known_extensions[eindex].from; ++eindex )
{
const std::string ext( known_extensions[i].from );
const std::string ext( known_extensions[eindex].from );
if( name.size() > ext.size() &&
name.compare( name.size() - ext.size(), ext.size(), ext ) == 0 )
return i;
return eindex;
}
return -1;
}
@ -298,11 +329,7 @@ int open_instream( const char * const name, struct stat * const in_statsp,
{
int infd = open( name, O_RDONLY | O_BINARY );
if( infd < 0 )
{
if( verbosity >= 0 )
std::fprintf( stderr, "%s: Can't open input file '%s': %s\n",
program_name, name, std::strerror( errno ) );
}
show_file_error( name, "Can't open input file", errno );
else
{
const int i = fstat( infd, in_statsp );
@ -338,15 +365,15 @@ void set_a_outname( const std::string & name )
}
void set_d_outname( const std::string & name, const int i )
void set_d_outname( const std::string & name, const int eindex )
{
if( i >= 0 )
if( eindex >= 0 )
{
const std::string from( known_extensions[i].from );
const std::string from( known_extensions[eindex].from );
if( name.size() > from.size() )
{
output_filename.assign( name, 0, name.size() - from.size() );
output_filename += known_extensions[i].to;
output_filename += known_extensions[eindex].to;
return;
}
}
@ -398,7 +425,8 @@ bool file_exists( const std::string & filename )
}
bool check_tty( const int infd, const Mode program_mode )
bool check_tty( const char * const input_filename, const int infd,
const Mode program_mode )
{
if( program_mode == m_alone_to_lz && isatty( outfd ) )
{
@ -407,7 +435,8 @@ bool check_tty( const int infd, const Mode program_mode )
}
if( isatty( infd ) ) // all modes read compressed data
{
show_error( "I won't read compressed data from a terminal.", 0, true );
show_file_error( input_filename,
"I won't read compressed data from a terminal." );
return false;
}
return true;
@ -465,10 +494,10 @@ void close_and_set_permissions( const struct stat * const in_statsp )
}
unsigned char xdigit( const int value )
unsigned char xdigit( const unsigned value )
{
if( value >= 0 && value <= 9 ) return '0' + value;
if( value >= 10 && value <= 15 ) return 'A' + value - 10;
if( value <= 9 ) return '0' + value;
if( value <= 15 ) return 'A' + value - 10;
return 0;
}
@ -482,26 +511,18 @@ bool show_trailing_data( const uint8_t * const data, const int size,
std::string msg;
if( !all ) msg = "first bytes of ";
msg += "trailing data = ";
bool text = true;
for( int i = 0; i < size; ++i )
if( !std::isprint( data[i] ) ) { text = false; break; }
if( text )
{
msg += '\'';
msg.append( (const char *)data, size );
msg += '\'';
}
else
{
for( int i = 0; i < size; ++i )
{
if( i > 0 ) msg += ' ';
msg += xdigit( data[i] >> 4 );
msg += xdigit( data[i] & 0x0F );
}
msg += xdigit( data[i] >> 4 );
msg += xdigit( data[i] & 0x0F );
msg += ' ';
}
msg += '\'';
for( int i = 0; i < size; ++i )
{ if( std::isprint( data[i] ) ) msg += data[i]; else msg += '.'; }
msg += '\'';
pp( msg.c_str() );
if( !ignore_trailing ) show_error( "Trailing data not allowed." );
if( !ignore_trailing ) show_file_error( pp.name(), trailing_msg );
}
return ignore_trailing;
}
@ -532,22 +553,16 @@ int decompress( const int infd, const Pretty_print & pp,
if( !header.verify_magic() )
{
if( first_member )
{ pp( "Bad magic number (file not in lzip format)." ); retval = 2; }
{ show_file_error( pp.name(), bad_magic_msg ); retval = 2; }
else if( !show_trailing_data( header.data, size, pp, false, ignore_trailing ) )
retval = 2;
break;
}
if( !header.verify_version() )
{
if( verbosity >= 0 )
{ pp();
std::fprintf( stderr, "Version %d member format not supported.\n",
header.version() ); }
retval = 2; break;
}
{ pp( bad_version( header.version() ) ); retval = 2; break; }
const unsigned dictionary_size = header.dictionary_size();
if( !isvalid_ds( dictionary_size ) )
{ pp( "Invalid dictionary size in member header." ); retval = 2; break; }
{ pp( bad_dict_msg ); retval = 2; break; }
if( verbosity >= 2 || ( verbosity == 1 && first_member ) )
{ pp(); show_header( dictionary_size ); }
@ -634,6 +649,16 @@ void show_error( const char * const msg, const int errcode, const bool help )
}
void show_file_error( const char * const filename, const char * const msg,
const int errcode )
{
if( verbosity < 0 ) return;
std::fprintf( stderr, "%s: %s: %s", program_name, filename, msg );
if( errcode > 0 ) std::fprintf( stderr, ": %s", std::strerror( errcode ) );
std::fputc( '\n', stderr );
}
void internal_error( const char * const msg )
{
if( verbosity >= 0 )
@ -662,13 +687,11 @@ void show_error4( const char * const msg1, const char * const name1,
int main( const int argc, const char * const argv[] )
{
Block range( 0, 0 );
long long bad_pos = -1;
std::string input_filename;
Bad_byte bad_byte;
std::string default_output_filename;
std::vector< std::string > filenames;
int infd = -1;
Mode program_mode = m_none;
uint8_t bad_value = 0;
bool force = false;
bool ignore_errors = false;
bool ignore_trailing = true;
@ -712,8 +735,8 @@ int main( const int argc, const char * const argv[] )
{
const int code = parser.code( argind );
if( !code ) break; // no more options
const std::string & arg = parser.argument( argind );
const char * const ptr = arg.c_str();
const std::string & sarg = parser.argument( argind );
const char * const arg = sarg.c_str();
switch( code )
{
case 'a': ignore_trailing = false; break;
@ -721,7 +744,7 @@ int main( const int argc, const char * const argv[] )
case 'c': to_stdout = true; break;
case 'd': set_mode( program_mode, m_decompress ); break;
case 'D': set_mode( program_mode, m_range_dec );
parse_range( ptr, range ); break;
parse_range( arg, range ); break;
case 'f': force = true; break;
case 'h': show_help(); return 0;
case 'i': ignore_errors = true; break;
@ -729,7 +752,7 @@ int main( const int argc, const char * const argv[] )
case 'l': set_mode( program_mode, m_list ); break;
case 'm': set_mode( program_mode, m_merge ); break;
case 'n': break;
case 'o': default_output_filename = arg; break;
case 'o': default_output_filename = sarg; break;
case 'q': verbosity = -1; break;
case 'R': set_mode( program_mode, m_repair ); break;
case 's': set_mode( program_mode, m_split ); break;
@ -737,13 +760,13 @@ int main( const int argc, const char * const argv[] )
case 'v': if( verbosity < 4 ) ++verbosity; break;
case 'V': show_version(); return 0;
case 'W': set_mode( program_mode, m_debug_decompress );
parse_pos_value( ptr, bad_pos, bad_value ); break;
parse_pos_value( arg, bad_byte ); break;
case 'X': set_mode( program_mode, m_show_packets );
if( ptr[0] ) parse_pos_value( ptr, bad_pos, bad_value ); break;
if( arg[0] ) { parse_pos_value( arg, bad_byte ); } break;
case 'Y': set_mode( program_mode, m_debug_delay );
parse_range( ptr, range ); break;
parse_range( arg, range ); break;
case 'Z': set_mode( program_mode, m_debug_repair );
parse_pos_value( ptr, bad_pos, bad_value ); break;
parse_pos_value( arg, bad_byte ); break;
default : internal_error( "uncaught option." );
}
} // end process options
@ -773,18 +796,15 @@ int main( const int argc, const char * const argv[] )
case m_alone_to_lz: break;
case m_debug_decompress:
one_file( filenames.size() );
return debug_decompress( filenames[0], bad_pos, verbosity, bad_value, false );
return debug_decompress( filenames[0], bad_byte, verbosity, false );
case m_debug_delay:
one_file( filenames.size() );
return debug_delay( filenames[0], range, verbosity );
case m_debug_repair:
one_file( filenames.size() );
return debug_repair( filenames[0], bad_pos, verbosity, bad_value );
return debug_repair( filenames[0], bad_byte, verbosity );
case m_decompress: break;
case m_list:
if( filenames.size() < 1 )
{ show_error( "You must specify at least 1 file.", 0, true ); return 1; }
return list_files( filenames, verbosity );
case m_list: break;
case m_merge:
if( filenames.size() < 2 )
{ show_error( "You must specify at least 2 files.", 0, true ); return 1; }
@ -794,7 +814,7 @@ int main( const int argc, const char * const argv[] )
one_file( filenames.size() );
set_signals();
return range_decompress( filenames[0], default_output_filename, range,
verbosity, force, ignore_errors, to_stdout );
verbosity, force, ignore_errors, ignore_trailing, to_stdout );
case m_repair:
one_file( filenames.size() );
set_signals();
@ -802,7 +822,7 @@ int main( const int argc, const char * const argv[] )
force );
case m_show_packets:
one_file( filenames.size() );
return debug_decompress( filenames[0], bad_pos, verbosity, bad_value, true );
return debug_decompress( filenames[0], bad_byte, verbosity, true );
case m_split:
one_file( filenames.size() );
set_signals();
@ -814,12 +834,16 @@ int main( const int argc, const char * const argv[] )
{ show_error( "Not enough memory." ); cleanup_and_fail( 1 ); }
catch( Error e ) { show_error( e.msg, errno ); cleanup_and_fail( 1 ); }
if( filenames.empty() ) filenames.push_back("-");
if( program_mode == m_list )
return list_files( filenames, verbosity, ignore_trailing );
if( program_mode == m_test )
outfd = -1;
else if( program_mode != m_alone_to_lz && program_mode != m_decompress )
internal_error( "invalid decompressor operation." );
if( filenames.empty() ) filenames.push_back("-");
if( !to_stdout && program_mode != m_test &&
( filenames_given || default_output_filename.size() ) )
set_signals();
@ -830,13 +854,13 @@ int main( const int argc, const char * const argv[] )
bool stdin_used = false;
for( unsigned i = 0; i < filenames.size(); ++i )
{
std::string input_filename;
struct stat in_stats;
output_filename.clear();
if( filenames[i].empty() || filenames[i] == "-" )
{
if( stdin_used ) continue; else stdin_used = true;
input_filename.clear();
infd = STDIN_FILENO;
if( program_mode != m_test )
{
@ -881,14 +905,15 @@ int main( const int argc, const char * const argv[] )
}
}
if( !check_tty( infd, program_mode ) )
pp.set_name( input_filename );
if( !check_tty( pp.name(), infd, program_mode ) )
{
if( retval < 1 ) retval = 1;
if( program_mode == m_test ) { close( infd ); infd = -1; continue; }
cleanup_and_fail( retval );
}
const struct stat * const in_statsp = input_filename.size() ? &in_stats : 0;
pp.set_name( input_filename );
int tmp;
if( program_mode == m_alone_to_lz )
tmp = alone_to_lz( infd, pp );