line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Brackup::Restore; |
2
|
13
|
|
|
13
|
|
81
|
use strict; |
|
13
|
|
|
|
|
25
|
|
|
13
|
|
|
|
|
902
|
|
3
|
13
|
|
|
13
|
|
75
|
use warnings; |
|
13
|
|
|
|
|
33
|
|
|
13
|
|
|
|
|
419
|
|
4
|
13
|
|
|
13
|
|
78
|
use Carp qw(croak); |
|
13
|
|
|
|
|
33
|
|
|
13
|
|
|
|
|
771
|
|
5
|
13
|
|
|
13
|
|
85
|
use Digest::SHA1; |
|
13
|
|
|
|
|
29
|
|
|
13
|
|
|
|
|
530
|
|
6
|
13
|
|
|
13
|
|
69
|
use POSIX qw(mkfifo); |
|
13
|
|
|
|
|
39
|
|
|
13
|
|
|
|
|
116
|
|
7
|
13
|
|
|
13
|
|
990
|
use Fcntl qw(O_RDONLY O_CREAT O_WRONLY O_TRUNC); |
|
13
|
|
|
|
|
27
|
|
|
13
|
|
|
|
|
741
|
|
8
|
13
|
|
|
13
|
|
85
|
use String::Escape qw(unprintable); |
|
13
|
|
|
|
|
32
|
|
|
13
|
|
|
|
|
678
|
|
9
|
13
|
|
|
13
|
|
658
|
use Brackup::DecryptedFile; |
|
13
|
|
|
|
|
29
|
|
|
13
|
|
|
|
|
330
|
|
10
|
13
|
|
|
13
|
|
70
|
use Brackup::Decrypt; |
|
13
|
|
|
|
|
26
|
|
|
13
|
|
|
|
|
41807
|
|
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
sub new { |
13
|
11
|
|
|
11
|
0
|
123
|
my ($class, %opts) = @_; |
14
|
11
|
|
|
|
|
47
|
my $self = bless {}, $class; |
15
|
|
|
|
|
|
|
|
16
|
11
|
|
|
|
|
80
|
$self->{to} = delete $opts{to}; # directory we're restoring to |
17
|
11
|
|
|
|
|
184
|
$self->{prefix} = delete $opts{prefix}; # directory/file filename prefix, or "" for all |
18
|
11
|
|
|
|
|
54
|
$self->{filename}= delete $opts{file}; # filename we're restoring from |
19
|
11
|
|
|
|
|
56
|
$self->{config} = delete $opts{config}; # brackup config (if available) |
20
|
11
|
|
|
|
|
34
|
$self->{verbose} = delete $opts{verbose}; |
21
|
|
|
|
|
|
|
|
22
|
11
|
|
|
|
|
76
|
$self->{_local_uid_map} = {}; # remote/metafile uid -> local uid |
23
|
11
|
|
|
|
|
46
|
$self->{_local_gid_map} = {}; # remote/metafile gid -> local gid |
24
|
|
|
|
|
|
|
|
25
|
11
|
100
|
|
|
|
59
|
$self->{prefix} =~ s/\/$// if $self->{prefix}; |
26
|
|
|
|
|
|
|
|
27
|
11
|
|
|
|
|
55
|
$self->{_stats_to_run} = []; # stack (push/pop) of subrefs to reset stat info on |
28
|
|
|
|
|
|
|
|
29
|
11
|
50
|
33
|
|
|
956
|
die "Destination directory doesn't exist" unless $self->{to} && -d $self->{to}; |
30
|
11
|
50
|
|
|
|
80
|
croak("Unknown options: " . join(', ', keys %opts)) if %opts; |
31
|
|
|
|
|
|
|
|
32
|
11
|
|
|
|
|
186
|
$self->{metafile} = Brackup::DecryptedFile->new(filename => $self->{filename}); |
33
|
|
|
|
|
|
|
|
34
|
11
|
|
|
|
|
62
|
return $self; |
35
|
|
|
|
|
|
|
} |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
# returns a hashref of { "foo" => "bar" } from { ..., "Driver-foo" => "bar" } |
38
|
|
|
|
|
|
|
sub _driver_meta { |
39
|
11
|
|
|
11
|
|
24
|
my $src = shift; |
40
|
11
|
|
|
|
|
27
|
my $ret = {}; |
41
|
11
|
|
|
|
|
79
|
foreach my $k (keys %$src) { |
42
|
147
|
100
|
|
|
|
577
|
next unless $k =~ /^Driver-(.+)/; |
43
|
22
|
|
|
|
|
111
|
$ret->{$1} = $src->{$k}; |
44
|
|
|
|
|
|
|
} |
45
|
11
|
|
|
|
|
45
|
return $ret; |
46
|
|
|
|
|
|
|
} |
47
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
sub restore { |
49
|
11
|
|
|
11
|
0
|
28
|
my ($self) = @_; |
50
|
11
|
|
|
|
|
74
|
my $parser = $self->parser; |
51
|
11
|
|
|
|
|
75
|
my $meta = $parser->readline; |
52
|
11
|
|
|
|
|
33
|
my $driver_class = $meta->{BackupDriver}; |
53
|
11
|
50
|
|
|
|
32
|
die "No driver specified" unless $driver_class; |
54
|
|
|
|
|
|
|
|
55
|
11
|
|
|
|
|
43
|
my $driver_meta = _driver_meta($meta); |
56
|
|
|
|
|
|
|
|
57
|
11
|
|
|
|
|
29
|
my $confsec; |
58
|
11
|
50
|
33
|
|
|
57
|
if ($self->{config} && $meta->{TargetName}) { |
59
|
0
|
|
|
|
|
0
|
$confsec = eval { $self->{config}->get_section('TARGET:' . $meta->{TargetName}) }; |
|
0
|
|
|
|
|
0
|
|
60
|
|
|
|
|
|
|
} |
61
|
|
|
|
|
|
|
# If no config section, use an empty one up with no keys to simplify Target handling |
62
|
11
|
|
33
|
|
|
271
|
$confsec ||= Brackup::ConfigSection->new('fake'); |
63
|
|
|
|
|
|
|
|
64
|
11
|
50
|
|
4
|
|
2196
|
eval "use $driver_class; 1;" or die |
|
4
|
|
|
4
|
|
33
|
|
|
4
|
|
|
1
|
|
8
|
|
|
4
|
|
|
1
|
|
91
|
|
|
4
|
|
|
1
|
|
53
|
|
|
4
|
|
|
|
|
14
|
|
|
4
|
|
|
|
|
299
|
|
|
1
|
|
|
|
|
10
|
|
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
23
|
|
|
1
|
|
|
|
|
10
|
|
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
25
|
|
|
1
|
|
|
|
|
8
|
|
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
21
|
|
65
|
|
|
|
|
|
|
"Failed to load driver ($driver_class) to restore from: $@\n"; |
66
|
11
|
|
|
|
|
27
|
my $target = eval {"$driver_class"->new_from_backup_header($driver_meta, $confsec); }; |
|
11
|
|
|
|
|
115
|
|
67
|
11
|
50
|
|
|
|
43
|
if ($@) { |
68
|
0
|
|
|
|
|
0
|
die "Failed to instantiate target ($driver_class) for restore. Perhaps it doesn't support restoring yet?\n\nThe error was: $@"; |
69
|
|
|
|
|
|
|
} |
70
|
11
|
|
|
|
|
123
|
$self->{_target} = $target; |
71
|
11
|
|
|
|
|
47
|
$self->{_meta} = $meta; |
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
# handle absolute prefixes by stripping off RootPath to relativise |
74
|
11
|
50
|
66
|
|
|
158
|
if ($self->{prefix} && $self->{prefix} =~ m/^\//) { |
75
|
0
|
|
|
|
|
0
|
$self->{prefix} =~ s/^\Q$meta->{RootPath}\E\/?//; |
76
|
|
|
|
|
|
|
} |
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
# we first process directories, then files sorted by their first chunk, |
79
|
|
|
|
|
|
|
# then the rest. The file sorting allows us to avoid loading composite |
80
|
|
|
|
|
|
|
# chunks and identical single chunk files multiple times from the target |
81
|
|
|
|
|
|
|
# (see _restore_file) |
82
|
11
|
|
|
|
|
23
|
my (@dirs, @files, @rest); |
83
|
11
|
|
|
|
|
48
|
while (my $it = $parser->readline) { |
84
|
155
|
|
100
|
|
|
638
|
my $type = $it->{Type} || 'f'; |
85
|
155
|
100
|
|
|
|
458
|
if($type eq 'f') { |
|
|
50
|
|
|
|
|
|
86
|
|
|
|
|
|
|
# find dig of first chunk |
87
|
126
|
|
50
|
|
|
545
|
($it->{Chunks} || '') =~ /^(\S+)/; |
88
|
126
|
|
50
|
|
|
790
|
my ($offset, $len, $enc_len, $dig) = split(/;/, $1 || ''); |
89
|
126
|
|
50
|
|
|
419
|
$it->{fst_dig} = $dig || ''; |
90
|
126
|
|
|
|
|
520
|
push @files, $it; |
91
|
|
|
|
|
|
|
} elsif($type eq 'd') { |
92
|
29
|
|
|
|
|
157
|
push @dirs, $it; |
93
|
|
|
|
|
|
|
} else { |
94
|
0
|
|
|
|
|
0
|
push @rest, $it; |
95
|
|
|
|
|
|
|
} |
96
|
|
|
|
|
|
|
} |
97
|
11
|
|
|
|
|
118
|
@files = sort { $a->{fst_dig} cmp $b->{fst_dig} } @files; |
|
289
|
|
|
|
|
473
|
|
98
|
|
|
|
|
|
|
|
99
|
11
|
|
|
|
|
31
|
my $restore_count = 0; |
100
|
11
|
|
|
|
|
47
|
for my $it (@dirs, @files, @rest) { |
101
|
155
|
|
100
|
|
|
1195
|
my $type = $it->{Type} || "f"; |
102
|
155
|
|
|
|
|
1395
|
my $path = unprintable($it->{Path}); |
103
|
155
|
|
|
|
|
1649
|
my $path_escaped = $it->{Path}; |
104
|
155
|
|
|
|
|
333
|
my $path_escaped_stripped = $it->{Path}; |
105
|
155
|
50
|
|
|
|
892
|
die "Unknown filetype: type=$type, file: $path_escaped" unless $type =~ /^[ldfp]$/; |
106
|
|
|
|
|
|
|
|
107
|
155
|
100
|
|
|
|
467
|
if ($self->{prefix}) { |
108
|
60
|
100
|
|
|
|
477
|
next unless $path =~ m/^\Q$self->{prefix}\E(?:\/|$)/; |
109
|
|
|
|
|
|
|
# if non-dir and $path eq $self->{prefix}, strip all but last component |
110
|
6
|
100
|
100
|
|
|
91
|
if ($type ne 'd' && $path =~ m/^\Q$self->{prefix}\E\/?$/) { |
111
|
2
|
100
|
|
|
|
17
|
if (my ($leading_prefix) = ($self->{prefix} =~ m/^(.*\/)[^\/]+\/?$/)) { |
112
|
1
|
|
|
|
|
13
|
$path =~ s/^\Q$leading_prefix\E//; |
113
|
1
|
|
|
|
|
10
|
$path_escaped_stripped =~ s/^\Q$leading_prefix\E//; |
114
|
|
|
|
|
|
|
} |
115
|
|
|
|
|
|
|
} |
116
|
|
|
|
|
|
|
else { |
117
|
4
|
|
|
|
|
36
|
$path =~ s/^\Q$self->{prefix}\E\/?//; |
118
|
4
|
|
|
|
|
43
|
$path_escaped_stripped =~ s/^\Q$self->{prefix}\E\/?//; |
119
|
|
|
|
|
|
|
} |
120
|
|
|
|
|
|
|
} |
121
|
|
|
|
|
|
|
|
122
|
101
|
|
|
|
|
463
|
$restore_count++; |
123
|
101
|
|
|
|
|
353
|
my $full = $self->{to} . "/" . $path; |
124
|
101
|
|
|
|
|
278
|
my $full_escaped = $self->{to} . "/" . $path_escaped_stripped; |
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
# restore default modes/user/group from header |
127
|
101
|
100
|
66
|
|
|
1593
|
$it->{Mode} ||= ($type eq 'd' ? $meta->{DefaultDirMode} : $meta->{DefaultFileMode}); |
128
|
101
|
|
33
|
|
|
672
|
$it->{UID} ||= $meta->{DefaultUID}; |
129
|
101
|
|
33
|
|
|
574
|
$it->{GID} ||= $meta->{DefaultGID}; |
130
|
|
|
|
|
|
|
|
131
|
101
|
50
|
|
|
|
312
|
warn " * restoring $path_escaped to $full_escaped\n" if $self->{verbose}; |
132
|
101
|
50
|
|
|
|
397
|
$self->_restore_link ($full, $it) if $type eq "l"; |
133
|
101
|
100
|
|
|
|
417
|
$self->_restore_directory($full, $it) if $type eq "d"; |
134
|
101
|
50
|
|
|
|
570
|
$self->_restore_fifo ($full, $it) if $type eq "p"; |
135
|
101
|
100
|
|
|
|
1031
|
$self->_restore_file ($full, $it) if $type eq "f"; |
136
|
|
|
|
|
|
|
|
137
|
101
|
50
|
33
|
|
|
10935
|
$self->_chown($full, $it, $type, $meta) if $it->{UID} || $it->{GID}; |
138
|
|
|
|
|
|
|
} |
139
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
# clear chunk cached by _restore_file |
141
|
11
|
|
|
|
|
103
|
delete $self->{_cached_dig}; |
142
|
11
|
|
|
|
|
58
|
delete $self->{_cached_dataref}; |
143
|
|
|
|
|
|
|
|
144
|
11
|
100
|
|
|
|
64
|
if ($restore_count) { |
145
|
10
|
50
|
|
|
|
56
|
warn " * fixing stat info\n" if $self->{verbose}; |
146
|
10
|
|
|
|
|
67
|
$self->_exec_statinfo_updates; |
147
|
10
|
50
|
|
|
|
52
|
warn " * done\n" if $self->{verbose}; |
148
|
10
|
|
|
|
|
871
|
return 1; |
149
|
|
|
|
|
|
|
} else { |
150
|
1
|
50
|
|
|
|
48
|
die "nothing found matching '$self->{prefix}'.\n" if $self->{prefix}; |
151
|
0
|
|
|
|
|
0
|
die "nothing found to restore.\n"; |
152
|
|
|
|
|
|
|
} |
153
|
|
|
|
|
|
|
} |
154
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
sub _lookup_remote_uid { |
156
|
101
|
|
|
101
|
|
380
|
my ($self, $remote_uid, $meta) = @_; |
157
|
|
|
|
|
|
|
|
158
|
101
|
100
|
|
|
|
802
|
return $self->{_local_uid_map}->{$remote_uid} |
159
|
|
|
|
|
|
|
if defined $self->{_local_uid_map}->{$remote_uid}; |
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
# meta remote user map - remote_uid => remote username |
162
|
10
|
|
50
|
|
|
129
|
$self->{_remote_user_map} ||= { map { split /:/, $_, 2 } split /\s+/, $meta->{UIDMap} }; |
|
0
|
|
|
|
|
0
|
|
163
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
# try and lookup local uid using remote username |
165
|
10
|
50
|
|
|
|
55
|
if (my $remote_user = $self->{_remote_user_map}->{$remote_uid}) { |
166
|
0
|
|
|
|
|
0
|
my $local_uid = getpwnam($remote_user); |
167
|
0
|
0
|
|
|
|
0
|
return $self->{_local_uid_map}->{$remote_uid} = $local_uid |
168
|
|
|
|
|
|
|
if defined $local_uid; |
169
|
|
|
|
|
|
|
} |
170
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
# if remote username missing locally, fallback to $remote_uid |
172
|
10
|
|
|
|
|
58
|
return $self->{_local_uid_map}->{$remote_uid} = $remote_uid; |
173
|
|
|
|
|
|
|
} |
174
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
sub _lookup_remote_gid { |
176
|
101
|
|
|
101
|
|
311
|
my ($self, $remote_gid, $meta) = @_; |
177
|
|
|
|
|
|
|
|
178
|
101
|
100
|
|
|
|
586
|
return $self->{_local_gid_map}->{$remote_gid} |
179
|
|
|
|
|
|
|
if defined $self->{_local_gid_map}->{$remote_gid}; |
180
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
# meta remote group map - remote_gid => remote group |
182
|
10
|
|
50
|
|
|
121
|
$self->{_remote_group_map} ||= { map { split /:/, $_, 2 } split /\s+/, $meta->{GIDMap} }; |
|
0
|
|
|
|
|
0
|
|
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
# try and lookup local gid using remote group |
185
|
10
|
50
|
|
|
|
46
|
if (my $remote_group = $self->{_remote_group_map}->{$remote_gid}) { |
186
|
0
|
|
|
|
|
0
|
my $local_gid = getgrnam($remote_group); |
187
|
0
|
0
|
|
|
|
0
|
return $self->{_local_gid_map}->{$remote_gid} = $local_gid |
188
|
|
|
|
|
|
|
if defined $local_gid; |
189
|
|
|
|
|
|
|
} |
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
# if remote group missing locally, fallback to $remote_gid |
192
|
10
|
|
|
|
|
91
|
return $self->{_local_gid_map}->{$remote_gid} = $remote_gid; |
193
|
|
|
|
|
|
|
} |
194
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
sub _chown { |
196
|
101
|
|
|
101
|
|
347
|
my ($self, $full, $it, $type, $meta) = @_; |
197
|
|
|
|
|
|
|
|
198
|
101
|
50
|
|
|
|
834
|
my $uid = $self->_lookup_remote_uid($it->{UID}, $meta) if $it->{UID}; |
199
|
101
|
50
|
|
|
|
702
|
my $gid = $self->_lookup_remote_gid($it->{GID}, $meta) if $it->{GID}; |
200
|
|
|
|
|
|
|
|
201
|
101
|
50
|
|
|
|
348
|
if ($type eq 'l') { |
202
|
0
|
0
|
|
|
|
0
|
if (! defined $self->{_lchown}) { |
203
|
13
|
|
|
13
|
|
106
|
no strict 'subs'; |
|
13
|
|
|
|
|
27
|
|
|
13
|
|
|
|
|
23485
|
|
204
|
0
|
|
0
|
|
|
0
|
$self->{_lchown} = eval { require Lchown } && Lchown::LCHOWN_AVAILABLE; |
205
|
|
|
|
|
|
|
} |
206
|
0
|
0
|
|
|
|
0
|
if ($self->{_lchown}) { |
207
|
0
|
0
|
|
|
|
0
|
Lchown::lchown($uid, -1, $full) if defined $uid; |
208
|
0
|
0
|
|
|
|
0
|
Lchown::lchown(-1, $gid, $full) if defined $gid; |
209
|
|
|
|
|
|
|
} |
210
|
|
|
|
|
|
|
} else { |
211
|
|
|
|
|
|
|
# ignore errors, but change uid and gid separately to sidestep unprivileged failures |
212
|
101
|
50
|
|
|
|
5118
|
chown $uid, -1, $full if defined $uid; |
213
|
101
|
50
|
|
|
|
4017
|
chown -1, $gid, $full if defined $gid; |
214
|
|
|
|
|
|
|
} |
215
|
|
|
|
|
|
|
} |
216
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
sub _update_statinfo { |
218
|
101
|
|
|
101
|
|
680
|
my ($self, $full, $it) = @_; |
219
|
|
|
|
|
|
|
|
220
|
101
|
|
|
|
|
3114
|
push @{ $self->{_stats_to_run} }, sub { |
221
|
101
|
50
|
|
101
|
|
268
|
if (defined $it->{Mode}) { |
222
|
101
|
50
|
|
|
|
3705
|
chmod(oct $it->{Mode}, $full) or |
223
|
|
|
|
|
|
|
die "Failed to change mode of $full: $!"; |
224
|
|
|
|
|
|
|
} |
225
|
|
|
|
|
|
|
|
226
|
101
|
50
|
33
|
|
|
369
|
if ($it->{Mtime} || $it->{Atime}) { |
227
|
101
|
50
|
33
|
|
|
4069
|
utime($it->{Atime} || $it->{Mtime}, |
|
|
|
33
|
|
|
|
|
228
|
|
|
|
|
|
|
$it->{Mtime} || $it->{Atime}, |
229
|
|
|
|
|
|
|
$full) or |
230
|
|
|
|
|
|
|
die "Failed to change utime of $full: $!"; |
231
|
|
|
|
|
|
|
} |
232
|
101
|
|
|
|
|
155
|
}; |
233
|
|
|
|
|
|
|
} |
234
|
|
|
|
|
|
|
|
235
|
|
|
|
|
|
|
sub _exec_statinfo_updates { |
236
|
10
|
|
|
10
|
|
173
|
my $self = shift; |
237
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
# change the modes/times in backwards order, going from deep |
239
|
|
|
|
|
|
|
# files/directories to shallow ones. (so we can reliably change |
240
|
|
|
|
|
|
|
# all the directory mtimes without kernel doing it for us when we |
241
|
|
|
|
|
|
|
# modify files deeper) |
242
|
10
|
|
|
|
|
28
|
while (my $sb = pop @{ $self->{_stats_to_run} }) { |
|
111
|
|
|
|
|
383
|
|
243
|
101
|
|
|
|
|
241
|
$sb->(); |
244
|
|
|
|
|
|
|
} |
245
|
|
|
|
|
|
|
} |
246
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
sub _restore_directory { |
248
|
19
|
|
|
19
|
|
40
|
my ($self, $full, $it) = @_; |
249
|
|
|
|
|
|
|
|
250
|
19
|
100
|
|
|
|
838
|
unless (-d $full) { |
251
|
11
|
50
|
|
|
|
952
|
mkdir $full or # FIXME: permissions on directory |
252
|
|
|
|
|
|
|
die "Failed to make directory: $full ($it->{Path})"; |
253
|
|
|
|
|
|
|
} |
254
|
|
|
|
|
|
|
|
255
|
19
|
|
|
|
|
70
|
$self->_update_statinfo($full, $it); |
256
|
|
|
|
|
|
|
} |
257
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
sub _restore_link { |
259
|
0
|
|
|
0
|
|
0
|
my ($self, $full, $it) = @_; |
260
|
|
|
|
|
|
|
|
261
|
0
|
0
|
|
|
|
0
|
if (-e $full) { |
262
|
|
|
|
|
|
|
# TODO: add --conflict={skip,overwrite} option, defaulting to nothing: which dies |
263
|
0
|
|
|
|
|
0
|
die "Link $full ($it->{Path}) already exists. Aborting."; |
264
|
|
|
|
|
|
|
} |
265
|
0
|
0
|
|
|
|
0
|
symlink $it->{Link}, $full |
266
|
|
|
|
|
|
|
or die "Failed to link"; |
267
|
|
|
|
|
|
|
} |
268
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
sub _restore_fifo { |
270
|
0
|
|
|
0
|
|
0
|
my ($self, $full, $it) = @_; |
271
|
|
|
|
|
|
|
|
272
|
0
|
0
|
|
|
|
0
|
if (-e $full) { |
273
|
0
|
|
|
|
|
0
|
die "Named pipe/fifo $full ($it->{Path}) already exists. Aborting."; |
274
|
|
|
|
|
|
|
} |
275
|
|
|
|
|
|
|
|
276
|
0
|
0
|
|
|
|
0
|
mkfifo($full, $it->{Mode}) or die "mkfifo failed: $!"; |
277
|
|
|
|
|
|
|
|
278
|
0
|
|
|
|
|
0
|
$self->_update_statinfo($full, $it); |
279
|
|
|
|
|
|
|
} |
280
|
|
|
|
|
|
|
|
281
|
|
|
|
|
|
|
sub _restore_file { |
282
|
82
|
|
|
82
|
|
169
|
my ($self, $full, $it) = @_; |
283
|
|
|
|
|
|
|
|
284
|
82
|
50
|
33
|
|
|
3283
|
if (-e $full && -s $full) { |
285
|
|
|
|
|
|
|
# TODO: add --conflict={skip,overwrite} option, defaulting to nothing: which dies |
286
|
0
|
|
|
|
|
0
|
die "File $full ($it->{Path}) already exists. Aborting."; |
287
|
|
|
|
|
|
|
} |
288
|
|
|
|
|
|
|
|
289
|
82
|
50
|
|
|
|
15200
|
sysopen(my $fh, $full, O_CREAT|O_WRONLY|O_TRUNC) or die "Failed to open '$full' for writing: $!"; |
290
|
82
|
|
|
|
|
280
|
binmode($fh); |
291
|
82
|
|
50
|
|
|
891
|
my @chunks = grep { $_ } split(/\s+/, $it->{Chunks} || ""); |
|
100
|
|
|
|
|
577
|
|
292
|
82
|
|
|
|
|
360
|
foreach my $ch (@chunks) { |
293
|
100
|
|
|
|
|
1122
|
my ($offset, $len, $enc_len, $dig) = split(/;/, $ch); |
294
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
# we process files sorted by the dig of their first chunk, caching |
296
|
|
|
|
|
|
|
# the last seen chunk to avoid loading composite chunks multiple |
297
|
|
|
|
|
|
|
# times (all files included in composite chunks are single-chunk |
298
|
|
|
|
|
|
|
# files, by definition). Even for non-composite chunks there is a |
299
|
|
|
|
|
|
|
# speedup if we have single-chunk identical files. |
300
|
100
|
|
|
|
|
181
|
my $dataref; |
301
|
100
|
100
|
100
|
|
|
669
|
if($dig eq ($self->{_cached_dig} || '')) { |
302
|
23
|
50
|
|
|
|
132
|
warn " ** using cached chunk $dig\n" if $self->{verbose}; |
303
|
23
|
|
|
|
|
60
|
$dataref = $self->{_cached_dataref}; |
304
|
|
|
|
|
|
|
} else { |
305
|
77
|
50
|
|
|
|
259
|
warn " ** loading chunk $dig from target\n" if $self->{verbose}; |
306
|
77
|
50
|
|
|
|
1068
|
$dataref = $self->{_target}->load_chunk($dig) or |
307
|
|
|
|
|
|
|
die "Error loading chunk $dig from the restore target\n"; |
308
|
77
|
|
|
|
|
242
|
$self->{_cached_dig} = $dig; |
309
|
77
|
|
|
|
|
183
|
$self->{_cached_dataref} = $dataref; |
310
|
|
|
|
|
|
|
} |
311
|
|
|
|
|
|
|
|
312
|
100
|
|
|
|
|
359
|
my $len_chunk = length $$dataref; |
313
|
|
|
|
|
|
|
|
314
|
|
|
|
|
|
|
# using just a range of the file |
315
|
100
|
100
|
|
|
|
468
|
if ($enc_len =~ /^(\d+)-(\d+)$/) { |
316
|
15
|
|
|
|
|
85
|
my ($from, $to) = ($1, $2); |
317
|
|
|
|
|
|
|
# file range. gotta be at least as big as bigger number |
318
|
15
|
50
|
|
|
|
57
|
unless ($len_chunk >= $to) { |
319
|
0
|
|
|
|
|
0
|
die "Backup chunk $dig isn't at least as big as range: got $len_chunk, needing $to\n"; |
320
|
|
|
|
|
|
|
} |
321
|
15
|
|
|
|
|
90
|
my $region = substr($$dataref, $from, $to-$from); |
322
|
15
|
|
|
|
|
35
|
$dataref = \$region; |
323
|
|
|
|
|
|
|
} else { |
324
|
|
|
|
|
|
|
# using the whole chunk, so make sure fetched size matches |
325
|
|
|
|
|
|
|
# expected size |
326
|
85
|
50
|
|
|
|
347
|
unless ($len_chunk == $enc_len) { |
327
|
0
|
|
|
|
|
0
|
die "Backup chunk $dig isn't of expected length: got $len_chunk, expecting $enc_len\n"; |
328
|
|
|
|
|
|
|
} |
329
|
|
|
|
|
|
|
} |
330
|
|
|
|
|
|
|
|
331
|
100
|
|
|
|
|
578
|
my $decrypted_ref = Brackup::Decrypt::decrypt_data($dataref, meta => $self->{_meta}); |
332
|
100
|
|
|
|
|
1909
|
print $fh $$decrypted_ref; |
333
|
|
|
|
|
|
|
} |
334
|
82
|
50
|
|
|
|
4842
|
close($fh) or die "Close failed"; |
335
|
|
|
|
|
|
|
|
336
|
82
|
50
|
|
|
|
473
|
if (my $good_dig = $it->{Digest}) { |
337
|
82
|
50
|
|
|
|
1533
|
die "not capable of verifying digests of from anything but sha1" |
338
|
|
|
|
|
|
|
unless $good_dig =~ /^sha1:(.+)/; |
339
|
82
|
|
|
|
|
582
|
$good_dig = $1; |
340
|
|
|
|
|
|
|
|
341
|
82
|
50
|
|
|
|
4776
|
sysopen(my $readfh, $full, O_RDONLY) or die "Failed to reopen '$full' for verification: $!"; |
342
|
82
|
|
|
|
|
278
|
binmode($readfh); |
343
|
82
|
|
|
|
|
1775
|
my $sha1 = Digest::SHA1->new; |
344
|
82
|
|
|
|
|
2411
|
$sha1->addfile($readfh); |
345
|
82
|
|
|
|
|
991
|
my $actual_dig = $sha1->hexdigest; |
346
|
|
|
|
|
|
|
|
347
|
|
|
|
|
|
|
# TODO: support --onerror={continue,prompt}, etc, but for now we just die |
348
|
82
|
50
|
33
|
|
|
1811
|
unless ($actual_dig eq $good_dig || $full =~ m!\.brackup-digest\.db\b!) { |
349
|
0
|
|
|
|
|
0
|
die "Digest of restored file ($full) doesn't match"; |
350
|
|
|
|
|
|
|
} |
351
|
|
|
|
|
|
|
} |
352
|
|
|
|
|
|
|
|
353
|
82
|
|
|
|
|
1093
|
$self->_update_statinfo($full, $it); |
354
|
|
|
|
|
|
|
} |
355
|
|
|
|
|
|
|
|
356
|
|
|
|
|
|
|
# returns iterator subref which returns hashrefs or undef on EOF |
357
|
|
|
|
|
|
|
sub parser { |
358
|
11
|
|
|
11
|
0
|
29
|
my $self = shift; |
359
|
11
|
|
|
|
|
69
|
return Brackup::Metafile->open($self->{metafile}->name); |
360
|
|
|
|
|
|
|
} |
361
|
|
|
|
|
|
|
|
362
|
|
|
|
|
|
|
1; |
363
|
|
|
|
|
|
|
|