diff options
Diffstat (limited to 'main.cc')
-rw-r--r-- | main.cc | 236 |
1 files changed, 122 insertions, 114 deletions
@@ -1,5 +1,5 @@ /* Lziprecover - Data recovery tool for the lzip format - Copyright (C) 2009-2021 Antonio Diaz Diaz. + Copyright (C) 2009-2022 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 @@ -18,7 +18,7 @@ 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 lziprecover to panic. + (e.g., bug) which caused lziprecover to panic. */ #define _FILE_OFFSET_BITS 64 @@ -39,9 +39,9 @@ #include <unistd.h> #include <utime.h> #include <sys/stat.h> -#if defined(__MSVCRT__) || defined(__OS2__) || defined(__DJGPP__) +#if defined __MSVCRT__ || defined __OS2__ || defined __DJGPP__ #include <io.h> -#if defined(__MSVCRT__) +#if defined __MSVCRT__ #define fchmod(x,y) 0 #define fchown(x,y,z) 0 #define SIGHUP SIGTERM @@ -53,7 +53,7 @@ #define S_IWOTH 0 #endif #endif -#if defined(__DJGPP__) +#if defined __DJGPP__ #define S_ISSOCK(x) 0 #define S_ISVTX 0 #endif @@ -71,6 +71,11 @@ #error "Environments where CHAR_BIT != 8 are not supported." #endif +#if ( defined SIZE_MAX && SIZE_MAX < UINT_MAX ) || \ + ( defined SSIZE_MAX && SSIZE_MAX < INT_MAX ) +#error "Environments where 'size_t' is narrower than 'int' are not supported." +#endif + int verbosity = 0; const char * const program_name = "lziprecover"; @@ -89,7 +94,8 @@ const struct { const char * from; const char * to; } known_extensions[] = { enum Mode { m_none, m_alone_to_lz, m_debug_decompress, m_debug_delay, m_debug_repair, m_decompress, m_dump, m_list, m_md5sum, m_merge, m_nrep_stats, m_range_dec, m_remove, m_repair, m_reproduce, - m_show_packets, m_split, m_strip, m_test, m_unzcrash }; + m_show_packets, m_split, m_strip, m_test, m_unzcrash_bit, + m_unzcrash_block }; /* Variable used in signal handler context. It is not declared volatile because the handler never returns. */ @@ -100,14 +106,12 @@ void show_help() { std::printf( "Lziprecover is a data recovery tool and decompressor for files in the lzip\n" "compressed data format (.lz). Lziprecover is able to repair slightly damaged\n" - "files, produce a correct file by merging the good parts of two or more\n" - "damaged copies, reproduce a missing (zeroed) sector using a reference file,\n" - "extract data from damaged files, decompress files, and test integrity of\n" - "files.\n" - "\nLziprecover can repair perfectly most files with small errors (up to one\n" - "single-byte error per member), without the need of any extra redundance\n" - "at all. Losing an entire archive just because of a corrupt byte near the\n" - "beginning is a thing of the past.\n" + "files (up to one single-byte error per member), produce a correct file by\n" + "merging the good parts of two or more damaged copies, reproduce a missing\n" + "(zeroed) sector using a reference file, extract data from damaged files,\n" + "decompress files, and test integrity of files.\n" + "\nWith the help of lziprecover, losing an entire archive just because of a\n" + "corrupt byte near the beginning is a thing of the past.\n" "\nLziprecover can remove the damaged members from multimember files, for\n" "example multimember tar.lz archives.\n" "\nLziprecover provides random access to the data in multimember files; it only\n" @@ -150,7 +154,7 @@ void show_help() " -E, --debug-reproduce=<range>[,ss] set range to 0 and try to reproduce file\n" " -M, --md5sum print the MD5 digests of the input files\n" " -S, --nrep-stats[=<val>] print stats of N-byte repeated sequences\n" - " -U, --unzcrash test 1-bit errors in the input file\n" + " -U, --unzcrash=1|B<size> test 1-bit or block errors in input file\n" " -W, --debug-decompress=<pos>,<val> set pos to val and decompress to stdout\n" " -X, --show-packets[=<pos>,<val>] show in stdout the decoded LZMA packets\n" " -Y, --debug-delay=<range> find max error detection delay in <range>\n" @@ -164,7 +168,7 @@ void show_help() "'tar -xf foo.tar.lz' or 'lziprecover -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" + "invalid input file, 3 for an internal consistency error (e.g., bug) which\n" "caused lziprecover to panic.\n" "\nReport bugs to lzip-bug@nongnu.org\n" "Lziprecover home page: http://www.nongnu.org/lzip/lziprecover.html\n" ); @@ -174,16 +178,14 @@ void show_help() void Pretty_print::operator()( const char * const msg, FILE * const f ) const { - if( verbosity >= 0 ) + if( verbosity < 0 ) return; + if( first_post ) { - if( first_post ) - { - first_post = false; - std::fputs( padded_name.c_str(), f ); - if( !msg ) std::fflush( f ); - } - if( msg ) std::fprintf( f, "%s\n", msg ); + first_post = false; + std::fputs( padded_name.c_str(), f ); + if( !msg ) std::fflush( f ); } + if( msg ) std::fprintf( f, "%s\n", msg ); } @@ -225,41 +227,41 @@ void show_header( const unsigned dictionary_size ) // Colon-separated list of "damaged", "tdata", [r][^]<list> (1 1,3-5,8) -void Member_list::parse( const char * p ) +void Member_list::parse_ml( const char * arg, const char * const option_name ) { while( true ) { - const char * tp = p; // points to terminator; ':' or null + const char * tp = arg; // points to terminator (':' or '\0') while( *tp && *tp != ':' ) ++tp; - const unsigned len = tp - p; - if( std::isalpha( *(const unsigned char *)p ) ) + const unsigned len = tp - arg; + if( std::islower( *(const unsigned char *)arg ) ) { - if( len <= 7 && std::strncmp( "damaged", p, len ) == 0 ) + if( len <= 7 && std::strncmp( "damaged", arg, len ) == 0 ) { damaged = true; goto next; } - if( len <= 5 && std::strncmp( "tdata", p, len ) == 0 ) + if( len <= 5 && std::strncmp( "tdata", arg, len ) == 0 ) { tdata = true; goto next; } } { - const bool reverse = ( *p == 'r' ); - if( reverse ) ++p; - if( *p == '^' ) { ++p; if( reverse ) rin = false; else in = false; } + const bool reverse = ( *arg == 'r' ); + if( reverse ) ++arg; + if( *arg == '^' ) { ++arg; if( reverse ) rin = false; else in = false; } std::vector< Block > * rvp = reverse ? &rrange_vector : &range_vector; - while( std::isdigit( *(const unsigned char *)p ) ) + while( std::isdigit( *(const unsigned char *)arg ) ) { const char * tail; - const int pos = getnum( p, 0, 1, INT_MAX, &tail ) - 1; + const int pos = getnum( arg, option_name, 0, 1, INT_MAX, &tail ) - 1; if( rvp->size() && pos < rvp->back().end() ) break; const int size = (*tail == '-') ? - getnum( tail + 1, 0, pos + 1, INT_MAX, &tail ) - pos : 1; + getnum( tail + 1, option_name, 0, pos + 1, INT_MAX, &tail ) - pos : 1; rvp->push_back( Block( pos, size ) ); if( tail == tp ) goto next; - if( *tail == ',' ) p = tail + 1; else break; + if( *tail == ',' ) arg = tail + 1; else break; } } show_error( "Invalid list of members." ); std::exit( 1 ); next: - if( *(p = tp) != 0 ) ++p; else return; + if( *(arg = tp) != 0 ) ++arg; else return; } } @@ -268,70 +270,60 @@ namespace { // Recognized formats: <digit> 'a' m[<match_length>] // -int parse_lzip_level( const char * const p ) +int parse_lzip_level( const char * const arg, const char * const option_name ) { - if( *p == 'a' || std::isdigit( *(const unsigned char *)p ) ) return *p; - if( *p != 'm' ) + if( *arg == 'a' || std::isdigit( *(const unsigned char *)arg ) ) return *arg; + if( *arg != 'm' ) { - show_error( "Bad argument in option '--lzip-level'.", 0, true ); + if( verbosity >= 0 ) + std::fprintf( stderr, "%s: Bad argument in option '%s'.\n", + program_name, option_name ); std::exit( 1 ); } - if( p[1] == 0 ) return -1; - return -getnum( p + 1, 0, min_match_len_limit, max_match_len ); + if( arg[1] == 0 ) return -1; + return -getnum( arg + 1, option_name, 0, min_match_len_limit, max_match_len ); } /* Recognized format: <range>[,<sector_size>] range formats: <begin> <begin>-<end> <begin>,<size> ,<size> */ -void parse_range( const char * const ptr, Block & range, - int * const sector_sizep = 0 ) +void parse_range( const char * const arg, const char * const pn, + Block & range, int * const sector_sizep = 0 ) { - const char * tail = ptr; + const char * tail = arg; long long value = - ( ptr[0] == ',' ) ? 0 : getnum( ptr, 0, 0, INT64_MAX - 1, &tail ); + ( arg[0] == ',' ) ? 0 : getnum( arg, pn, 0, 0, INT64_MAX - 1, &tail ); if( tail[0] == 0 || tail[0] == ',' || tail[0] == '-' ) { range.pos( value ); if( tail[0] == 0 ) { range.size( INT64_MAX - value ); return; } const bool is_size = ( tail[0] == ',' ); if( sector_sizep && tail[1] == ',' ) { value = INT64_MAX - value; ++tail; } - else value = getnum( tail + 1, 0, 1, INT64_MAX, &tail ); // size - if( is_size || value > range.pos() ) + else value = getnum( tail + 1, pn, 0, 1, INT64_MAX, &tail ); // size + if( !is_size && value <= range.pos() ) { - if( !is_size ) value -= range.pos(); - if( INT64_MAX - range.pos() >= value ) - { - range.size( value ); - if( sector_sizep && tail[0] == ',' ) - *sector_sizep = getnum( tail + 1, 0, 8, INT_MAX ); - return; - } + if( verbosity >= 0 ) + std::fprintf( stderr, "%s: Begin must be < end in range argument " + "of option '%s'.\n", program_name, pn ); + std::exit( 1 ); + } + if( !is_size ) value -= range.pos(); + if( INT64_MAX - value >= range.pos() ) + { + range.size( value ); + if( sector_sizep && tail[0] == ',' ) + *sector_sizep = getnum( tail + 1, pn, 0, 8, INT_MAX ); + return; } } - show_error( "Bad decompression range.", 0, true ); + if( verbosity >= 0 ) + std::fprintf( stderr, "%s: Bad decompression range in option '%s'.\n", + program_name, pn ); std::exit( 1 ); } -// Recognized formats: <pos>,<value> <pos>,+<value> <pos>,f<value> -// -void parse_pos_value( const char * const ptr, Bad_byte & bad_byte ) - { - const char * tail; - bad_byte.pos = getnum( ptr, 0, 0, INT64_MAX, &tail ); - if( tail[0] != ',' ) - { - show_error( "Bad separator between <pos> and <val>.", 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, 0, 255 ); - } - - void one_file( const int files ) { if( files != 1 ) @@ -353,6 +345,23 @@ void set_mode( Mode & program_mode, const Mode new_mode ) } +void parse_u( const char * const arg, const char * const option_name, + Mode & program_mode, int & sector_size ) + { + if( arg[0] == '1' ) set_mode( program_mode, m_unzcrash_bit ); + else if( arg[0] == 'B' ) + { set_mode( program_mode, m_unzcrash_block ); + sector_size = getnum( arg + 1, option_name, 0, 1, INT_MAX ); } + else + { + if( verbosity >= 0 ) + std::fprintf( stderr, "%s: Bad argument for option '%s'.\n", + program_name, option_name ); + std::exit( 1 ); + } + } + + int extension_index( const std::string & name ) { for( int eindex = 0; known_extensions[eindex].from; ++eindex ) @@ -506,6 +515,17 @@ void cleanup_and_fail( const int retval ) std::exit( retval ); } + +bool check_tty_out() + { + if( isatty( outfd ) ) + { show_file_error( output_filename.size() ? + output_filename.c_str() : "(stdout)", + "I won't write compressed data to a terminal." ); + return false; } + return true; + } + namespace { extern "C" void signal_handler( int ) @@ -521,21 +541,14 @@ bool check_tty_in( const char * const input_filename, const int infd, if( isatty( infd ) ) // all modes read compressed data { show_file_error( input_filename, "I won't read compressed data from a terminal." ); - close( infd ); set_retval( retval, 1 ); + close( infd ); set_retval( retval, 2 ); if( program_mode != m_test ) cleanup_and_fail( retval ); return false; } return true; } bool check_tty_out( const Mode program_mode ) - { - if( program_mode == m_alone_to_lz && isatty( outfd ) ) - { show_file_error( output_filename.size() ? - output_filename.c_str() : "(stdout)", - "I won't write compressed data to a terminal." ); - return false; } - return true; - } + { return program_mode != m_alone_to_lz || ::check_tty_out(); } // Set permissions, owner, and times. @@ -611,9 +624,10 @@ int decompress( const unsigned long long cfile_size, const int infd, const bool ignore_trailing, const bool loose_trailing, const bool testing ) { - int retval = 0; unsigned long long partial_file_pos = 0; Range_decoder rdec( infd ); + int retval = 0; + for( bool first_member = true; ; first_member = false ) { Lzip_header header; @@ -708,16 +722,6 @@ std::string insert_fixed( std::string name ) } -void show_file_error( const char * const filename, const char * const msg, - const int errcode ) - { - if( verbosity >= 0 ) - std::fprintf( stderr, "%s: %s: %s%s%s\n", program_name, filename, msg, - ( errcode > 0 ) ? ": " : "", - ( errcode > 0 ) ? std::strerror( errcode ) : "" ); - } - - void show_2file_error( const char * const msg1, const char * const name1, const char * const name2, const char * const msg2 ) { @@ -765,7 +769,6 @@ int main( const int argc, const char * const argv[] ) Bad_byte bad_byte; Member_list member_list; std::string default_output_filename; - std::vector< std::string > filenames; const char * lzip_name = "lzip"; // default is lzip const char * reference_filename = 0; Mode program_mode = m_none; @@ -805,7 +808,7 @@ int main( const int argc, const char * const argv[] ) { 's', "split", Arg_parser::no }, { 'S', "nrep-stats", Arg_parser::maybe }, { 't', "test", Arg_parser::no }, - { 'U', "unzcrash", Arg_parser::no }, + { 'U', "unzcrash", Arg_parser::yes }, { 'v', "verbose", Arg_parser::no }, { 'V', "version", Arg_parser::no }, { 'W', "debug-decompress", Arg_parser::yes }, @@ -830,6 +833,7 @@ int main( const int argc, const char * const argv[] ) { const int code = parser.code( argind ); if( !code ) break; // no more options + const char * const pn = parser.parsed_name( argind ).c_str(); const std::string & sarg = parser.argument( argind ); const char * const arg = sarg.c_str(); switch( code ) @@ -839,10 +843,10 @@ 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( arg, range ); break; + parse_range( arg, pn, range ); break; case 'e': set_mode( program_mode, m_reproduce ); break; case 'E': set_mode( program_mode, m_reproduce ); - parse_range( arg, range, §or_size ); break; + parse_range( arg, pn, range, §or_size ); break; case 'f': force = true; break; case 'h': show_help(); return 0; case 'i': ignore_errors = true; break; @@ -856,35 +860,35 @@ int main( const int argc, const char * const argv[] ) case 'q': verbosity = -1; break; case 'R': set_mode( program_mode, m_repair ); break; case 's': set_mode( program_mode, m_split ); break; - case 'S': if( arg[0] ) repeated_byte = getnum( arg, 0, 0, 255 ); + case 'S': if( arg[0] ) repeated_byte = getnum( arg, pn, 0, 0, 255 ); set_mode( program_mode, m_nrep_stats ); break; case 't': set_mode( program_mode, m_test ); break; - case 'U': set_mode( program_mode, m_unzcrash ); break; + case 'U': parse_u( arg, pn, program_mode, sector_size ); break; 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( arg, bad_byte ); break; + bad_byte.parse_bb( arg, pn ); break; case 'X': set_mode( program_mode, m_show_packets ); - if( arg[0] ) { parse_pos_value( arg, bad_byte ); } break; + if( arg[0] ) { bad_byte.parse_bb( arg, pn ); } break; case 'Y': set_mode( program_mode, m_debug_delay ); - parse_range( arg, range ); break; + parse_range( arg, pn, range ); break; case 'Z': set_mode( program_mode, m_debug_repair ); - parse_pos_value( arg, bad_byte ); break; + bad_byte.parse_bb( arg, pn ); break; case opt_du: set_mode( program_mode, m_dump ); - member_list.parse( arg ); break; + member_list.parse_ml( arg, pn ); break; case opt_lt: loose_trailing = true; break; - case opt_lzl: lzip_level = parse_lzip_level( arg ); break; + case opt_lzl: lzip_level = parse_lzip_level( arg, pn ); break; case opt_lzn: lzip_name = arg; break; case opt_ref: reference_filename = arg; break; case opt_re: set_mode( program_mode, m_remove ); - member_list.parse( arg ); break; + member_list.parse_ml( arg, pn ); break; case opt_st: set_mode( program_mode, m_strip ); - member_list.parse( arg ); break; + member_list.parse_ml( arg, pn ); break; default : internal_error( "uncaught option." ); } } // end process options -#if defined(__MSVCRT__) || defined(__OS2__) || defined(__DJGPP__) +#if defined __MSVCRT__ || defined __OS2__ || defined __DJGPP__ setmode( STDIN_FILENO, O_BINARY ); setmode( STDOUT_FILENO, O_BINARY ); #endif @@ -895,6 +899,7 @@ int main( const int argc, const char * const argv[] ) return 1; } + std::vector< std::string > filenames; bool filenames_given = false; for( ; argind < parser.arguments(); ++argind ) { @@ -963,9 +968,12 @@ int main( const int argc, const char * const argv[] ) one_file( filenames.size() ); return split_file( filenames[0], default_output_filename, force ); case m_test: break; - case m_unzcrash: + case m_unzcrash_bit: + one_file( filenames.size() ); + return lunzcrash_bit( filenames[0].c_str() ); + case m_unzcrash_block: one_file( filenames.size() ); - return lunzcrash( filenames[0] ); + return lunzcrash_block( filenames[0].c_str(), sector_size ); } } catch( std::bad_alloc & ) { show_error( mem_msg ); cleanup_and_fail( 1 ); } |