/*  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 <http://www.gnu.org/licenses/>.
*/
/*
    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 <cerrno>
#include <climits>
#include <cstdio>
#include <cstdlib>
#include <string>
#include <vector>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>

#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=<bytes>     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=<blocks>  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=<n>         maximum number of error areas allowed\n" );
  std::printf( "  -F, --fill=<types>           fill given type areas with infile data (?*/-+)\n" );
  std::printf( "  -i, --input-position=<pos>   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=<pos>  starting position in output file [ipos]\n" );
  std::printf( "  -q, --quiet                  quiet operation\n" );
  std::printf( "  -r, --max-retries=<n>        exit after given retries (-1=infinity) [0]\n" );
  std::printf( "  -s, --max-size=<bytes>       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 <http://gnu.org/licenses/gpl.html>\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 );
  }


syntax highlighted by Code2HTML, v. 0.9.1