/* GNU ddrescue - Data recovery tool Copyright (C) 2004, 2005, 2006, 2007 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 3 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /* Return values: 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 ddrescue to panic. */ #define _FILE_OFFSET_BITS 64 #include #include #include #include #include #include #include #include #include #include "arg_parser.h" #include "ddrescue.h" namespace { const char * invocation_name = 0; const char * const Program_name = "GNU ddrescue"; const char * const program_name = "ddrescue"; const char * const program_year = "2007"; void show_help( const int cluster, const int hardbs ) throw() { std::printf( "%s - Data recovery tool.\n", Program_name ); std::printf( "Copies data from one file or block device to another,\n" ); std::printf( "trying hard to rescue data in case of read errors.\n" ); std::printf( "\nUsage: %s [options] infile outfile [logfile]\n", invocation_name ); std::printf( "Options:\n" ); std::printf( " -h, --help display this help and exit\n" ); std::printf( " -V, --version output version information and exit\n" ); std::printf( " -b, --block-size= hardware block size of input device [%d]\n", hardbs ); std::printf( " -B, --binary-prefixes show binary multipliers in numbers [default SI]\n" ); std::printf( " -c, --cluster-size= hardware blocks to copy at a time [%d]\n", cluster ); std::printf( " -C, --complete-only do not read new data beyond logfile limits\n" ); std::printf( " -d, --direct use direct disc access for input file\n" ); std::printf( " -e, --max-errors= maximum number of error areas allowed\n" ); std::printf( " -F, --fill= fill given type areas with infile data (?*/-+)\n" ); std::printf( " -i, --input-position= starting position in input file [0]\n" ); std::printf( " -n, --no-split do not try to split error areas\n" ); std::printf( " -o, --output-position= starting position in output file [ipos]\n" ); std::printf( " -q, --quiet quiet operation\n" ); std::printf( " -r, --max-retries= exit after given retries (-1=infinity) [0]\n" ); std::printf( " -s, --max-size= maximum size of data to be copied\n" ); std::printf( " -S, --sparse use sparse writes for output file\n" ); std::printf( " -t, --truncate truncate output file\n" ); std::printf( " -v, --verbose verbose operation\n" ); std::printf( "Numbers may be followed by a multiplier: b = blocks, k = kB = 10^3 = 1000,\n" ); std::printf( "Ki = KiB = 2^10 = 1024, M = 10^6, Mi = 2^20, G = 10^9, Gi = 2^30, etc...\n" ); std::printf( "\nReport bugs to bug-ddrescue@gnu.org\n"); } void show_version() throw() { std::printf( "%s %s\n", Program_name, PROGVERSION ); std::printf( "Copyright (C) %s Antonio Diaz Diaz.\n", program_year ); std::printf( "License GPLv3+: GNU GPL version 3 or later \n" ); std::printf( "This is free software: you are free to change and redistribute it.\n" ); std::printf( "There is NO WARRANTY, to the extent permitted by law.\n" ); } long long getnum( const char * ptr, const int bs, const int verbosity, const long long min = LLONG_MIN + 1, const long long max = LLONG_MAX ) throw() { errno = 0; char *tail; long long result = strtoll( ptr, &tail, 0 ); if( tail == ptr ) { if( verbosity >= 0 ) show_error( "bad or missing numerical argument", 0, true ); std::exit( 1 ); } if( !errno && tail[0] ) { int factor = ( tail[1] == 'i' ) ? 1024 : 1000; int exponent = 0; bool bad_multiplier = false; switch( tail[0] ) { case ' ': break; case 'b': if( bs > 0 ) { factor = bs; exponent = 1; } else bad_multiplier = true; break; case 'Y': exponent = 8; break; case 'Z': exponent = 7; break; case 'E': exponent = 6; break; case 'P': exponent = 5; break; case 'T': exponent = 4; break; case 'G': exponent = 3; break; case 'M': exponent = 2; break; case 'K': if( factor == 1024 ) exponent = 1; else bad_multiplier = true; break; case 'k': if( factor == 1000 ) exponent = 1; else bad_multiplier = true; break; default: bad_multiplier = true; } if( bad_multiplier ) { if( verbosity >= 0 ) show_error( "bad multiplier in numerical argument", 0, true ); std::exit( 1 ); } for( int i = 0; i < exponent; ++i ) { if( LLONG_MAX / factor >= llabs( result ) ) result *= factor; else { errno = ERANGE; break; } } } if( !errno && ( result < min || result > max ) ) errno = ERANGE; if( errno ) { if( verbosity >= 0 ) show_error( "numerical argument out of limits" ); std::exit( 1 ); } return result; } void check_fill_types( const std::string filltypes, const int verbosity ) throw() { bool good = true; for( unsigned int i = 0; i < filltypes.size(); ++i ) if( !Sblock::isstatus( filltypes[i] ) ) { good = false; break; } if( !filltypes.size() || !good ) { if( verbosity >= 0 ) show_error( "invalid type for `fill' option" ); std::exit( 1 ); } } bool check_identical( const char * name1, const char * name2 ) throw() { if( !std::strcmp( name1, name2 ) ) return true; struct stat stat1, stat2; if( stat( name1, &stat1) || stat( name2, &stat2) ) return false; return ( stat1.st_ino == stat2.st_ino && stat1.st_dev == stat2.st_dev ); } int do_fill( long long ipos, const long long opos, const long long max_size, const char *iname, const char *oname, const char *logname, const int cluster, const int hardbs, const int verbosity, const std::string & filltypes ) throw() { if( !logname ) { if( verbosity >= 0 ) show_error( "logfile required in fill mode", 0, true ); return 1; } Fillbook fillbook( opos, max_size, logname, cluster, hardbs, verbosity ); if( fillbook.domain().size() == 0 ) { if( verbosity >= 0 ) { show_error( "Nothing to do" ); } return 0; } const int ides = open( iname, O_RDONLY ); if( ides < 0 ) { if( verbosity >= 0 ) show_error( "cannot open input file", errno ); return 1; } if( ipos < 0 ) ipos = 0; else if( ipos > 0 ) { const long long isize = lseek( ides, 0, SEEK_END ); if( isize < 0 ) { if( verbosity >= 0 ) show_error( "input file is not seekable" ); return 1; } if( isize > 0 && ipos >= isize ) { if( verbosity >= 0 ) { input_pos_error( ipos, isize ); } return 1; } } if( !fillbook.read_buffer( ipos, ides ) ) { if( verbosity >= 0 ) show_error( "error reading fill data from input file" ); return 1; } const int odes = open( oname, O_WRONLY | O_CREAT, 0644 ); if( odes < 0 ) { if( verbosity >= 0 ) show_error( "cannot open output file", errno ); return 1; } if( lseek( odes, 0, SEEK_SET ) ) { if( verbosity >= 0 ) show_error( "output file is not seekable" ); return 1; } if( verbosity >= 0 ) std::printf( "\n\n" ); if( verbosity > 0 ) { std::printf( "About to fill with data from %s areas of %s marked %s\n", iname, oname, filltypes.c_str() ); std::printf( " Maximum size to fill: %sBytes\n", format_num( fillbook.domain().size() ) ); std::printf( " Starting positions: infile = %sB", format_num( ipos ) ); std::printf( ", outfile = %sB\n", format_num( fillbook.domain().pos() ) ); std::printf( " Copy block size: %d hard blocks\n", cluster ); std::printf( "Hard block size: %d bytes\n", hardbs ); std::printf( "\n" ); } return fillbook.do_fill( odes, filltypes ); } int do_rescue( const long long ipos, const long long opos, const long long max_size, const char *iname, const char *oname, const char *logname, const int cluster, const int hardbs, const int max_errors, const int max_retries, const int o_direct, const int o_trunc, const int verbosity, const bool complete_only, const bool nosplit, const bool sparse ) throw() { const int ides = open( iname, O_RDONLY | o_direct ); if( ides < 0 ) { if( verbosity >= 0 ) show_error( "cannot open input file", errno ); return 1; } const long long isize = lseek( ides, 0, SEEK_END ); if( isize < 0 ) { if( verbosity >= 0 ) show_error( "input file is not seekable" ); return 1; } Rescuebook rescuebook( ipos, opos, max_size, isize, logname, cluster, hardbs, max_errors, max_retries, verbosity, complete_only, nosplit, sparse ); if( rescuebook.domain().size() == 0 ) { if( verbosity >= 0 ) { show_error( "Nothing to do" ); } return 0; } if( o_trunc && !rescuebook.blank() ) { if( verbosity >= 0 ) show_error( "outfile truncation and logfile input are incompatible", 0, true ); return 1; } const int odes = open( oname, O_WRONLY | O_CREAT | o_trunc, 0644 ); if( odes < 0 ) { if( verbosity >= 0 ) show_error( "cannot open output file", errno ); return 1; } if( lseek( odes, 0, SEEK_SET ) ) { if( verbosity >= 0 ) show_error( "output file is not seekable" ); return 1; } if( verbosity >= 0 ) std::printf( "\n\n" ); if( verbosity > 0 ) { std::printf( "About to copy %sBytes from %s to %s\n", ( rescuebook.domain().size() >= 0 ) ? format_num( rescuebook.domain().size() ) : "an undefined number of ", iname, oname ); std::printf( " Starting positions: infile = %sB", format_num( rescuebook.domain().pos() ) ); std::printf( ", outfile = %sB\n", format_num( rescuebook.rescue_opos() ) ); std::printf( " Copy block size: %d hard blocks\n", cluster ); std::printf( "Hard block size: %d bytes\n", hardbs ); bool nl = false; if( max_errors >= 0 ) { nl = true; std::printf( "Max_errors: %d ", max_errors ); } if( max_retries >= 0 ) { nl = true; std::printf( "Max_retries: %d ", max_retries ); } if( nl ) std::printf( "\n" ); std::printf( "Direct: %s ", o_direct ? "yes" : "no" ); std::printf( "Sparse: %s ", sparse ? "yes" : "no" ); std::printf( "Split: %s ", !nosplit ? "yes" : "no" ); std::printf( "Truncate: %s\n", o_trunc ? "yes" : "no" ); if( complete_only ) std::printf( "Complete only\n" ); std::printf( "\n" ); } return rescuebook.do_rescue( ides, odes ); } } // end namespace void input_pos_error( const long long pos, const long long isize ) throw() { char buf[80]; snprintf( buf, sizeof( buf ), "can't start reading at pos %lld", pos ); show_error( buf ); snprintf( buf, sizeof( buf ), "input file is only %lld bytes long", isize ); show_error( buf ); } void internal_error( const char * msg ) throw() { char buf[80]; snprintf( buf, sizeof( buf ), "internal error: %s", msg ); show_error( buf ); std::exit( 3 ); } void show_error( const char * msg, const int errcode, const bool help ) throw() { if( msg && msg[0] != 0 ) { std::fprintf( stderr, "%s: %s", program_name, msg ); if( errcode > 0 ) std::fprintf( stderr, ": %s", strerror( errcode ) ); std::fprintf( stderr, "\n" ); } if( help && invocation_name && invocation_name[0] != 0 ) std::fprintf( stderr, "Try `%s --help' for more information.\n", invocation_name ); } void write_logfile_header( FILE * f ) throw() { std::fprintf( f, "# Rescue Logfile. Created by %s version %s\n", Program_name, PROGVERSION ); } int main( const int argc, const char * argv[] ) throw() { long long ipos = -1, opos = -1, max_size = -1; const int cluster_bytes = 65536, default_hardbs = 512; int cluster = 0, hardbs = 512; int max_errors = -1, max_retries = 0; int o_direct = 0, o_trunc = 0, verbosity = 0; bool complete_only = false, nosplit = false, sparse = false; std::string filltypes; invocation_name = argv[0]; const Arg_parser::Option options[] = { { 'b', "block-size", Arg_parser::yes }, { 'B', "binary_prefixes", Arg_parser::no }, { 'c', "cluster-size", Arg_parser::yes }, { 'C', "complete-only", Arg_parser::no }, { 'd', "direct", Arg_parser::no }, { 'e', "max-errors", Arg_parser::yes }, { 'F', "fill", Arg_parser::yes }, { 'h', "help", Arg_parser::no }, { 'i', "input-position", Arg_parser::yes }, { 'n', "no-split", Arg_parser::no }, { 'o', "output-position", Arg_parser::yes }, { 'q', "quiet", Arg_parser::no }, { 'r', "max-retries", Arg_parser::yes }, { 's', "max-size", Arg_parser::yes }, { 'S', "sparse", Arg_parser::no }, { 't', "truncate", Arg_parser::no }, { 'v', "verbose", Arg_parser::no }, { 'V', "version", Arg_parser::no }, { 0 , 0, Arg_parser::no } }; Arg_parser parser( argc, argv, options ); if( parser.error().size() ) // bad option { show_error( parser.error().c_str(), 0, true ); return 1; } int argind; for( argind = 0; argind < parser.arguments(); ++argind ) { const int code = parser.code( argind ); if( !code ) break; // no more options const char * arg = parser.argument( argind ).c_str(); switch( code ) { case 'b': hardbs = getnum( arg, 0, verbosity, 1, INT_MAX ); break; case 'B': format_num( 0, 0, -1 ); break; // set binary prefixes case 'c': cluster = getnum( arg, 1, verbosity, 1, INT_MAX ); break; case 'C': complete_only = true; break; case 'd': #ifdef O_DIRECT o_direct = O_DIRECT; #endif if( !o_direct ) { if( verbosity >= 0 ) show_error( "direct disc access not available" ); return 1; } break; case 'e': max_errors = getnum( arg, 0, verbosity, -1, INT_MAX ); break; case 'F': filltypes = arg; check_fill_types( filltypes, verbosity ); break; case 'h': show_help( cluster_bytes / default_hardbs, default_hardbs ); return 0; case 'i': ipos = getnum( arg, hardbs, verbosity, 0 ); break; case 'n': nosplit = true; break; case 'o': opos = getnum( arg, hardbs, verbosity, 0 ); break; case 'q': verbosity = -1; break; case 'r': max_retries = getnum( arg, 0, verbosity, -1, INT_MAX ); break; case 's': max_size = getnum( arg, hardbs, verbosity, -1 ); break; case 'S': sparse = true; break; case 't': o_trunc = O_TRUNC; break; case 'v': verbosity = 1; break; case 'V': show_version(); return 0; default : internal_error( "uncaught option" ); } } // end process options if( hardbs < 1 ) hardbs = default_hardbs; if( cluster >= INT_MAX / hardbs ) cluster = ( INT_MAX / hardbs ) - 1; if( cluster < 1 ) cluster = cluster_bytes / hardbs; if( cluster < 1 ) cluster = 1; const char *iname = 0, *oname = 0, *logname = 0; if( argind < parser.arguments() ) iname = parser.argument( argind++ ).c_str(); if( argind < parser.arguments() ) oname = parser.argument( argind++ ).c_str(); if( argind < parser.arguments() ) logname = parser.argument( argind++ ).c_str(); if( argind < parser.arguments() ) { if( verbosity >= 0 ) show_error( "too many files", 0, true ); return 1; } // end scan arguments if( !iname || !oname ) { if( verbosity >= 0 ) show_error( "both input and output must be specified", 0, true ); return 1; } if( check_identical ( iname, oname ) ) { if( verbosity >= 0 ) show_error( "infile and outfile are the same" ); return 1; } if( !filltypes.size() ) return do_rescue( ipos, opos, max_size, iname, oname, logname, cluster, hardbs, max_errors, max_retries, o_direct, o_trunc, verbosity, complete_only, nosplit, sparse ); if( verbosity >= 0 && ( max_errors >= 0 || max_retries || o_direct || o_trunc || complete_only || nosplit || sparse ) ) show_error( "warning: options -C -d -e -n -r -S and -t are ignored in fill mode" ); return do_fill( ipos, opos, max_size, iname, oname, logname, cluster, hardbs, verbosity, filltypes ); }