#!/usr/bin/perl

use bignum;       # support for numbers greater than 2^32 - 1
use Pod::Usage;   # error messages using internal POD
use Getopt::Long; # command line option parser

sub printhex64($)
{
	# printf() in Perl prints numbers > 2^32 - 1 as -1 (0xFFFFFFFF in hex),
	# at least when compiled for 32-bit systems. As byte offsets in a
	# ddrescue log file get bigger than 32 bit for drives with more than
	# 4 GiB capacity, a workaround has to be used.
	# This function was heavily inspired by paxdiablo's comment at
	# http://stackoverflow.com/a/828603
	$max32 = 4_294_967_296; # 2^32, thus 1 more than a 32-bit int can hold
	$bignum = shift;
	$ms32b = int($bignum / $max32); # most significant 32 bit
	if ($ms32b > 0)
	{
		# The number to be printed is bigger than what fits into a
		# 32-bit integer, thus the special treatment is really needed.
		$ls32b = $bignum - $ms32b * $max32; # least significant 32 bit
		return sprintf("%X%08X", $ms32b, $ls32b);
	}
	else
	{
		# The number to be printed actually fits into a 32-bit integer.
		# If the previous sprintf() would be re-used, a leading zero
		# would be printed.
		$ls32b = $bignum; # least significant 32 bit
		return sprintf("%08X", $ls32b);
	}
}

my $block_size = 512; # defaults to 512 byte
my $block_type = "?"; # defaults to "?" (non-tried block)

my $block_offset = '';
my $logfile = '';

GetOptions('block-size|b=i' => \$block_size,
           'block-type|l=s' => \$block_type,
           'help|h|?' => sub { pod2usage({-exitval => 1}) }) or pod2usage(2);

$block_offset = @ARGV[0];
shift(@ARGV); # remove block offset from list so that while <> loop further down sees only $logfile
$logfile = @ARGV[0];

if ($block_offset !~ m/^\d+$/)
{
	$msg = "Error: Block offset has to be an integer\n";
	pod2usage({-exitval => 2, -message => $msg, -verbose => 1});
}
if ($block_offset eq '')
{
	$msg = "Error: Block offset undefined\n";
	pod2usage({-exitval => 2, -message => $msg});
}
if ($logfile eq '')
{
	$msg = "Error: Log filename undefined\n";
	pod2usage({-exitval => 2, -message => $msg});
}
if (! -r $logfile)
{
	$msg = "Error: Log file $logfile cannot be opened for reading\n";
	pod2usage({-exitval => 2, -message => $msg});
}

$byte_offset = $block_offset * $block_size;

local $^I = '.bak'; # This enables in-place editing with backup named *.bak

while (<>)
{
	if ($_ =~ m|^0x([0-9A-F]{8,})  0x([0-9A-F]{8,})  ([?*/+-])$|)
	{
		$start = hex($1);
		$length = hex($2);
		$end = $start + $length;
		$type = $3;
		if (($start <= $byte_offset) and ($end > $byte_offset))
		{
			if ($start < $byte_offset)
			{
				printf("0x%s  0x%s  %s\n", printhex64($start),
				       printhex64($byte_offset - $start), $type);
			}
			printf("0x%s  0x%s  %s\n", printhex64($byte_offset),
			       printhex64($block_size), $block_type);
			$block_end = $byte_offset + $block_size;
			if ($end > $block_end)
			{
				printf("0x%s  0x%s  %s\n",
			               printhex64($block_end),
				       printhex64($end - $block_end), $type);
			}
		}
		else
		{
			print $_;
		}
	}
	else
	{
		print $_;
	}
}

__END__

=pod

=head1 NAME

ddrlogmod.pl - modifies GNU ddrescue log files

=head1 SYNOPSIS

B<ddrlogmod.pl> [options] block logfile

=head1 DESCRIPTION

B<ddrlogmod.pl> sets the specified block in a GNU ddrescue log file to a specified type.
Before the original log file is overwritten, a backup with the name <logfile>.bak is created.

=head1 ARGUMENTS

=over 7

=item B<block>

block whose state is to be changed, with 0 being the first

=item B<logfile>

the ddrescue logfile to modify

=back

=head1 OPTIONS

=over 7

=item B<-h>, B<--help>

display this help and exit

=item B<-b>, B<--block-size>=<bytes>

block size in bytes [default 512]

=item B<-l>, B<--block-type>=<type>

set type of block (?*/-+) [default ?]

=back

=head1 AUTHOR

Burkart Lingner, 2011

=cut
