#!/usr/bin/perl use strict; use warnings; use Sys::Trace; use Set::IntSpan; my $usage = "$0 [--dry-run | --verbose] arg_idx offset block_size -- ddrescue_command {} ddrescue_args -- command args\n"; my $dry_run = 0; if (defined $ARGV[0] && $ARGV[0] eq '--dry-run') { shift @ARGV; $dry_run = 1; } my $verbose = 0; if (defined $ARGV[0] && $ARGV[0] eq '--verbose') { shift @ARGV; $verbose = 1; } my $file_arg_index = shift @ARGV; defined($file_arg_index) || die($usage); $file_arg_index >= 1 || die($usage); my $ddr_offset = shift @ARGV; defined($ddr_offset) || die($usage); my $block_size = shift @ARGV; defined($block_size) || die($usage); my $sep = shift @ARGV; $sep eq '--' || die($usage); my @ddr_args; my $arg; while(1) { $arg = shift @ARGV; last if !defined($arg) || $arg eq '--'; push @ddr_args, $arg; }; (defined($arg) && $arg eq '--') || die($usage); scalar(grep { $_ eq '{}' } @ddr_args) == 1 || die($usage); my $file_name = $ARGV[$file_arg_index]; defined($file_name) || die($usage); warn "Tracing reads from $file_name\n" if $dry_run || $verbose; scalar @ARGV >= 2 || die($usage); my $total_span = new Set::IntSpan; while(1) { my $cur_span = new Set::IntSpan; my $trace = Sys::Trace->new(exec => [@ARGV]); $trace->start; $trace->wait; my %trace_fds; my $res = $trace->results; for($res->calls) { if ($_->{call} eq 'open' && $_->{args}->[0] eq $file_name && $_->{'return'} != -1) { $trace_fds{$_->{pid}} = {} if !defined($trace_fds{$_->{pid}}); $trace_fds{$_->{pid}}->{$_->{'return'}} = 0; } elsif ($_->{call} eq 'close' && defined $trace_fds{$_->{pid}} && defined $trace_fds{$_->{pid}}->{$_->{args}->[0]} ) { delete $trace_fds{$_->{pid}}->{$_->{args}->[0]}; } elsif ($_->{call} =~ /^pread(64|)$/ && defined $trace_fds{$_->{pid}} && defined $trace_fds{$_->{pid}}->{$_->{args}->[0]} ) { my $offset = ($_->{args}->[3] || 0) + $ddr_offset; my $count = $_->{args}->[2]; $cur_span += [[ int($offset / $block_size), int(($offset + $count - 1) / $block_size) ]]; } elsif ($_->{call} eq 'lseek' && $_->{'return'} != -1 && defined $trace_fds{$_->{pid}} && defined $trace_fds{$_->{pid}}->{$_->{args}->[0]} ) { $trace_fds{$_->{pid}}->{$_->{args}->[0]} = ($_->{'return'} || 0); } elsif ($_->{call} eq 'read' && $_->{'return'} != -1 && defined $trace_fds{$_->{pid}} && defined $trace_fds{$_->{pid}}->{$_->{args}->[0]} ) { my $offset = $trace_fds{$_->{pid}}->{$_->{args}->[0]} + $ddr_offset; my $count = $_->{args}->[2]; $trace_fds{$_->{pid}}->{$_->{args}->[0]} += ($_->{'return'} || 0); $cur_span += [[ int($offset / $block_size), int(($offset + $count - 1) / $block_size) ]]; } # TODO: Add more calls here as needed } my $new_span = $cur_span - $total_span; last if empty $new_span; for my $run (spans $new_span) { my $offset = $run->[0] * $block_size; my $count = ($run->[1] - $run->[0] + 1) * $block_size; my @cmd = map { $_ eq '{}' ? ('-i', $offset, '-s', $count) : $_ } @ddr_args; warn "@cmd\n" if $dry_run || $verbose; system(@cmd) if !$dry_run; } last if $dry_run; $total_span += $cur_span; }