diff options
Diffstat (limited to 'main.cc')
-rw-r--r-- | main.cc | 275 |
1 files changed, 167 insertions, 108 deletions
@@ -1,5 +1,5 @@ /* Lziprecover - Data recovery tool for the lzip format - Copyright (C) 2009-2018 Antonio Diaz Diaz. + Copyright (C) 2009-2019 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,25 +38,29 @@ #include <unistd.h> #include <utime.h> #include <sys/stat.h> -#if defined(__MSVCRT__) +#if defined(__MSVCRT__) || defined(__OS2__) || defined(__DJGPP__) #include <io.h> +#if defined(__MSVCRT__) #define fchmod(x,y) 0 #define fchown(x,y,z) 0 #define SIGHUP SIGTERM #define S_ISSOCK(x) 0 +#ifndef S_IRGRP #define S_IRGRP 0 #define S_IWGRP 0 #define S_IROTH 0 #define S_IWOTH 0 #endif -#if defined(__OS2__) -#include <io.h> +#endif +#if defined(__DJGPP__) +#define S_ISSOCK(x) 0 +#define S_ISVTX 0 +#endif #endif #include "arg_parser.h" #include "lzip.h" #include "decoder.h" -#include "block.h" #ifndef O_BINARY #define O_BINARY 0 @@ -67,12 +71,11 @@ #endif int verbosity = 0; -std::string output_filename; // global vars for output file -int outfd = -1; +std::string output_filename; // global vars for output file +int outfd = -1; // see 'delete_output_on_interrupt' below namespace { -const char * const Program_name = "Lziprecover"; const char * const program_name = "lziprecover"; const char * invocation_name = 0; @@ -82,53 +85,58 @@ const struct { const char * from; const char * to; } known_extensions[] = { { 0, 0 } }; enum Mode { m_none, m_alone_to_lz, m_debug_decompress, m_debug_delay, - m_debug_repair, m_decompress, m_dump_tdata, m_list, m_merge, - m_range_dec, m_remove_tdata, m_repair, m_show_packets, m_split, - m_strip_tdata, m_test }; + m_debug_repair, m_decompress, m_dump, m_list, m_merge, + m_range_dec, m_remove, m_repair, m_show_packets, m_split, + m_strip, m_test }; +/* Variable used in signal handler context. + It is not declared volatile because the handler never returns. */ bool delete_output_on_interrupt = false; void show_help() { - std::printf( "%s - Data recovery tool and decompressor for the lzip format.\n", Program_name ); - std::printf( "\nLziprecover can repair perfectly most files with small errors (up to one\n" + 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, extract data from damaged files, decompress files and test\n" + "integrity of 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" - "\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 facilitates the management of metadata stored as trailing\n" - "data in lzip files.\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" + "decompresses the members containing the desired data.\n" + "\nLziprecover facilitates the management of metadata stored as trailing data\n" + "in lzip files.\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 ); std::printf( "\nOptions:\n" - " -h, --help display this help and exit\n" - " -V, --version output version information and exit\n" - " -a, --trailing-error exit with error status if trailing data\n" - " -A, --alone-to-lz convert lzma-alone files to lzip format\n" - " -c, --stdout write to standard output, keep input files\n" - " -d, --decompress decompress\n" - " -D, --range-decompress=<range> decompress a range of bytes (N-M) to stdout\n" - " -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 (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" - " -R, --repair try to repair a small error in file\n" - " -s, --split split multimember file in single-member files\n" - " -t, --test test compressed file integrity\n" - " -v, --verbose be verbose (a 2nd -v gives more)\n" - " --loose-trailing allow trailing data seeming corrupt header\n" - " --dump-tdata dump trailing data to standard output\n" - " --remove-tdata remove trailing data from files in place\n" - " --strip-tdata copy files to stdout without trailing data\n" ); + " -h, --help display this help and exit\n" + " -V, --version output version information and exit\n" + " -a, --trailing-error exit with error status if trailing data\n" + " -A, --alone-to-lz convert lzma-alone files to lzip format\n" + " -c, --stdout write to standard output, keep input files\n" + " -d, --decompress decompress\n" + " -D, --range-decompress=<n-m> decompress a range of bytes to stdout\n" + " -f, --force overwrite existing output files\n" + " -i, --ignore-errors all errors in -D, format errors in -l, --dump\n" + " -k, --keep keep (don't delete) input files\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" + " -R, --repair try to repair a small error in file\n" + " -s, --split split multimember file in single-member files\n" + " -t, --test test compressed file integrity\n" + " -v, --verbose be verbose (a 2nd -v gives more)\n" + " --loose-trailing allow trailing data seeming corrupt header\n" + " --dump=<list>:d:t dump members listed/damaged, tdata to stdout\n" + " --remove=<list>:d:t remove members, tdata from files in place\n" + " --strip=<list>:d:t copy files to stdout stripping members given\n" ); if( verbosity >= 1 ) { std::printf( " -W, --debug-decompress=<pos>,<val> set pos to val and decompress to stdout\n" @@ -202,6 +210,46 @@ void show_header( const unsigned dictionary_size ) #include "main_common.cc" +// Colon-separated list of "damaged", "tdata", [r][^]<list> (1 1,3-5,8) +void Member_list::parse( const char * p ) + { + while( true ) + { + const char * tp = p; // points to terminator; ':' or null + while( *tp && *tp != ':' ) ++tp; + const unsigned len = tp - p; + if( std::isalpha( (const unsigned char)*p ) ) + { + if( len <= 7 && std::strncmp( "damaged", p, len ) == 0 ) + { damaged = true; goto next; } + if( len <= 5 && std::strncmp( "tdata", p, len ) == 0 ) + { tdata = true; goto next; } + } + { + const bool reverse = ( *p == 'r' ); + if( reverse ) ++p; + if( *p == '^' ) { ++p; if( reverse ) rin = false; else in = false; } + std::vector< Block > * rvp = reverse ? &rrange_vector : &range_vector; + while( std::isdigit( (const unsigned char)*p ) ) + { + const char * tail; + const int pos = getnum( p, 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; + rvp->push_back( Block( pos, size ) ); + if( tail == tp ) goto next; + if( *tail == ',' ) p = tail + 1; else break; + } + } + show_error( "Invalid list of members." ); + std::exit( 1 ); +next: + if( *(p = tp) != 0 ) ++p; else return; + } + } + + namespace { // Recognized formats: <begin> <begin>-<end> <begin>,<size> ,<size> @@ -215,11 +263,11 @@ void parse_range( const char * const ptr, Block & range ) { range.pos( value ); if( tail[0] == 0 ) { range.size( INT64_MAX - value ); return; } - const bool issize = ( tail[0] == ',' ); + const bool is_size = ( tail[0] == ',' ); value = getnum( tail + 1, 0, 1, INT64_MAX ); // size - if( issize || value > range.pos() ) + if( is_size || value > range.pos() ) { - if( !issize ) value -= range.pos(); + if( !is_size ) value -= range.pos(); if( INT64_MAX - range.pos() >= value ) { range.size( value ); return; } } } @@ -343,23 +391,23 @@ int open_instream( const char * const name, struct stat * const in_statsp, int open_truncable_stream( const char * const name, struct stat * const in_statsp ) { - int infd = open( name, O_RDWR | O_BINARY ); - if( infd < 0 ) + int fd = open( name, O_RDWR | O_BINARY ); + if( fd < 0 ) show_file_error( name, "Can't open input file", errno ); else { - const int i = fstat( infd, in_statsp ); + const int i = fstat( fd, in_statsp ); const mode_t mode = in_statsp->st_mode; if( i != 0 || !S_ISREG( mode ) ) { if( verbosity >= 0 ) std::fprintf( stderr, "%s: File '%s' is not a regular file.\n", program_name, name ); - close( infd ); - infd = -1; + close( fd ); + fd = -1; } } - return infd; + return fd; } @@ -421,8 +469,17 @@ bool check_tty( const char * const input_filename, const int infd, } +void set_signals( void (*action)(int) ) + { + std::signal( SIGHUP, action ); + std::signal( SIGINT, action ); + std::signal( SIGTERM, action ); + } + + void cleanup_and_fail( const int retval ) { + set_signals( SIG_IGN ); // ignore signals if( delete_output_on_interrupt ) { delete_output_on_interrupt = false; @@ -438,6 +495,13 @@ void cleanup_and_fail( const int retval ) namespace { +extern "C" void signal_handler( int ) + { + show_error( "Control-C or similar caught, quitting." ); + cleanup_and_fail( 1 ); + } + + // Set permissions, owner and times. void close_and_set_permissions( const struct stat * const in_statsp ) { @@ -517,9 +581,9 @@ int decompress( const unsigned long long cfile_size, const int infd, Range_decoder rdec( infd ); for( bool first_member = true; ; first_member = false ) { - File_header header; + Lzip_header header; rdec.reset_member_position(); - const int size = rdec.read_data( header.data, File_header::size ); + const int size = rdec.read_data( header.data, Lzip_header::size ); if( rdec.finished() ) // End Of File { if( first_member ) @@ -573,30 +637,16 @@ int decompress( const unsigned long long cfile_size, const int infd, { std::fputs( testing ? "ok\n" : "done\n", stderr ); pp.reset(); } } } - catch( std::bad_alloc ) { pp( "Not enough memory." ); retval = 1; } - catch( Error e ) { pp(); show_error( e.msg, errno ); retval = 1; } + catch( std::bad_alloc & ) { pp( "Not enough memory." ); retval = 1; } + catch( Error & e ) { pp(); show_error( e.msg, errno ); retval = 1; } if( verbosity == 1 && retval == 0 ) std::fputs( testing ? "ok\n" : "done\n", stderr ); return retval; } - -extern "C" void signal_handler( int ) - { - show_error( "Control-C or similar caught, quitting." ); - cleanup_and_fail( 1 ); - } - - -void set_signals() - { - std::signal( SIGHUP, signal_handler ); - std::signal( SIGINT, signal_handler ); - std::signal( SIGTERM, signal_handler ); - } - } // end namespace +void set_signal_handler() { set_signals( signal_handler ); } int close_outstream( const struct stat * const in_statsp ) { @@ -625,23 +675,15 @@ 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 ) 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 show_error2( const char * const msg1, const char * const name, - const char * const msg2 ) - { if( verbosity >= 0 ) - std::fprintf( stderr, "%s: %s '%s' %s\n", program_name, msg1, name, msg2 ); + std::fprintf( stderr, "%s: %s: %s%s%s\n", program_name, filename, msg, + ( errcode > 0 ) ? ": " : "", + ( errcode > 0 ) ? std::strerror( errcode ) : "" ); } -void show_error4( const char * const msg1, const char * const name1, - const char * const name2, const char * const msg2 ) +void show_2file_error( const char * const msg1, const char * const name1, + const char * const name2, const char * const msg2 ) { if( verbosity >= 0 ) std::fprintf( stderr, "%s: %s '%s' and '%s' %s\n", @@ -684,6 +726,7 @@ int main( const int argc, const char * const argv[] ) { Block range( 0, 0 ); Bad_byte bad_byte; + Member_list member_list; std::string default_output_filename; std::vector< std::string > filenames; Mode program_mode = m_none; @@ -695,7 +738,7 @@ int main( const int argc, const char * const argv[] ) bool to_stdout = false; invocation_name = argv[0]; - enum { opt_dtd = 256, opt_lt, opt_rtd, opt_std }; + enum { opt_du = 256, opt_dtd, opt_lt, opt_re, opt_rtd, opt_st, opt_std }; const Arg_parser::Option options[] = { { 'a', "trailing-error", Arg_parser::no }, @@ -721,9 +764,12 @@ int main( const int argc, const char * const argv[] ) { 'X', "show-packets", Arg_parser::maybe }, { 'Y', "debug-delay", Arg_parser::yes }, { 'Z', "debug-repair", Arg_parser::yes }, + { opt_du, "dump", Arg_parser::yes }, { opt_dtd, "dump-tdata", Arg_parser::no }, { opt_lt, "loose-trailing", Arg_parser::no }, + { opt_re, "remove", Arg_parser::yes }, { opt_rtd, "remove-tdata", Arg_parser::no }, + { opt_st, "strip", Arg_parser::yes }, { opt_std, "strip-tdata", Arg_parser::no }, { 0 , 0, Arg_parser::no } }; @@ -768,15 +814,24 @@ int main( const int argc, const char * const argv[] ) parse_range( arg, range ); break; case 'Z': set_mode( program_mode, m_debug_repair ); parse_pos_value( arg, bad_byte ); break; - case opt_dtd: set_mode( program_mode, m_dump_tdata ); break; + case opt_du: set_mode( program_mode, m_dump ); + member_list.parse( arg ); break; + case opt_dtd: set_mode( program_mode, m_dump ); + member_list.parse( "tdata" ); break; case opt_lt: loose_trailing = true; break; - case opt_rtd: set_mode( program_mode, m_remove_tdata ); break; - case opt_std: set_mode( program_mode, m_strip_tdata ); break; + case opt_re: set_mode( program_mode, m_remove ); + member_list.parse( arg ); break; + case opt_rtd: set_mode( program_mode, m_remove ); + member_list.parse( "tdata" ); break; + case opt_st: set_mode( program_mode, m_strip ); + member_list.parse( arg ); break; + case opt_std: set_mode( program_mode, m_strip ); + member_list.parse( "tdata" ); break; default : internal_error( "uncaught option." ); } } // end process options -#if defined(__MSVCRT__) || defined(__OS2__) +#if defined(__MSVCRT__) || defined(__OS2__) || defined(__DJGPP__) setmode( STDIN_FILENO, O_BINARY ); setmode( STDOUT_FILENO, O_BINARY ); #endif @@ -794,6 +849,7 @@ int main( const int argc, const char * const argv[] ) if( filenames.back() != "-" ) filenames_given = true; } + const char terminator = isatty( STDOUT_FILENO ) ? '\r' : '\n'; try { switch( program_mode ) { @@ -804,56 +860,54 @@ int main( const int argc, const char * const argv[] ) return debug_decompress( filenames[0], bad_byte, false ); case m_debug_delay: one_file( filenames.size() ); - return debug_delay( filenames[0], range ); + return debug_delay( filenames[0], range, terminator ); case m_debug_repair: one_file( filenames.size() ); - return debug_repair( filenames[0], bad_byte ); + return debug_repair( filenames[0], bad_byte, terminator ); case m_decompress: break; - case m_dump_tdata: - case m_strip_tdata: + case m_dump: + case m_strip: if( filenames.size() < 1 ) { show_error( "You must specify at least 1 file.", 0, true ); return 1; } - if( default_output_filename.size() ) set_signals(); - return dump_tdata( filenames, default_output_filename, force, - program_mode == m_strip_tdata, loose_trailing ); + return dump_members( filenames, default_output_filename, member_list, + force, ignore_errors, ignore_trailing, + loose_trailing, program_mode == m_strip ); case m_list: break; case m_merge: if( filenames.size() < 2 ) { show_error( "You must specify at least 2 files.", 0, true ); return 1; } - set_signals(); - return merge_files( filenames, default_output_filename, force ); + return merge_files( filenames, default_output_filename, force, terminator ); case m_range_dec: one_file( filenames.size() ); - set_signals(); return range_decompress( filenames[0], default_output_filename, range, force, ignore_errors, ignore_trailing, loose_trailing, to_stdout ); - case m_remove_tdata: + case m_remove: if( filenames.size() < 1 ) { show_error( "You must specify at least 1 file.", 0, true ); return 1; } - return remove_tdata( filenames, loose_trailing ); + return remove_members( filenames, member_list, ignore_errors, + ignore_trailing, loose_trailing ); case m_repair: one_file( filenames.size() ); - set_signals(); - return repair_file( filenames[0], default_output_filename, force ); + return repair_file( filenames[0], default_output_filename, force, terminator ); case m_show_packets: one_file( filenames.size() ); return debug_decompress( filenames[0], bad_byte, true ); case m_split: one_file( filenames.size() ); - set_signals(); return split_file( filenames[0], default_output_filename, force ); case m_test: break; } } - catch( std::bad_alloc ) + catch( std::bad_alloc & ) { show_error( "Not enough memory." ); cleanup_and_fail( 1 ); } - catch( Error e ) { show_error( e.msg, errno ); 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, ignore_trailing, loose_trailing ); + return list_files( filenames, ignore_errors, ignore_trailing, + loose_trailing ); if( program_mode == m_test ) outfd = -1; @@ -862,7 +916,7 @@ int main( const int argc, const char * const argv[] ) if( !to_stdout && program_mode != m_test && ( filenames_given || default_output_filename.size() ) ) - set_signals(); + set_signals( signal_handler ); Pretty_print pp( filenames ); @@ -941,6 +995,12 @@ int main( const int argc, const char * const argv[] ) else tmp = decompress( cfile_size, infd, pp, ignore_trailing, loose_trailing, program_mode == m_test ); + if( close( infd ) != 0 ) + { + show_error( input_filename.size() ? "Error closing input file" : + "Error closing stdin", errno ); + if( tmp < 1 ) tmp = 1; + } if( tmp > retval ) retval = tmp; if( tmp ) { if( program_mode != m_test ) cleanup_and_fail( retval ); @@ -950,7 +1010,6 @@ int main( const int argc, const char * const argv[] ) close_and_set_permissions( in_statsp ); if( input_filename.size() ) { - close( infd ); if( !keep_input_files && !to_stdout && program_mode != m_test ) std::remove( input_filename.c_str() ); } |