6 |
use File::Slurp; |
use File::Slurp; |
7 |
use File::Path; |
use File::Path; |
8 |
use Getopt::Long; |
use Getopt::Long; |
9 |
|
use Data::Dump qw/dump/; |
10 |
|
|
11 |
my $dev = '/dev/sdb2'; |
my $dev = '/dev/sdb'; |
12 |
my $mnt = '/mnt/42'; |
my $mnt = '/mnt/42'; |
13 |
|
|
14 |
my ( $rollback, $commit ); |
my ( $init, $debug ); |
15 |
|
|
16 |
GetOptions( |
GetOptions( |
17 |
'rollback!' => \$rollback, |
'init!' => \$init, |
18 |
'commit!' => \$commit, |
'debug!' => \$debug, |
19 |
) or die "unknown options: $!"; |
) or die "unknown options: $!"; |
20 |
|
|
|
map { mkdir $_ unless -e $_ } map { "$mnt/$_" } ( 'log', 'fs' ); |
|
21 |
|
|
22 |
my $path = shift @ARGV || die "usage: $0 /path/to/file\n"; |
my @part; |
23 |
|
|
24 |
my ($dir,$file) = ($1,$2) if $path =~ m{^(?:(.+)/)?([^/]+)}; |
sub partitions { |
25 |
|
open(my $parted, '-|', "parted -s $dev unit mb print free") || die "parted: $!"; |
26 |
|
while(<$parted>) { |
27 |
|
warn "## $_" if $debug; |
28 |
|
if ( m{^\s+([\d\.]+)MB\s+([\d\.]+)MB\s+([\d\.]+)MB\s+Free Space} ) { |
29 |
|
$part[0] = [ $1, $2, $3, 'free' ]; |
30 |
|
} |
31 |
|
next unless m{^\s+\d+\s+}; |
32 |
|
s{^\s+}{}; |
33 |
|
s{([\d\.]+)MB}{$1}g; |
34 |
|
my ( $nr, $start, $end, $size, undef ) = split(/\s+/, $_, 5); |
35 |
|
$part[$nr] = [ $start, $end, $size ]; |
36 |
|
} |
37 |
|
warn "## part = ",dump( @part ) if $debug; |
38 |
|
return @part; |
39 |
|
} |
40 |
|
|
41 |
|
sub parted { |
42 |
|
my $command = shift; |
43 |
|
warn "+ $command\n"; |
44 |
|
system("parted -s $dev unit mb $command") == 0 or die "parted: $!"; |
45 |
|
} |
46 |
|
|
|
my $seq; |
|
|
my $opos = 0; |
|
47 |
|
|
48 |
if ( -e "$mnt/last" ) { |
if ( $init ) { |
49 |
my $log = read_file("$mnt/last"); |
system("umount $mnt"); |
50 |
$seq = readlink("$mnt/last"); |
partitions(); |
51 |
$seq =~ s{^.+/(\d+)$}{$1} || die "can't parse sequence: $seq"; |
parted("rm $_") foreach grep { defined $part[$_] } ( 1 .. 4 ); |
52 |
$seq++; |
parted("mkpartfs primary ext2 0 42MB"); |
53 |
$opos = $1 if $log =~ m{opos:\s+(\d+\.\d[k])}; |
partitions(); |
54 |
die "can't decode opos" unless $opos; |
parted("mkpart extended 42MB " . $part[0]->[1] . 'MB'); |
55 |
warn "# seq: $seq start: $opos\n"; |
system "sync;sync"; |
56 |
|
partitions(); |
57 |
} else { |
system("mount ${dev}1 $mnt") == 0 or die "can't mount: $!"; |
58 |
$seq = $#{ glob("$mnt/log/*") } + 1; |
my $cmd = read_file($0); |
59 |
warn "# $mnt/last not found, recover seq: $seq\n"; |
write_file( "$mnt/42.pl", $cmd ); |
60 |
|
chmod 0755, "$mnt/42.pl"; |
61 |
|
exit; |
62 |
} |
} |
63 |
|
|
64 |
|
map { mkdir $_ unless -e $_ } map { "$mnt/$_" } ( 'log', 'fs', 'stat' ); |
65 |
|
|
66 |
my $log = "$mnt/log/$seq"; |
my $path = shift @ARGV || die "usage: $0 /path/to/file\n"; |
67 |
|
|
68 |
if ( -e $log ) { |
my ($dir,$file) = ($1,$2) if $path =~ m{^(?:(.+)/)?([^/]+)}; |
|
print "ERROR $log exists!\n",read_file($log); |
|
|
if ( $rollback ) { |
|
|
unlink $log || die "can't rollback $log: $!"; |
|
|
die "RECOVERY removed $log\n"; |
|
|
} elsif ( $commit ) { |
|
|
unlink "$mnt/last" || die "can't remove $mnt/last: $!"; |
|
|
symlink $log,"$mnt/last" || die "can't commit $log: $!"; |
|
|
die "ERROR: can't update $mnt/last -> $log" unless $log eq readlink "$mnt/last"; |
|
|
die "RECOVERY updated $mnt/last -> $log\n"; |
|
|
} else { |
|
|
die "$log exists, re-run with --commit or --rollback"; |
|
|
} |
|
|
} |
|
69 |
|
|
70 |
my $fs = "$mnt/fs/$dir"; |
my $fs = "$mnt/fs/$dir"; |
71 |
mkpath $fs unless -e $fs; |
mkpath $fs unless -e $fs; |
72 |
|
|
73 |
$fs .= '/' . $file; |
$fs .= '/' . $file; |
|
|
|
74 |
die "$fs exists!" if -e $fs; |
die "$fs exists!" if -e $fs; |
75 |
|
|
76 |
my $cmd = "dd_rescue -l $log -S $opos $path $dev"; |
my @stat = stat($path); |
77 |
print "+ $cmd\n"; |
die "can't stat $path: $!" unless @stat; |
78 |
system($cmd) == 0 or warn "# exit code: $?"; |
warn "# $path ",$stat[7],$/; |
79 |
|
|
80 |
unlink("$mnt/last") && symlink($log,"$mnt/last") || die "can't commit log $log -> $mnt/last"; |
partitions(); |
81 |
|
|
82 |
symlink($log,$fs) || die "can't create $fs: $!"; |
my $size_mb = int( $stat[7] / 1000 / 1000 ) + 1; |
83 |
|
my ( $free_start, undef, $free_size ) = @{$part[0]}; |
84 |
|
|
85 |
print "OK $fs -> $log\n"; |
my $part_end = $free_start + $size_mb; |
86 |
|
|
87 |
|
my $last_part = $#part; |
88 |
|
|
89 |
|
system("umount $mnt"); |
90 |
|
parted("mkpart logical ${free_start}MB ${part_end}MB"); |
91 |
|
partitions(); |
92 |
|
system("mount ${dev}1 $mnt") == 0 or die "can't mount $mnt: $!"; |
93 |
|
|
94 |
|
my $part_size = $part[$#part]->[2] || die "can't get size of new partition"; |
95 |
|
|
96 |
|
die "not enough space on $dev $size_mb > $part_size" if $size_mb > $part_size; |
97 |
|
|
98 |
|
my $part_nr = $#part; |
99 |
|
|
100 |
|
die "can't create partition $last_part == $part_nr" if $last_part == $part_nr; |
101 |
|
|
102 |
|
write_file( "$mnt/stat/$part_nr", join("\n",@stat) ); |
103 |
|
|
104 |
|
my $log = "$mnt/log/$part_nr"; |
105 |
|
|
106 |
|
my $to_dev = $dev . $part_nr; |
107 |
|
|
108 |
|
symlink( $to_dev ,$fs) || die "can't create $fs: $!"; |
109 |
|
|
110 |
|
my $cmd = "dd_rescue -w -l $log $path $to_dev"; |
111 |
|
print "+ $cmd\n"; |
112 |
|
exec $cmd; |