diff --git a/backup-tar/restore.pl b/backup-tar/restore.pl index 5ded7c07bfc9463a1f20aea3aeb13ea35d66d585..08c67abcb97ea61f2e598f8496bb9807a137541f 100755 --- a/backup-tar/restore.pl +++ b/backup-tar/restore.pl @@ -31,14 +31,19 @@ my $date; if ( (shift @ARGV) =~ /([0-9]+-[0-9]+-[0-9]+)/ ) { $date = $1; } else { - print STDERR "Date must be in YEAR-MOONTH-DAY format\n"; + print STDERR "Date must be in YEAR-MONTH-DAY format\n"; usage; exit 2; } my @paths = (); for my $p ( @ARGV ) { + if ( $p =~ m,/[.]{2}(/|$), ) { + print STDERR "Paths must not contain backward (/../) elements"; + usage; + exit 2; + } # strip the leading / to match paths in the tar - if ( $p =~ m|^/(.*)$| ) { + if ( $p =~ m,^/(.*)$, ) { push( @paths, $1 ); } else { print STDERR "Expected absolute path but got $p\n"; @@ -49,38 +54,74 @@ for my $p ( @ARGV ) { print "$machine $date @paths\n"; -my $full; -my @weekly = (); -my @daily = (); - my $namematch = qr/^([0-9]+-[0-9]+-[0-9]+)[.](daily|weekly|full)[.](.*)$/; -opendir( my $dir, catdir("/backup-tar", $machine ) ) or die $!; -my @backups = sort( grep { /$namematch/ } readdir( $dir ) ); -for my $backup ( @backups ) { - $backup =~ /$namematch/; - $backup = "$1.$2.$3"; - my $path = catfile( "/backup-tar", $machine, $backup ); - my $from = $1; - my $type = $2; - last if $from gt $date; - - if ( $type eq "full" ) { - $full = $path; - @weekly = (); - } elsif ( $type eq "weekly" ) { - push( @weekly, $path ); - @daily = (); - } else { - push( @daily, $path ); - } +sub getDirContents { + my ( $dirname ) = @_; + opendir( my $dir, $dirname ) or die $!; + my @contents = readdir( $dir ); + my @backups = sort( grep { /$namematch/ } @contents ); + my @subdirs = sort( grep { !/^(stamps|[.]{1,2})$/ && !/$namematch/ } @contents ); + return ( [ @backups ], [ @subdirs ] ); } -my @to_restore = ( $full, @weekly, @daily ); +sub restore { + my ( $restore_path, @backups ) = @_; + say "restore( $restore_path, " . join( ", ", @backups ) . " );"; + + my $full; + my @weekly = (); + my @daily = (); + + for my $backup_path ( @backups ) { + my $backup = basename( $backup_path ); + $backup =~ /$namematch/; + my $from = $1; + my $type = $2; + last if $from gt $date; + + if ( $type eq "full" ) { + $full = $backup_path; + @weekly = (); + } elsif ( $type eq "weekly" ) { + push( @weekly, $backup_path ); + @daily = (); + } else { + push( @daily, $backup_path ); + } + } + + my @to_restore = ( $full, @weekly, @daily ); + + for my $backup ( @to_restore ) { + print "$backup…\n"; + my @args = ( "tar", "--extract", "--auto-compress", "--incremental", "--xattrs", + "--acls", "-vv", "-f$backup", "--", $restore_path ); + system( @args ) == 0 or die "extract of $backup failed with $?, @args\n"; + } +} + +sub find_and_restore { + my ( $anchor, $path, $fullpath ) = @_; + $fullpath = $path unless defined $fullpath; + + say "find_and_restore( $anchor, $path, $fullpath );"; + + my ( $backups, $subdirs ) = getDirContents( $anchor ); + if ( @{$backups} ) { + return restore( $fullpath, map { catdir( $anchor, $_ ) } @{$backups} ); + } + $path =~ m,([^/]*)(?:/|$)(.*), or die "Could not parse path $path"; + my $first_segment = $1; + my $path_rest = $2; + my @next = grep { $_ eq $first_segment } @{$subdirs}; + if ( @next ) { + return find_and_restore( catdir( $anchor, $first_segment ), $path_rest, $fullpath ); + } + say STDERR "No match for $first_segment in $anchor"; + exit 3; +} -for my $backup ( @to_restore ) { - print "$backup…\n"; - my @args = ( "tar", "--extract", "--auto-compress", "--incremental", "--xattrs", - "--acls", "-vv", "-f$backup", "--", @paths ); - system( @args ) == 0 or die "extract of $backup failed with $?, @args\n"; +for my $p ( @paths ) { + find_and_restore( catdir( "/backup-tar", $machine ), $p ); }