line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Brackup::Target::Filesystem; |
2
|
5
|
|
|
5
|
|
35
|
use strict; |
|
5
|
|
|
|
|
9
|
|
|
5
|
|
|
|
|
264
|
|
3
|
5
|
|
|
5
|
|
415
|
use warnings; |
|
5
|
|
|
|
|
12
|
|
|
5
|
|
|
|
|
337
|
|
4
|
5
|
|
|
5
|
|
30
|
use base 'Brackup::Target::Filebased'; |
|
5
|
|
|
|
|
11
|
|
|
5
|
|
|
|
|
6461
|
|
5
|
5
|
|
|
5
|
|
41
|
use File::Basename; |
|
5
|
|
|
|
|
13
|
|
|
5
|
|
|
|
|
1217
|
|
6
|
5
|
|
|
5
|
|
75
|
use File::Find (); |
|
5
|
|
|
|
|
14
|
|
|
5
|
|
|
|
|
1044
|
|
7
|
5
|
|
|
5
|
|
31
|
use File::Path; |
|
5
|
|
|
|
|
9
|
|
|
5
|
|
|
|
|
352
|
|
8
|
5
|
|
|
5
|
|
30
|
use File::stat (); |
|
5
|
|
|
|
|
13
|
|
|
5
|
|
|
|
|
215
|
|
9
|
5
|
|
|
5
|
|
30
|
use Brackup::Util qw(io_print_to_fh); |
|
5
|
|
|
|
|
13
|
|
|
5
|
|
|
|
|
35870
|
|
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
sub new { |
13
|
7
|
|
|
7
|
0
|
17
|
my ($class, $confsec) = @_; |
14
|
7
|
|
|
|
|
107
|
my $self = $class->SUPER::new($confsec); |
15
|
|
|
|
|
|
|
|
16
|
7
|
|
|
|
|
75
|
$self->{path} = $confsec->path_value("path"); |
17
|
7
|
|
|
|
|
40
|
$self->{nocolons} = $confsec->value("no_filename_colons"); |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
# LAME: Make it work on Windows |
20
|
7
|
50
|
|
|
|
71
|
$self->{nocolons} = ($^O eq 'MSWin32') unless defined $self->{nocolons}; |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
# see if we're operating in a pre-1.06 environment |
23
|
7
|
50
|
|
|
|
420
|
if (opendir(my $dh, $self->{path})) { |
24
|
7
|
|
|
|
|
31
|
$self->{_no_four_hex_dirs_in_root} = 1; |
25
|
7
|
|
|
|
|
130
|
while (my $file = readdir($dh)) { |
26
|
14
|
50
|
|
|
|
101
|
if ($file =~ /^[0-9a-f]{4}$/) { |
27
|
0
|
|
|
|
|
0
|
$self->{_no_four_hex_dirs_in_root} = 0; |
28
|
|
|
|
|
|
|
} |
29
|
|
|
|
|
|
|
} |
30
|
|
|
|
|
|
|
} |
31
|
|
|
|
|
|
|
|
32
|
7
|
50
|
|
|
|
45
|
if ($ENV{BRACKUP_REARRANGE_FS_TARGET}) { |
33
|
0
|
|
|
|
|
0
|
$self->_upgrade_layout; |
34
|
|
|
|
|
|
|
} |
35
|
|
|
|
|
|
|
|
36
|
7
|
|
|
|
|
154
|
return $self; |
37
|
|
|
|
|
|
|
} |
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
sub new_from_backup_header { |
40
|
11
|
|
|
11
|
0
|
26
|
my ($class, $header) = @_; |
41
|
11
|
|
|
|
|
43
|
my $self = bless {}, $class; |
42
|
11
|
50
|
|
|
|
69
|
$self->{path} = $header->{"BackupPath"} or |
43
|
|
|
|
|
|
|
die "No BackupPath specified in the backup metafile.\n"; |
44
|
11
|
50
|
|
|
|
55
|
$self->{nocolons} = $header->{"NoColons"} or 0; |
45
|
11
|
50
|
|
|
|
563
|
unless (-d $self->{path}) { |
46
|
0
|
|
|
|
|
0
|
die "Restore path $self->{path} doesn't exist.\n"; |
47
|
|
|
|
|
|
|
} |
48
|
11
|
|
|
|
|
45
|
return $self; |
49
|
|
|
|
|
|
|
} |
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
sub nocolons { |
52
|
384
|
|
|
384
|
0
|
1363
|
my ($self) = @_; |
53
|
384
|
|
|
|
|
2671
|
return $self->{nocolons}; |
54
|
|
|
|
|
|
|
} |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
sub backup_header { |
57
|
8
|
|
|
8
|
0
|
20
|
my $self = shift; |
58
|
|
|
|
|
|
|
return { |
59
|
8
|
50
|
|
|
|
219
|
"BackupPath" => $self->{path}, |
60
|
|
|
|
|
|
|
"NoColons" => $self->{nocolons}?"1":"0", |
61
|
|
|
|
|
|
|
}; |
62
|
|
|
|
|
|
|
} |
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
# 1.05 and before stored files on disk as: xxxx/xxxx/xxxxxxxxxx.brackup |
65
|
|
|
|
|
|
|
# (that is, two levels of directories, each 4 hex digits long, or 65536 |
66
|
|
|
|
|
|
|
# files per directory, which is 2x what ext3 can store, leading to errors. |
67
|
|
|
|
|
|
|
# in 1.06 and above, xx/xx/xxxxxx is used. that is, two levels of 2 hex |
68
|
|
|
|
|
|
|
# digits. this function |
69
|
|
|
|
|
|
|
sub _upgrade_layout { |
70
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
71
|
0
|
|
|
|
|
0
|
my $clean_limit = shift; # optional; if set, max top-level dirs to clean |
72
|
|
|
|
|
|
|
|
73
|
0
|
|
|
|
|
0
|
my $root = $self->{path}; |
74
|
|
|
|
|
|
|
|
75
|
0
|
0
|
|
|
|
0
|
opendir(my $dh, $root) or die "Error opening $root: $!"; |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
# read the current state of things in the root directory |
78
|
|
|
|
|
|
|
# (which is presumably maxed out on files, at 32k or whatnot) |
79
|
0
|
|
|
|
|
0
|
my %exist_twodir; # two_dir -> 1 (which two-letter directories exist) |
80
|
|
|
|
|
|
|
my %exist_fourdir; # four_dir -> 1 (which four-letter directories exist) |
81
|
0
|
|
|
|
|
0
|
my %four_of_two; # two_dir -> [ four_dir, four_dir, ... ] |
82
|
0
|
|
|
|
|
0
|
while (my $dir = readdir($dh)) { |
83
|
0
|
0
|
|
|
|
0
|
next unless -d "$root/$dir"; |
84
|
0
|
0
|
|
|
|
0
|
if ($dir =~ /^[0-9a-f]{2}$/) { |
85
|
0
|
|
|
|
|
0
|
$exist_twodir{$dir} = 1; |
86
|
0
|
|
|
|
|
0
|
next; |
87
|
|
|
|
|
|
|
} |
88
|
0
|
0
|
|
|
|
0
|
if ($dir =~ /^([0-9a-f]{2})([0-9a-f]{2})$/) { |
89
|
0
|
|
|
|
|
0
|
$exist_fourdir{"$1$2"} = 1; |
90
|
0
|
|
0
|
|
|
0
|
push @{ $four_of_two{$1} ||= [] }, "$1$2"; |
|
0
|
|
|
|
|
0
|
|
91
|
|
|
|
|
|
|
} |
92
|
|
|
|
|
|
|
} |
93
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
# for each 4-digit directory, sorted by number of four-digit directories |
95
|
|
|
|
|
|
|
# that exist for their leading 2-digit prefix (to most quickly free up |
96
|
|
|
|
|
|
|
# a link in root, in 2 iterations), |
97
|
|
|
|
|
|
|
# see if the "01/" directory exists (the leading two bytes). |
98
|
|
|
|
|
|
|
# if not, |
99
|
|
|
|
|
|
|
# move it to some random other 'xxxx' directory, |
100
|
|
|
|
|
|
|
# as, say, "abcd/tmp-was-root-0123". |
101
|
|
|
|
|
|
|
# now, for either the "0123" directory or "tmp-was-root-0123" |
102
|
|
|
|
|
|
|
# directory, file all chunks, and move them to the |
103
|
|
|
|
|
|
|
# right locations "01/23/*.chunk", making "01/23" if needed. |
104
|
|
|
|
|
|
|
# (shouldn't be any out-of-link problems down one level) |
105
|
0
|
|
|
|
|
0
|
my @four_dirs = map { |
106
|
0
|
|
|
|
|
0
|
sort @{ $four_of_two{$_} } |
|
0
|
|
|
|
|
0
|
|
107
|
|
|
|
|
|
|
} |
108
|
|
|
|
|
|
|
sort { |
109
|
0
|
|
|
|
|
0
|
scalar(@{ $four_of_two{$b} }) <=> scalar(@{ $four_of_two{$a} }) |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
110
|
|
|
|
|
|
|
} keys %four_of_two; |
111
|
0
|
|
|
|
|
0
|
my $n_done; |
112
|
0
|
|
|
|
|
0
|
while (my $four_dir = shift @four_dirs) { |
113
|
0
|
|
|
|
|
0
|
my $leading_two = substr($four_dir, 0, 2); |
114
|
0
|
|
|
|
|
0
|
my $migrate_source; |
115
|
0
|
0
|
|
|
|
0
|
if ($exist_twodir{$leading_two}) { |
|
|
0
|
|
|
|
|
|
116
|
|
|
|
|
|
|
# top-level destination already exists. no need for more |
117
|
|
|
|
|
|
|
# links in the top-level |
118
|
0
|
|
|
|
|
0
|
$migrate_source = $four_dir; |
119
|
|
|
|
|
|
|
} elsif (@four_dirs) { |
120
|
|
|
|
|
|
|
# we need to move four_dir away, into another four_dir, |
121
|
|
|
|
|
|
|
# to make room to create a new two_dir in the root |
122
|
0
|
|
|
|
|
0
|
my $holder_four_dir = $four_dirs[0]; |
123
|
0
|
|
|
|
|
0
|
$migrate_source = "$holder_four_dir/tmp-was-root-$four_dir"; |
124
|
0
|
|
|
|
|
0
|
my $temp_dir = "$root/$migrate_source"; |
125
|
0
|
0
|
|
|
|
0
|
rename "$root/$four_dir", $temp_dir |
126
|
|
|
|
|
|
|
or die "Rename of $root/$four_dir -> $temp_dir failed: $!"; |
127
|
|
|
|
|
|
|
} else { |
128
|
|
|
|
|
|
|
# no four_dirs left? then I bet we aren't out of links |
129
|
|
|
|
|
|
|
# anymore. just migrate. |
130
|
0
|
|
|
|
|
0
|
$migrate_source = $four_dir; |
131
|
|
|
|
|
|
|
} |
132
|
|
|
|
|
|
|
|
133
|
0
|
|
|
|
|
0
|
$self->_upgrade_chunks_in_directory($four_dir, $migrate_source); |
134
|
0
|
0
|
|
|
|
0
|
if (-e "$root/$four_dir") { |
135
|
0
|
|
|
|
|
0
|
die "Upgrade of $root/$four_dir/* didn't seem to have worked."; |
136
|
|
|
|
|
|
|
} |
137
|
0
|
|
|
|
|
0
|
$n_done++; |
138
|
0
|
0
|
0
|
|
|
0
|
last if $clean_limit && $n_done >= $clean_limit; |
139
|
|
|
|
|
|
|
} |
140
|
|
|
|
|
|
|
} |
141
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
sub _upgrade_chunks_in_directory { |
143
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
144
|
0
|
|
|
|
|
0
|
my $four_dig = shift; # first four hex digits of all files being moved |
145
|
0
|
|
|
|
|
0
|
my $rel_dir = shift; # directory (relative to root) to move files from, and then remove |
146
|
0
|
0
|
|
|
|
0
|
die "not relative" unless $rel_dir =~ m!^[^/]!; |
147
|
|
|
|
|
|
|
|
148
|
0
|
|
|
|
|
0
|
my $root = $self->{path}; |
149
|
|
|
|
|
|
|
|
150
|
0
|
0
|
|
|
|
0
|
my ($hex12, $hex34) = $four_dig =~ /^([0-9a-f]{2})([0-9a-f]{2})$/ |
151
|
|
|
|
|
|
|
or die "four_dig not four hex digits"; |
152
|
|
|
|
|
|
|
|
153
|
0
|
|
|
|
|
0
|
my $dest_dir0 = "$root/$hex12"; |
154
|
0
|
|
|
|
|
0
|
my $dest_dir = "$root/$hex12/$hex34"; |
155
|
0
|
|
|
|
|
0
|
for ($dest_dir0, $dest_dir) { |
156
|
0
|
0
|
|
|
|
0
|
next if -d $_; |
157
|
0
|
0
|
|
|
|
0
|
mkdir $_ or die "Failed to mkdir $_: $!"; |
158
|
|
|
|
|
|
|
} |
159
|
|
|
|
|
|
|
|
160
|
0
|
|
|
|
|
0
|
my @dirs; |
161
|
|
|
|
|
|
|
File::Find::find({wanted => sub { |
162
|
0
|
|
|
0
|
|
0
|
my $name = $File::Find::name; |
163
|
0
|
0
|
|
|
|
0
|
if (-f $name) { |
|
|
0
|
|
|
|
|
|
164
|
0
|
|
|
|
|
0
|
my $basefile = $_; # stupid File::Find conventions |
165
|
0
|
0
|
|
|
|
0
|
rename $name, "$dest_dir/$basefile" or die |
166
|
|
|
|
|
|
|
"Failed to move $name to $dest_dir: $!"; |
167
|
|
|
|
|
|
|
} elsif (-d $name) { |
168
|
0
|
0
|
0
|
|
|
0
|
return if $_ eq "." || $_ eq ".."; |
169
|
0
|
|
|
|
|
0
|
push @dirs, $name; |
170
|
|
|
|
|
|
|
} |
171
|
0
|
|
|
|
|
0
|
}}, "$root/$rel_dir"); |
172
|
|
|
|
|
|
|
|
173
|
0
|
|
|
|
|
0
|
my $final_dir = "$root/$four_dig"; |
174
|
0
|
|
|
|
|
0
|
for my $dir (reverse(@dirs), $final_dir) { |
175
|
0
|
0
|
0
|
|
|
0
|
if (!rmdir($dir) && -d $dir) { |
176
|
0
|
|
|
|
|
0
|
warn "Directory not empty? $dir. Skipping cleanup.\n"; |
177
|
0
|
|
|
|
|
0
|
return; |
178
|
|
|
|
|
|
|
} |
179
|
|
|
|
|
|
|
} |
180
|
0
|
|
|
|
|
0
|
warn "Rearranged & removed $four_dig\n"; |
181
|
|
|
|
|
|
|
} |
182
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
# version <= 1.05: 0123/4567/89ab/cdef/0123456789abcdef...xxx.chunk |
184
|
|
|
|
|
|
|
# this is totally stupid. 65k files in root (twice ext3's historical/common |
185
|
|
|
|
|
|
|
# maximum), and the leaves were always containing but one file. |
186
|
|
|
|
|
|
|
sub _old_diskpath { |
187
|
77
|
|
|
77
|
|
193
|
my ($self, $dig) = @_; |
188
|
77
|
|
|
|
|
146
|
my @parts; |
189
|
77
|
|
|
|
|
145
|
my $fulldig = $dig; |
190
|
77
|
|
|
|
|
988
|
$dig =~ s/^\w+://; # remove the "hashtype:" from beginning |
191
|
77
|
50
|
|
|
|
1742
|
$fulldig =~ s/:/./g if $self->nocolons; # Convert colons to dots if we've been asked to |
192
|
77
|
|
66
|
|
|
706
|
while (length $dig && @parts < 4) { |
193
|
308
|
50
|
|
|
|
1985
|
$dig =~ s/^([0-9a-f]{4})// or die "Can't get 4 hex digits of $fulldig"; |
194
|
308
|
|
|
|
|
2207
|
push @parts, $1; |
195
|
|
|
|
|
|
|
} |
196
|
77
|
|
|
|
|
583
|
return $self->{path} . "/" . join("/", @parts) . "/$fulldig.chunk"; |
197
|
|
|
|
|
|
|
} |
198
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
sub chunkpath { |
200
|
273
|
|
|
273
|
0
|
996
|
my ($self, $dig) = @_; |
201
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
# if the old (version <= 1.05) chunk still exists, |
203
|
|
|
|
|
|
|
# just use that, unless we know (from initial scan) |
204
|
|
|
|
|
|
|
# that such paths can't exist, thus avoiding a |
205
|
|
|
|
|
|
|
# bunch of stats() |
206
|
273
|
100
|
|
|
|
1842
|
unless ($self->{_no_four_hex_dirs_in_root}) { |
207
|
77
|
|
|
|
|
335
|
my $old = $self->_old_diskpath($dig); |
208
|
77
|
50
|
|
|
|
2570
|
return $old if -e $old; |
209
|
|
|
|
|
|
|
} |
210
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
# else, use the new (version >= 1.06) location, which |
212
|
|
|
|
|
|
|
# is much more sensible |
213
|
273
|
|
|
|
|
6147
|
return $self->{path} . '/' . $self->SUPER::chunkpath($dig); |
214
|
|
|
|
|
|
|
} |
215
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
sub metapath { |
217
|
16
|
|
|
16
|
0
|
62
|
my ($self, $name) = @_; |
218
|
16
|
|
|
|
|
209
|
return $self->{path} . '/' . $self->SUPER::metapath($name); |
219
|
|
|
|
|
|
|
} |
220
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
sub size { |
222
|
0
|
|
|
0
|
0
|
0
|
my ($self, $path) = @_; |
223
|
0
|
|
|
|
|
0
|
return -s $path; |
224
|
|
|
|
|
|
|
} |
225
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
sub has_chunk_of_handle { |
227
|
0
|
|
|
0
|
0
|
0
|
my ($self, $handle) = @_; |
228
|
0
|
|
|
|
|
0
|
my $dig = $handle->digest; # "sha1:sdfsdf" format scalar |
229
|
0
|
|
|
|
|
0
|
my $path = $self->chunkpath($dig); |
230
|
0
|
|
|
|
|
0
|
return -e $path; |
231
|
|
|
|
|
|
|
} |
232
|
|
|
|
|
|
|
|
233
|
|
|
|
|
|
|
sub load_chunk { |
234
|
184
|
|
|
184
|
0
|
771
|
my ($self, $dig) = @_; |
235
|
184
|
|
|
|
|
1279
|
my $path = $self->chunkpath($dig); |
236
|
184
|
50
|
|
|
|
14019
|
open (my $fh, $path) or die "Error opening $path to load chunk: $!"; |
237
|
184
|
|
|
|
|
490
|
my $chunk = do { local $/; <$fh>; }; |
|
184
|
|
|
|
|
1027
|
|
|
184
|
|
|
|
|
5309
|
|
238
|
184
|
|
|
|
|
4611
|
return \$chunk; |
239
|
|
|
|
|
|
|
} |
240
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
sub has_chunk { |
242
|
0
|
|
|
0
|
0
|
0
|
my ($self, $chunk) = @_; |
243
|
0
|
|
|
|
|
0
|
my $dig = $chunk->backup_digest; |
244
|
0
|
|
|
|
|
0
|
my $blen = $chunk->backup_length; |
245
|
0
|
|
|
|
|
0
|
my $path = $self->chunkpath($dig); |
246
|
0
|
|
|
|
|
0
|
my $exist_size = -s $path; |
247
|
0
|
0
|
0
|
|
|
0
|
if ($exist_size && $exist_size == $blen) { |
248
|
0
|
|
|
|
|
0
|
return 1; |
249
|
|
|
|
|
|
|
} |
250
|
0
|
|
|
|
|
0
|
return 0; |
251
|
|
|
|
|
|
|
} |
252
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
sub store_chunk { |
254
|
79
|
|
|
79
|
0
|
248
|
my ($self, $chunk) = @_; |
255
|
79
|
|
|
|
|
388
|
my $dig = $chunk->backup_digest; |
256
|
79
|
|
|
|
|
382
|
my $blen = $chunk->backup_length; |
257
|
|
|
|
|
|
|
|
258
|
79
|
|
|
|
|
849
|
my $path = $self->chunkpath($dig); |
259
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
# is it already there? then do nothing. |
261
|
79
|
|
|
|
|
5296
|
my $exist_size = -s $path; |
262
|
79
|
50
|
33
|
|
|
325
|
if ($exist_size && $exist_size == $blen) { |
263
|
0
|
|
|
|
|
0
|
return 1; |
264
|
|
|
|
|
|
|
} |
265
|
|
|
|
|
|
|
|
266
|
79
|
|
|
|
|
10690
|
my $dir = dirname($path); |
267
|
|
|
|
|
|
|
|
268
|
79
|
50
|
|
|
|
2236
|
unless (-d $dir) { |
269
|
79
|
50
|
|
|
|
223
|
unless (eval { File::Path::mkpath($dir) }) { |
|
79
|
|
|
|
|
42719
|
|
270
|
0
|
0
|
|
|
|
0
|
if ($!{EMLINK}) { |
271
|
0
|
|
|
|
|
0
|
warn "Too many directories in one directory; doing partial cleanup before proceeding...\n"; |
272
|
|
|
|
|
|
|
# NOTE: 2 directories is key to freeing up one link. imagine upgrading one: |
273
|
|
|
|
|
|
|
# it'd remove "0000" but possibly (likely) create "00". so we do two, |
274
|
|
|
|
|
|
|
# because, following the example, "0001" would also go into "00", so we'd have one |
275
|
|
|
|
|
|
|
# link left in the root. _upgrade_layout orders the directories to clean in |
276
|
|
|
|
|
|
|
# an order such that 2 will succeed or fail, but no higher will succeed when |
277
|
|
|
|
|
|
|
# 2 won't. |
278
|
0
|
|
|
|
|
0
|
$self->_upgrade_layout(2); |
279
|
0
|
0
|
|
|
|
0
|
unless (eval { File::Path::mkpath($dir) }) { |
|
0
|
|
|
|
|
0
|
|
280
|
0
|
|
|
|
|
0
|
die "Still can't create directory $dir: $!\n"; |
281
|
|
|
|
|
|
|
} |
282
|
|
|
|
|
|
|
} else { |
283
|
0
|
|
|
|
|
0
|
die "Failed to mkdir: $dir: $!\n"; |
284
|
|
|
|
|
|
|
} |
285
|
|
|
|
|
|
|
} |
286
|
|
|
|
|
|
|
} |
287
|
|
|
|
|
|
|
|
288
|
79
|
|
|
|
|
415
|
my $partial = "$path.partial"; |
289
|
79
|
50
|
|
|
|
12618
|
open (my $fh, '>', $partial) or die "Failed to open $partial for writing: $!\n"; |
290
|
79
|
|
|
|
|
273
|
binmode($fh); |
291
|
79
|
|
|
|
|
613
|
io_print_to_fh($chunk->chunkref, $fh); |
292
|
79
|
50
|
|
|
|
23601
|
close($fh) or die "Failed to close $path\n"; |
293
|
|
|
|
|
|
|
|
294
|
79
|
|
|
|
|
4331
|
unlink $path; |
295
|
79
|
50
|
|
|
|
13293
|
rename $partial, $path or die "Failed to rename $partial to $path: $!\n"; |
296
|
|
|
|
|
|
|
|
297
|
79
|
|
|
|
|
12527
|
my $actual_size = -s $path; |
298
|
79
|
|
|
|
|
928
|
my $expected_size = $chunk->backup_length; |
299
|
79
|
50
|
|
|
|
276
|
unless (defined($actual_size)) { |
300
|
0
|
|
|
|
|
0
|
die "Chunk output file $path does not exist. Do you need to set no_filename_colons=1?"; |
301
|
|
|
|
|
|
|
} |
302
|
79
|
50
|
|
|
|
329
|
unless ($actual_size == $expected_size) { |
303
|
0
|
|
|
|
|
0
|
die "Chunk $path was written to disk wrong: size is $actual_size, expecting $expected_size\n"; |
304
|
|
|
|
|
|
|
} |
305
|
|
|
|
|
|
|
|
306
|
79
|
|
|
|
|
1343
|
return 1; |
307
|
|
|
|
|
|
|
} |
308
|
|
|
|
|
|
|
|
309
|
|
|
|
|
|
|
sub delete_chunk { |
310
|
10
|
|
|
10
|
0
|
19
|
my ($self, $dig) = @_; |
311
|
10
|
|
|
|
|
219
|
my $path = $self->chunkpath($dig); |
312
|
10
|
|
|
|
|
5351
|
unlink $path; |
313
|
|
|
|
|
|
|
} |
314
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
|
316
|
|
|
|
|
|
|
# returns a list of names of all chunks |
317
|
|
|
|
|
|
|
sub chunks { |
318
|
2
|
|
|
2
|
0
|
4
|
my $self = shift; |
319
|
|
|
|
|
|
|
|
320
|
2
|
|
|
|
|
6
|
my @chunks = (); |
321
|
|
|
|
|
|
|
my $found_chunk = sub { |
322
|
112
|
100
|
|
112
|
|
7776
|
m/\.chunk$/ or return; |
323
|
34
|
|
|
|
|
1365
|
my $chunk_name = basename($_); |
324
|
34
|
|
|
|
|
123
|
$chunk_name =~ s/\.chunk$//; |
325
|
34
|
50
|
|
|
|
84
|
$chunk_name =~ s/\./:/g if $self->nocolons; |
326
|
34
|
|
|
|
|
637
|
push @chunks, $chunk_name; |
327
|
2
|
|
|
|
|
15
|
}; |
328
|
2
|
|
|
|
|
247
|
File::Find::find({ wanted => $found_chunk, no_chdir => 1}, $self->{path}); |
329
|
2
|
|
|
|
|
66
|
return @chunks; |
330
|
|
|
|
|
|
|
} |
331
|
|
|
|
|
|
|
|
332
|
|
|
|
|
|
|
sub store_backup_meta { |
333
|
8
|
|
|
8
|
0
|
34
|
my ($self, $name, $fh) = @_; |
334
|
|
|
|
|
|
|
|
335
|
8
|
|
|
|
|
68
|
my $dir = $self->metapath(); |
336
|
8
|
100
|
|
|
|
311
|
unless (-d $dir) { |
337
|
7
|
50
|
|
|
|
939
|
mkdir $dir or die "Failed to mkdir $dir: $!\n"; |
338
|
|
|
|
|
|
|
} |
339
|
|
|
|
|
|
|
|
340
|
8
|
|
|
|
|
40
|
my $out_filepath = "$dir/$name.brackup"; |
341
|
8
|
50
|
|
|
|
1041
|
open (my $out_fh, '>', $out_filepath) |
342
|
|
|
|
|
|
|
or die "Failed to open metafile '$out_filepath': $!\n"; |
343
|
8
|
|
|
|
|
263
|
io_print_to_fh($fh, $out_fh); |
344
|
8
|
50
|
|
|
|
783
|
close $out_fh or die "Failed to close metafile '$out_filepath': $!\n"; |
345
|
|
|
|
|
|
|
|
346
|
8
|
|
|
|
|
52
|
return 1; |
347
|
|
|
|
|
|
|
} |
348
|
|
|
|
|
|
|
|
349
|
|
|
|
|
|
|
sub backups { |
350
|
5
|
|
|
5
|
0
|
118
|
my ($self) = @_; |
351
|
|
|
|
|
|
|
|
352
|
5
|
|
|
|
|
23
|
my $dir = $self->metapath(); |
353
|
5
|
50
|
|
|
|
211
|
return () unless -d $dir; |
354
|
|
|
|
|
|
|
|
355
|
5
|
50
|
|
|
|
187
|
opendir(my $dh, $dir) or |
356
|
|
|
|
|
|
|
die "Failed to open $dir: $!\n"; |
357
|
|
|
|
|
|
|
|
358
|
5
|
|
|
|
|
14
|
my @ret = (); |
359
|
5
|
|
|
|
|
104
|
while (my $fn = readdir($dh)) { |
360
|
17
|
100
|
|
|
|
123
|
next unless $fn =~ s/\.brackup$//; |
361
|
7
|
|
|
|
|
46
|
my $stat = File::stat::stat("$dir/$fn.brackup"); |
362
|
7
|
|
|
|
|
1322
|
push @ret, Brackup::TargetBackupStatInfo->new($self, $fn, |
363
|
|
|
|
|
|
|
time => $stat->mtime, |
364
|
|
|
|
|
|
|
size => $stat->size); |
365
|
|
|
|
|
|
|
} |
366
|
5
|
|
|
|
|
62
|
closedir($dh); |
367
|
|
|
|
|
|
|
|
368
|
5
|
|
|
|
|
172
|
return @ret; |
369
|
|
|
|
|
|
|
} |
370
|
|
|
|
|
|
|
|
371
|
|
|
|
|
|
|
# downloads the given backup name to the current directory (with |
372
|
|
|
|
|
|
|
# *.brackup extension) or to the specified location |
373
|
|
|
|
|
|
|
sub get_backup { |
374
|
2
|
|
|
2
|
0
|
6
|
my ($self, $name, $output_file) = @_; |
375
|
2
|
|
|
|
|
11
|
my $file = $self->metapath("$name.brackup"); |
376
|
|
|
|
|
|
|
|
377
|
2
|
50
|
|
|
|
68
|
die "File doesn't exist: $file" unless -e $file; |
378
|
|
|
|
|
|
|
|
379
|
2
|
|
33
|
|
|
8
|
$output_file ||= "$name.brackup"; |
380
|
|
|
|
|
|
|
|
381
|
2
|
50
|
|
|
|
99
|
open(my $in, $file) or die "Failed to open $file: $!\n"; |
382
|
2
|
50
|
|
|
|
133
|
open(my $out, '>', $output_file) or die "Failed to open $output_file: $!\n"; |
383
|
|
|
|
|
|
|
|
384
|
2
|
|
|
|
|
4
|
my $buf; |
385
|
|
|
|
|
|
|
my $rv; |
386
|
2
|
|
|
|
|
29
|
while ($rv = sysread($in, $buf, 128*1024)) { |
387
|
2
|
|
|
|
|
79
|
my $outv = syswrite($out, $buf); |
388
|
2
|
50
|
|
|
|
24
|
die "copy error" unless $outv == $rv; |
389
|
|
|
|
|
|
|
} |
390
|
2
|
50
|
|
|
|
9
|
die "copy error" unless defined $rv; |
391
|
|
|
|
|
|
|
|
392
|
2
|
|
|
|
|
48
|
return 1; |
393
|
|
|
|
|
|
|
} |
394
|
|
|
|
|
|
|
|
395
|
|
|
|
|
|
|
sub delete_backup { |
396
|
1
|
|
|
1
|
0
|
2
|
my $self = shift; |
397
|
1
|
|
|
|
|
2
|
my $name = shift; |
398
|
|
|
|
|
|
|
|
399
|
1
|
|
|
|
|
7
|
my $file = $self->metapath("$name.brackup"); |
400
|
1
|
50
|
|
|
|
27
|
die "File doesn't exist: $file" unless -e $file; |
401
|
1
|
|
|
|
|
120
|
unlink $file; |
402
|
1
|
|
|
|
|
6
|
return 1; |
403
|
|
|
|
|
|
|
} |
404
|
|
|
|
|
|
|
|
405
|
|
|
|
|
|
|
1; |
406
|
|
|
|
|
|
|
|
407
|
|
|
|
|
|
|
|
408
|
|
|
|
|
|
|
=head1 NAME |
409
|
|
|
|
|
|
|
|
410
|
|
|
|
|
|
|
Brackup::Target::Filesystem - backup to a locally mounted filesystem |
411
|
|
|
|
|
|
|
|
412
|
|
|
|
|
|
|
=head1 DESCRIPTION |
413
|
|
|
|
|
|
|
|
414
|
|
|
|
|
|
|
Back up to an NFS or Samba server, another disk array (external storage), etc. |
415
|
|
|
|
|
|
|
|
416
|
|
|
|
|
|
|
=head1 EXAMPLE |
417
|
|
|
|
|
|
|
|
418
|
|
|
|
|
|
|
In your ~/.brackup.conf file: |
419
|
|
|
|
|
|
|
|
420
|
|
|
|
|
|
|
[TARGET:nfs_in_garage] |
421
|
|
|
|
|
|
|
type = Filesystem |
422
|
|
|
|
|
|
|
path = /mnt/nfs-garage/brackup/ |
423
|
|
|
|
|
|
|
|
424
|
|
|
|
|
|
|
=head1 CONFIG OPTIONS |
425
|
|
|
|
|
|
|
|
426
|
|
|
|
|
|
|
=over |
427
|
|
|
|
|
|
|
|
428
|
|
|
|
|
|
|
=item B |
429
|
|
|
|
|
|
|
|
430
|
|
|
|
|
|
|
Must be "B". |
431
|
|
|
|
|
|
|
|
432
|
|
|
|
|
|
|
=item B |
433
|
|
|
|
|
|
|
|
434
|
|
|
|
|
|
|
Path to backup to. |
435
|
|
|
|
|
|
|
|
436
|
|
|
|
|
|
|
=back |
437
|
|
|
|
|
|
|
|
438
|
|
|
|
|
|
|
=head1 SEE ALSO |
439
|
|
|
|
|
|
|
|
440
|
|
|
|
|
|
|
L |