/* 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 . */ #define _FILE_OFFSET_BITS 64 #include #include #include #include #include #include #include #include #include #include "ddrescue.h" namespace { int my_fgetc( FILE * f ) throw() { int ch; bool comment = false; do { ch = std::fgetc( f ); if( ch == '#' ) comment = true; else if( ch == '\n' || ch == EOF ) comment = false; } while( comment ); return ch; } const char * my_fgets( FILE * f, int & linenum ) throw() { const int maxlen = 127; static char buf[maxlen+1]; int ch, len = 1; while( len == 1 ) { do { ch = my_fgetc( f ); if( ch == '\n' ) ++linenum; } while( std::isspace( ch ) ); len = 0; while( true ) { if( ch == EOF ) { if( len > 0 ) ch = '\n'; else break; } if( len < maxlen ) buf[len++] = ch; if( ch == '\n' ) { ++linenum; break; } ch = my_fgetc( f ); } } if( len > 0 ) { buf[len] = 0; return buf; } else return 0; } void extend_sblock_vector( std::vector< Sblock > & sblock_vector, const long long isize, const int verbosity ) throw() { if( sblock_vector.size() == 0 ) { Sblock sb( 0, (isize > 0) ? isize : -1, Sblock::non_tried ); sblock_vector.push_back( sb ); return; } Sblock & front = sblock_vector.front(); if( front.pos() > 0 ) { if( front.status() == Sblock::non_tried ) front.pos( 0 ); else sblock_vector.insert( sblock_vector.begin(), Sblock( 0, front.pos(), Sblock::non_tried ) ); } Sblock & back = sblock_vector.back(); const long long end = back.end(); if( isize > 0 ) { if( back.pos() >= isize ) { if( back.pos() == isize && back.status() == Sblock::non_tried ) { sblock_vector.pop_back(); return; } if( verbosity >= 0 ) show_error( "bad logfile; last block begins past end of input file" ); std::exit( 1 ); } if( end < 0 || end > isize ) back.size( isize - back.pos() ); else if( end < isize ) { if( back.status() == Sblock::non_tried ) back.size( isize - back.pos() ); else sblock_vector.push_back( Sblock( end, isize - end, Sblock::non_tried ) ); } } else if( end >= 0 ) { if( back.status() == Sblock::non_tried ) back.size( -1 ); else sblock_vector.push_back( Sblock( end, -1, Sblock::non_tried ) ); } } void show_logfile_error( const char * filename, const int linenum ) throw() { char buf[80]; snprintf( buf, sizeof( buf ), "error in logfile %s, line %d", filename, linenum ); show_error( buf ); } } // end namespace bool Logbook::read_logfile() throw() { if( !_filename ) return false; FILE *f = std::fopen( _filename, "r" ); if( !f ) return false; int linenum = 0; sblock_vector.clear(); const char *line = my_fgets( f, linenum ); if( line ) // status line { char ch; int n = std::sscanf( line, "%lli %c\n", &_current_pos, &ch ); if( n == 2 && _current_pos >= 0 && isstatus( ch ) ) _current_status = Logbook::Status( ch ); else { if( _verbosity >= 0 ) { show_logfile_error( _filename, linenum ); show_error( "Are you using a logfile from ddrescue 1.5 or older?" ); } std::exit( 2 ); } while( true ) { line = my_fgets( f, linenum ); if( !line ) break; long long pos, size; n = std::sscanf( line, "%lli %lli %c\n", &pos, &size, &ch ); if( n == 3 && pos >= 0 && ( size > 0 || size == -1 ) && Sblock::isstatus( ch ) ) { Sblock::Status st = Sblock::Status( ch ); Sblock sb( pos, size, st ); if( sblock_vector.size() > 0 && !sb.follows( sblock_vector.back() ) ) { if( _verbosity >= 0 ) show_logfile_error( _filename, linenum ); std::exit( 2 ); } sblock_vector.push_back( sb ); } else { if( _verbosity >= 0 ) show_logfile_error( _filename, linenum ); std::exit( 2 ); } } if( sblock_vector.size() && sblock_vector.back().size() < 0 && sblock_vector.back().status() != Sblock::non_tried ) { if( _verbosity >= 0 ) { show_logfile_error( _filename, linenum ); show_error( "Only areas of type non_tried may have an undefined size" ); } std::exit( 2 ); } } std::fclose( f ); return true; } bool Logbook::check_domain_size( const long long isize ) throw() { if( isize > 0 ) { if( _domain.pos() >= isize ) { if( _verbosity >= 0 ) input_pos_error( _domain.pos(), isize ); return false; } if( _domain.size() < 0 || _domain.pos() + _domain.size() > isize ) _domain.size( isize - _domain.pos() ); } return true; } Logbook::Logbook( const long long pos, const long long max_size, const long long isize, const char * name, const int cluster, const int hardbs, const int verbosity, const bool complete_only ) throw() : _current_pos( 0 ), _current_status( copying ), _domain( std::max( 0LL, pos ), max_size ), _filename( name ), _hardbs( hardbs ), _softbs( cluster * hardbs ), _verbosity( verbosity ), _final_msg( 0 ), _final_errno( 0 ) { int alignment = sysconf( _SC_PAGESIZE ); if( alignment < _hardbs || alignment % _hardbs ) alignment = _hardbs; if( alignment < 2 || alignment > 65536 ) alignment = 0; _iobuf = iobuf_base = new char[ _softbs + alignment ]; if( alignment > 1 ) // align iobuf for use with raw devices { const int disp = alignment - ( reinterpret_cast (_iobuf) % alignment ); if( disp > 0 && disp < alignment ) _iobuf += disp; } if( !check_domain_size( isize ) ) std::exit( 1 ); if( _filename ) read_logfile(); if( !complete_only ) extend_sblock_vector( sblock_vector, isize, _verbosity ); else // limit domain to blocks of finite size read from logfile { if( sblock_vector.size() && sblock_vector.back().size() < 0 ) sblock_vector.pop_back(); if( sblock_vector.size() ) { const Block b( sblock_vector.front().pos(), sblock_vector.back().end() - sblock_vector.front().pos() ); _domain = b.overlap( _domain ); } else _domain.size( 0 ); } compact_sblock_vector(); } bool Logbook::blank() const throw() { return ( sblock_vector.size() == 1 && sblock_vector[0].status() == Sblock::non_tried ); } void Logbook::compact_sblock_vector() throw() { for( unsigned int i = 1; i < sblock_vector.size(); ) { if( sblock_vector[i-1].join( sblock_vector[i] ) ) sblock_vector.erase( sblock_vector.begin() + i ); else ++i; } } void Logbook::split_domain_border_sblocks() throw() { unsigned int i = 0; for( ; i < sblock_vector.size(); ++i ) { Sblock & sb = sblock_vector[i]; if( sb.includes( _domain.pos() ) ) { Sblock head = sb.split( _domain.pos() ); if( head.size() > 0 ) { insert_sblock( i, head ); ++i; } break; } } const long long end = _domain.end(); if( end < 0 ) return; for( ; i < sblock_vector.size(); ++i ) { Sblock & sb = sblock_vector[i]; if( sb.includes( end ) ) { Sblock head = sb.split( end ); if( head.size() > 0 ) { insert_sblock( i, head ); ++i; } break; } } } // Writes periodically the logfile to disc. // Returns false only if update is attempted and fails. // bool Logbook::update_logfile( const int odes, const bool force ) throw() { static time_t t1 = std::time( 0 ); if( !_filename ) return true; const int interval = 30 + std::min( 270, sblocks() / 40 ); const time_t t2 = std::time( 0 ); if( !force && t2 - t1 < interval ) return true; t1 = t2; fsync( odes ); errno = 0; FILE *f = std::fopen( _filename, "w" ); if( !f ) { if( _verbosity >= 0 ) { char buf[80]; snprintf( buf, sizeof( buf ), "error opening logfile %s for writing", _filename ); show_error( buf, errno ); } return false; } write_logfile_header( f ); std::fprintf( f, "# current_pos current_status\n" ); std::fprintf( f, "0x%08llX %c\n", _current_pos, _current_status ); std::fprintf( f, "# pos size status\n" ); for( unsigned int i = 0; i < sblock_vector.size(); ++i ) { const Sblock & sb = sblock_vector[i]; if( sb.size() >= 0 ) std::fprintf( f, "0x%08llX 0x%08llX %c\n", sb.pos(), sb.size(), sb.status() ); else std::fprintf( f, "0x%08llX -1 %c\n", sb.pos(), sb.status() ); } if( std::fclose( f ) ) { if( _verbosity >= 0 ) { char buf[80]; snprintf( buf, sizeof( buf ), "error writing logfile %s", _filename ); show_error( buf, errno ); } return false; } return true; } int Fillbook::fill_areas( const std::string & filltypes ) throw() { bool first_post = true; split_domain_border_sblocks(); for( int index = 0; index < sblocks(); ++index ) { const Sblock & sb = sblock( index ); if( !sb.overlaps( domain() ) ) { if( sb < domain() ) continue; else break; } if( filltypes.find( sb.status() ) >= filltypes.size() ) continue; if( sb.end() <= current_pos() ) continue; Block b( sb.pos(), softbs() ); if( sb.includes( current_pos() ) ) b.pos( current_pos() ); if( b.end() > sb.end() ) b = b.overlap( sb ); current_status( copying ); while( b.size() > 0 ) { if( verbosity() >= 0 ) { show_status( b.pos(), first_post ); first_post = false; } const int retval = fill_block( b ); if( retval ) return retval; if( !update_logfile( _odes ) ) return 1; b.pos( b.pos() + softbs() ); if( b.end() > sb.end() ) b = b.overlap( sb ); } ++filled_areas; --remaining_areas; } return 0; } int Fillbook::do_fill( const int odes, const std::string & filltypes ) throw() { filled_size = 0, remaining_size = 0; filled_areas = 0, remaining_areas = 0; _odes = odes; if( current_status() != copying || !domain().includes( current_pos() ) ) current_pos( 0 ); for( int i = 0; i < sblocks(); ++i ) { const Sblock & sb = sblock( i ); const Block b = sb.overlap( domain() ); if( b.size() == 0 ) { if( sb < domain() ) continue; else break; } if( filltypes.find( sb.status() ) >= filltypes.size() ) continue; if( b.end() <= current_pos() ) { ++filled_areas; filled_size += b.size(); } else if( b.includes( current_pos() ) ) { filled_size += current_pos() - b.pos(); ++remaining_areas; remaining_size += b.end() - current_pos(); } else { ++remaining_areas; remaining_size += b.size(); } } set_handler(); if( verbosity() >= 0 ) { std::printf( "Press Ctrl-C to interrupt\n" ); if( filename() ) { std::printf( "Initial status (read from logfile)\n" ); std::printf( "filled size: %10sB,", format_num( filled_size ) ); std::printf( " filled areas: %7u\n", filled_areas ); std::printf( "remaining size: %10sB,", format_num( remaining_size ) ); std::printf( " remaining areas: %7u\n", remaining_areas ); std::printf( "Current status\n" ); } } int retval = fill_areas( filltypes ); if( verbosity() >= 0 ) { show_status( -1, true ); if( retval == 0 ) std::printf( "Finished" ); else if( retval < 0 ) std::printf( "Interrupted by user" ); std::fputc( '\n', stdout ); } compact_sblock_vector(); if( retval == 0 ) current_status( finished ); else if( retval < 0 ) retval = 0; // interrupted by user if( !update_logfile( _odes, true ) && retval == 0 ) retval = 1; if( verbosity() >= 0 && final_msg() ) { show_error( final_msg(), final_errno() ); } return retval; } void Rescuebook::count_errors() throw() { errors = 0; for( int i = 0; i < sblocks(); ++i ) { const Sblock & sb = sblock( i ); const Block b = sb.overlap( domain() ); if( b.size() == 0 ) { if( sb < domain() ) continue; else break; } switch( sb.status() ) { case Sblock::non_tried: break; case Sblock::non_trimmed: case Sblock::non_split: ++errors; break; case Sblock::bad_block: errors += b.hard_blocks( hardbs() ); break; case Sblock::finished: break; } } } bool Rescuebook::find_valid_index( int & index, const Sblock::Status st ) const throw() { while( index < sblocks() ) { const Sblock & sb = sblock( index ); if( !sb.overlaps( domain() ) ) { if( sb < domain() ) { ++index; continue; } else break; } if( sb.status() != st ) { ++index; continue; } return true; } return false; } int Rescuebook::copy_and_update( int & index, const Sblock::Status st, const int size, bool & block_done, int & copied_size, int & error_size, const char * msg, bool & first_post ) throw() { Block & b = sblock( index ); Block chip = b.split( b.pos() + size, hardbs() ); if( chip.size() == 0 ) { chip = b; b.size( 0 ); block_done = true; } if( verbosity() >= 0 ) { show_status( chip.pos(), msg, first_post ); first_post = false; } int retval = copy_block( chip, copied_size, error_size ); if( retval ) { b.join( chip ); return retval; } if( copied_size > 0 ) { if( index > 0 && sblock( index - 1 ).status() == Sblock::finished ) sblock( index - 1 ).inc_size( copied_size ); else { insert_sblock( index, Sblock( chip.pos(), copied_size, Sblock::finished ) ); ++index; } recsize += copied_size; } if( error_size > 0 ) { if( index > 0 && sblock( index - 1 ).status() == st ) sblock( index - 1 ).inc_size( error_size ); else { insert_sblock( index, Sblock( chip.pos() + copied_size, error_size, st ) ); ++index; } } if( copied_size + error_size < chip.size() ) // EOF { truncate_vector( index ); block_done = true; } else if( block_done && sblock( index ).size() == 0 ) erase_sblock( index ); count_errors(); return 0; } // Read the non-damaged part of the domain, skipping over the damaged areas. // int Rescuebook::copy_non_tried() throw() { const int skip_ini = -1; // skip after 2 consecutive errors int index = 0; bool first_post = true; split_domain_border_sblocks(); while( find_valid_index( index, Sblock::non_tried ) ) { current_status( copying ); int skip_counter = skip_ini; bool block_done = false; while( !block_done ) { if( skip_counter > 0 ) { Block & b = sblock( index ); long long pos = softbs(); pos *= skip_counter; pos += b.pos(); const Sblock chip( b.split( pos, hardbs() ), Sblock::non_trimmed ); if( chip.size() == 0 ) skip_counter = skip_ini; // can't skip more else if( index == 0 || !sblock( index - 1 ).join( chip ) ) { insert_sblock( index, chip ); ++index; } errsize += chip.size(); } int copied_size, error_size; int retval = copy_and_update( index, Sblock::non_trimmed, softbs(), block_done, copied_size, error_size, "Copying data...", first_post ); if( error_size > 0 ) // increment counter on error { errsize += error_size; ++skip_counter; } else skip_counter = skip_ini; // reset counter on success if( retval || too_many_errors() ) return retval; if( !update_logfile( _odes ) ) return 1; } } return 0; } // Trim the damaged areas backwards. // int Rescuebook::trim_errors() throw() { int index = 0; bool first_post = true; split_domain_border_sblocks(); while( find_valid_index( index, Sblock::non_trimmed ) ) { current_status( trimming ); bool block_done = false; while( !block_done ) { Block & b = sblock( index ); Block chip = b.backsplit( b.end() - 1, hardbs() ); if( chip.size() == 0 ) { chip = b; b.size( 0 ); block_done = true; } if( verbosity() >= 0 ) { show_status( chip.pos(), "Trimming error areas...", first_post ); first_post = false; } int copied_size, error_size; int retval = copy_block( chip, copied_size, error_size ); if( retval ) b.join( chip ); else { if( error_size > 0 ) { sblock( index ).status( Sblock::non_split ); block_done = true; if( error_size == chip.size() ) b.join( chip ); else if( index + 1 < sblocks() && sblock( index + 1 ).status() == Sblock::non_split ) sblock( index + 1 ).dec_pos( error_size ); else insert_sblock( index + 1, Sblock( chip.pos() + copied_size, error_size, Sblock::non_split ) ); } if( copied_size > 0 ) { if( index + 1 < sblocks() && sblock( index + 1 ).status() == Sblock::finished ) sblock( index + 1 ).dec_pos( copied_size ); else insert_sblock( index + 1, Sblock( chip.pos(), copied_size, Sblock::finished ) ); recsize += copied_size; errsize -= copied_size; } if( copied_size + error_size < chip.size() ) // EOF { if( index + 1 < sblocks() ) truncate_vector( index + 1 ); } if( block_done && sblock( index ).size() == 0 ) erase_sblock( index ); count_errors(); } if( retval || too_many_errors() ) return retval; if( !update_logfile( _odes ) ) return 1; } } return 0; } // Try to read the damaged areas, splitting them into smaller pieces. // int Rescuebook::split_errors() throw() { int index = 0; bool first_post = true; split_domain_border_sblocks(); while( find_valid_index( index, Sblock::non_split ) ) { current_status( splitting ); bool block_done = false; while( !block_done ) { int copied_size, error_size; int retval = copy_and_update( index, Sblock::bad_block, hardbs(), block_done, copied_size, error_size, "Splitting error areas...", first_post ); if( copied_size > 0 ) errsize -= copied_size; if( retval || too_many_errors() ) return retval; if( !update_logfile( _odes ) ) return 1; } } return 0; } int Rescuebook::copy_errors() throw() { if( _max_retries != 0 ) { char msgbuf[80] = "Copying bad blocks... Retry "; const int msglen = std::strlen( msgbuf ); bool resume = ( current_status() == retrying && domain().includes( current_pos() ) ); for( int retry = 1; _max_retries < 0 || retry <= _max_retries; ++retry ) { snprintf( msgbuf + msglen, sizeof( msgbuf ) - msglen, "%d", retry ); int index = 0; bool first_post = true, bad_block_found = false; split_domain_border_sblocks(); while( find_valid_index( index, Sblock::bad_block ) ) { bad_block_found = true; if( resume ) { Sblock & sb = sblock( index ); if( sb.end() <= current_pos() ) { ++index; continue; } if( sb.includes( current_pos() ) ) { Sblock head = sb.split( current_pos() ); if( head.size() != 0 ) { insert_sblock( index, head ); ++index; } } resume = false; } current_status( retrying ); bool block_done = false; while( !block_done ) { int copied_size, error_size; int retval = copy_and_update( index, Sblock::bad_block, hardbs(), block_done, copied_size, error_size, msgbuf, first_post ); if( copied_size > 0 ) errsize -= copied_size; if( retval || too_many_errors() ) return retval; if( !update_logfile( _odes ) ) return 1; } } if( !bad_block_found ) break; } } return 0; } int Rescuebook::do_rescue( const int ides, const int odes ) throw() { bool copy_pending = false, trim_pending = false, split_pending = false; recsize = 0; errsize = 0; _ides = ides; _odes = odes; for( int i = 0; i < sblocks(); ++i ) { const Sblock & sb = sblock( i ); const Block b = sb.overlap( domain() ); if( b.size() == 0 ) { if( sb < domain() ) continue; else break; } switch( sb.status() ) { case Sblock::non_tried: copy_pending = trim_pending = split_pending = true; break; case Sblock::non_trimmed: trim_pending = true; // fall through case Sblock::non_split: split_pending = true; // fall through case Sblock::bad_block: errsize += b.size(); break; case Sblock::finished: recsize += b.size(); break; } } count_errors(); set_handler(); if( verbosity() >= 0 ) { std::printf( "Press Ctrl-C to interrupt\n" ); if( filename() ) { std::printf( "Initial status (read from logfile)\n" ); std::printf( "rescued: %10sB,", format_num( recsize ) ); std::printf( " errsize:%9sB,", format_num( errsize, 99999 ) ); std::printf( " errors: %7u\n", errors ); std::printf( "Current status\n" ); } } int retval = 0; if( copy_pending && !too_many_errors() ) retval = copy_non_tried(); if( !retval && trim_pending && !too_many_errors() ) retval = trim_errors(); if( !retval && split_pending && !_nosplit && !too_many_errors() ) retval = split_errors(); if( !retval && !too_many_errors() ) retval = copy_errors(); if( verbosity() >= 0 ) { show_status( -1, (retval ? 0 : "Finished"), true ); if( retval < 0 ) std::printf( "\nInterrupted by user" ); else if( too_many_errors() ) std::printf("\nToo many errors in input file" ); std::fputc( '\n', stdout ); } compact_sblock_vector(); if( retval == 0 ) current_status( finished ); else if( retval < 0 ) retval = 0; // interrupted by user if( !sync_sparse_file() ) { if( verbosity() >= 0 ) show_error( "error syncing sparse output file" ); if( retval == 0 ) retval = 1; } if( !update_logfile( _odes, true ) && retval == 0 ) retval = 1; if( verbosity() >= 0 && final_msg() ) { show_error( final_msg(), final_errno() ); } return retval; }