Brackup
view release on metacpan or search on metacpan
lib/Brackup/Restore.pm view on Meta::CPAN
use POSIX qw(mkfifo);
use Fcntl qw(O_RDONLY O_CREAT O_WRONLY O_TRUNC);
use String::Escape qw(unprintable);
use Brackup::DecryptedFile;
use Brackup::Decrypt;
sub new {
my ($class, %opts) = @_;
my $self = bless {}, $class;
$self->{to} = delete $opts{to}; # directory we're restoring to
$self->{prefix} = delete $opts{prefix}; # directory/file filename prefix, or "" for all
$self->{filename}= delete $opts{file}; # filename we're restoring from
$self->{config} = delete $opts{config}; # brackup config (if available)
$self->{verbose} = delete $opts{verbose};
$self->{_local_uid_map} = {}; # remote/metafile uid -> local uid
$self->{_local_gid_map} = {}; # remote/metafile gid -> local gid
$self->{prefix} =~ s/\/$// if $self->{prefix};
$self->{_stats_to_run} = []; # stack (push/pop) of subrefs to reset stat info on
die "Destination directory doesn't exist" unless $self->{to} && -d $self->{to};
croak("Unknown options: " . join(', ', keys %opts)) if %opts;
$self->{metafile} = Brackup::DecryptedFile->new(filename => $self->{filename});
return $self;
}
# returns a hashref of { "foo" => "bar" } from { ..., "Driver-foo" => "bar" }
sub _driver_meta {
my $src = shift;
my $ret = {};
foreach my $k (keys %$src) {
next unless $k =~ /^Driver-(.+)/;
$ret->{$1} = $src->{$k};
}
return $ret;
}
sub restore {
my ($self) = @_;
my $parser = $self->parser;
my $meta = $parser->readline;
my $driver_class = $meta->{BackupDriver};
die "No driver specified" unless $driver_class;
my $driver_meta = _driver_meta($meta);
my $confsec;
if ($self->{config} && $meta->{TargetName}) {
$confsec = eval { $self->{config}->get_section('TARGET:' . $meta->{TargetName}) };
}
# If no config section, use an empty one up with no keys to simplify Target handling
$confsec ||= Brackup::ConfigSection->new('fake');
eval "use $driver_class; 1;" or die
"Failed to load driver ($driver_class) to restore from: $@\n";
my $target = eval {"$driver_class"->new_from_backup_header($driver_meta, $confsec); };
if ($@) {
die "Failed to instantiate target ($driver_class) for restore. Perhaps it doesn't support restoring yet?\n\nThe error was: $@";
}
$self->{_target} = $target;
$self->{_meta} = $meta;
# handle absolute prefixes by stripping off RootPath to relativise
if ($self->{prefix} && $self->{prefix} =~ m/^\//) {
$self->{prefix} =~ s/^\Q$meta->{RootPath}\E\/?//;
}
# we first process directories, then files sorted by their first chunk,
# then the rest. The file sorting allows us to avoid loading composite
# chunks and identical single chunk files multiple times from the target
# (see _restore_file)
my (@dirs, @files, @rest);
while (my $it = $parser->readline) {
my $type = $it->{Type} || 'f';
if($type eq 'f') {
# find dig of first chunk
($it->{Chunks} || '') =~ /^(\S+)/;
my ($offset, $len, $enc_len, $dig) = split(/;/, $1 || '');
$it->{fst_dig} = $dig || '';
push @files, $it;
} elsif($type eq 'd') {
push @dirs, $it;
} else {
push @rest, $it;
}
}
@files = sort { $a->{fst_dig} cmp $b->{fst_dig} } @files;
my $restore_count = 0;
for my $it (@dirs, @files, @rest) {
my $type = $it->{Type} || "f";
my $path = unprintable($it->{Path});
my $path_escaped = $it->{Path};
my $path_escaped_stripped = $it->{Path};
die "Unknown filetype: type=$type, file: $path_escaped" unless $type =~ /^[ldfp]$/;
if ($self->{prefix}) {
next unless $path =~ m/^\Q$self->{prefix}\E(?:\/|$)/;
# if non-dir and $path eq $self->{prefix}, strip all but last component
if ($type ne 'd' && $path =~ m/^\Q$self->{prefix}\E\/?$/) {
if (my ($leading_prefix) = ($self->{prefix} =~ m/^(.*\/)[^\/]+\/?$/)) {
$path =~ s/^\Q$leading_prefix\E//;
$path_escaped_stripped =~ s/^\Q$leading_prefix\E//;
}
}
else {
$path =~ s/^\Q$self->{prefix}\E\/?//;
$path_escaped_stripped =~ s/^\Q$self->{prefix}\E\/?//;
}
}
$restore_count++;
my $full = $self->{to} . "/" . $path;
my $full_escaped = $self->{to} . "/" . $path_escaped_stripped;
# restore default modes/user/group from header
( run in 0.487 second using v1.01-cache-2.11-cpan-c966e8aa7e8 )