| 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 |