#!/opt/i386-linux/perl/bin/perl -w
#
# copy_badblocks.pl
#
# Copyright (C) 2002-2004 Blair Zajac.  All rights reserved.
# Author: Blair Zajac <blair@orcaware.com>.
#
# $URL$
# $LastChangedDate$
# $LastChangedBy$
# $LastChangedRevision$
#
# Distributed under the terms of the GNU General Public License,
# version 2 or later, which is available for download at
# http://www.gnu.org/licenses/gpl.html.
#
# This script is used to make a copy of a file that contains bad
# blocks.  Normally, programs that read a file, such as cp, will quit
# immediately after the first read failure and not continue to attempt
# to read the remaining portion of the file.  If you have a file that
# contains one or more bad blocks and you want to make a copy of the
# file containing as much readable data as you can, this script
# accomplishes this.  It reads a block at a time from the input file
# to the output file.  If a particular block cannot be read, it writes
# to the output file a standard filler string that can be searched
# for.

$| = 1;

use strict;
use Fcntl qw(O_RDONLY SEEK_SET);
use Getopt::Long 2.26;

# These are configurable parameters.

# Set this to the default blocksize to read from the file.
my $opt_default_blocksize = 4096;

sub usage {
  die "usage: $0 [-b blocksize] from to\n";
}

GetOptions('blocksize=i' => \$opt_default_blocksize) or
  usage;

usage unless @ARGV == 2;

my $from_filename = shift;
my $to_filename   = shift;

my @stat = stat($from_filename);
unless (@stat) {
  die "$0: cannot stat '$from_filename': $!\n";
}
my $file_length = $stat[7];
my $remaining   = $file_length;

unless (sysopen(READ, $from_filename, O_RDONLY)) {
  die "$0: cannot sysopen '$from_filename' for reading: $!\n";
}
unless (open(WRITE, ">$to_filename")) {
  die "$0: cannot open '$to_filename' for writing: $!\n";
}

print "Using $opt_default_blocksize bytes per block.\n";

my $pos = 0;
while ($remaining) {
  my $buffer        = "BAD BLOCKS.....\n" x ($opt_default_blocksize/16);
  my $bytes_to_read = 0;
  if ($remaining < $opt_default_blocksize) {
    $bytes_to_read  = $remaining;
    $buffer         = substr($buffer, 0, $remaining);
  } else {
    $bytes_to_read  = $opt_default_blocksize;
  }

  my $block_number = $pos/$opt_default_blocksize;

  if (sysseek(READ, $pos, SEEK_SET)) {
    my $bytes_read = sysread(READ, $buffer, $bytes_to_read);
    unless (defined $bytes_read and $bytes_read == $bytes_to_read) {
      warn "$0: cannot read $opt_default_blocksize bytes at block ",
	   "$block_number byte $pos: $!\n";
    }
  } else {
    warn "$0: cannot sysseek to $pos: $!\n";
  }
  print WRITE $buffer;

  $pos       += $bytes_to_read;
  $remaining -= $bytes_to_read;
}

unless (close(WRITE)) {
  unlink($to_filename);
  die "$0: cannot close '$to_filename' for writing: $!\n";
}

unless (close(READ)) {
  unlink($to_filename);
  die "$0: cannot close '$from_filename' for reading: $!\n";
}

chmod($stat[2],   $to_filename) or
  warn "$0: cannot chmod '$to_filename': $!\n";
if ($> == 0) {
  chown(@stat[4,5], $to_filename) or
    warn "$0: cannot chown '$to_filename': $!\n";
}
