1 |
dpavlin |
1 |
#============================================================= -*-perl-*- |
2 |
|
|
# |
3 |
|
|
# BackupPC::View package |
4 |
|
|
# |
5 |
|
|
# DESCRIPTION |
6 |
|
|
# |
7 |
|
|
# This library defines a BackupPC::View class for merging of |
8 |
|
|
# incremental backups and file attributes. This provides the |
9 |
|
|
# caller with a single view of a merged backup, without worrying |
10 |
|
|
# about which backup contributes which files. |
11 |
|
|
# |
12 |
|
|
# AUTHOR |
13 |
|
|
# Craig Barratt <cbarratt@users.sourceforge.net> |
14 |
|
|
# |
15 |
|
|
# COPYRIGHT |
16 |
|
|
# Copyright (C) 2002-2003 Craig Barratt |
17 |
|
|
# |
18 |
|
|
# This program is free software; you can redistribute it and/or modify |
19 |
|
|
# it under the terms of the GNU General Public License as published by |
20 |
|
|
# the Free Software Foundation; either version 2 of the License, or |
21 |
|
|
# (at your option) any later version. |
22 |
|
|
# |
23 |
|
|
# This program is distributed in the hope that it will be useful, |
24 |
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
25 |
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
26 |
|
|
# GNU General Public License for more details. |
27 |
|
|
# |
28 |
|
|
# You should have received a copy of the GNU General Public License |
29 |
|
|
# along with this program; if not, write to the Free Software |
30 |
|
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
31 |
|
|
# |
32 |
|
|
#======================================================================== |
33 |
|
|
# |
34 |
|
|
# Version 2.1.0, released 20 Jun 2004. |
35 |
|
|
# |
36 |
|
|
# See http://backuppc.sourceforge.net. |
37 |
|
|
# |
38 |
|
|
#======================================================================== |
39 |
|
|
|
40 |
|
|
package BackupPC::View; |
41 |
|
|
|
42 |
|
|
use strict; |
43 |
|
|
|
44 |
|
|
use File::Path; |
45 |
|
|
use BackupPC::Lib; |
46 |
|
|
use BackupPC::Attrib qw(:all); |
47 |
|
|
use BackupPC::FileZIO; |
48 |
|
|
use Data::Dumper; |
49 |
|
|
|
50 |
|
|
sub new |
51 |
|
|
{ |
52 |
|
|
my($class, $bpc, $host, $backups) = @_; |
53 |
|
|
my $m = bless { |
54 |
|
|
bpc => $bpc, # BackupPC::Lib object |
55 |
|
|
host => $host, # host name |
56 |
|
|
backups => $backups, # all backups for this host |
57 |
|
|
num => -1, # backup number |
58 |
|
|
idx => -1, # index into backups for backup |
59 |
|
|
# we are viewing |
60 |
|
|
dirPath => undef, # path to current directory |
61 |
|
|
dirAttr => undef, # attributes of current directory |
62 |
|
|
}, $class; |
63 |
|
|
for ( my $i = 0 ; $i < @{$m->{backups}} ; $i++ ) { |
64 |
|
|
next if ( defined($m->{backups}[$i]{level}) ); |
65 |
|
|
$m->{backups}[$i]{level} = $m->{backups}[$i]{type} eq "incr" ? 1 : 0; |
66 |
|
|
} |
67 |
|
|
$m->{topDir} = $m->{bpc}->TopDir(); |
68 |
|
|
return $m; |
69 |
|
|
} |
70 |
|
|
|
71 |
|
|
sub dirCache |
72 |
|
|
{ |
73 |
|
|
my($m, $backupNum, $share, $dir) = @_; |
74 |
|
|
my($i, $level); |
75 |
|
|
|
76 |
|
|
#print STDERR "dirCache($backupNum, $share, $dir)\n"; |
77 |
|
|
$dir = "/$dir" if ( $dir !~ m{^/} ); |
78 |
|
|
$dir =~ s{/+$}{}; |
79 |
|
|
return if ( $m->{num} == $backupNum |
80 |
|
|
&& $m->{share} eq $share |
81 |
|
|
&& defined($m->{dir}) |
82 |
|
|
&& $m->{dir} eq $dir ); |
83 |
|
|
$m->backupNumCache($backupNum) if ( $m->{num} != $backupNum ); |
84 |
|
|
return if ( $m->{idx} < 0 ); |
85 |
|
|
|
86 |
|
|
$m->{files} = {}; |
87 |
|
|
$level = $m->{backups}[$m->{idx}]{level} + 1; |
88 |
|
|
|
89 |
|
|
# |
90 |
|
|
# Remember the requested share and dir |
91 |
|
|
# |
92 |
|
|
$m->{share} = $share; |
93 |
|
|
$m->{dir} = $dir; |
94 |
|
|
|
95 |
|
|
# |
96 |
|
|
# merge backups, starting at the requested one, and working |
97 |
|
|
# backwards until we get to level 0. |
98 |
|
|
# |
99 |
|
|
$m->{mergeNums} = []; |
100 |
|
|
for ( $i = $m->{idx} ; $level > 0 && $i >= 0 ; $i-- ) { |
101 |
|
|
#print(STDERR "Do $i ($m->{backups}[$i]{noFill},$m->{backups}[$i]{level})\n"); |
102 |
|
|
# |
103 |
|
|
# skip backups with the same or higher level |
104 |
|
|
# |
105 |
|
|
next if ( $m->{backups}[$i]{level} >= $level ); |
106 |
|
|
|
107 |
|
|
$level = $m->{backups}[$i]{level}; |
108 |
|
|
$backupNum = $m->{backups}[$i]{num}; |
109 |
|
|
push(@{$m->{mergeNums}}, $backupNum); |
110 |
|
|
my $mangle = $m->{backups}[$i]{mangle}; |
111 |
|
|
my $compress = $m->{backups}[$i]{compress}; |
112 |
|
|
my $path = "$m->{topDir}/pc/$m->{host}/$backupNum/"; |
113 |
|
|
my $sharePathM; |
114 |
|
|
if ( $mangle ) { |
115 |
|
|
$sharePathM = $m->{bpc}->fileNameEltMangle($share) |
116 |
|
|
. $m->{bpc}->fileNameMangle($dir); |
117 |
|
|
} else { |
118 |
|
|
$sharePathM = $share . $dir; |
119 |
|
|
} |
120 |
|
|
$path .= $sharePathM; |
121 |
|
|
#print(STDERR "Opening $path (share=$share)\n"); |
122 |
|
|
if ( !opendir(DIR, $path) ) { |
123 |
|
|
if ( $i == $m->{idx} ) { |
124 |
|
|
# |
125 |
|
|
# Oops, directory doesn't exist. |
126 |
|
|
# |
127 |
|
|
$m->{files} = undef; |
128 |
|
|
return; |
129 |
|
|
} |
130 |
|
|
next; |
131 |
|
|
} |
132 |
|
|
my @dir = readdir(DIR); |
133 |
|
|
closedir(DIR); |
134 |
|
|
my $attr; |
135 |
|
|
if ( $mangle ) { |
136 |
|
|
$attr = BackupPC::Attrib->new({ compress => $compress }); |
137 |
|
|
if ( -f $attr->fileName($path) && !$attr->read($path) ) { |
138 |
|
|
$m->{error} = "Can't read attribute file in $path"; |
139 |
|
|
$attr = undef; |
140 |
|
|
} |
141 |
|
|
} |
142 |
|
|
foreach my $file ( @dir ) { |
143 |
|
|
$file = $1 if ( $file =~ /(.*)/ ); |
144 |
|
|
my $fileUM = $file; |
145 |
|
|
$fileUM = $m->{bpc}->fileNameUnmangle($fileUM) if ( $mangle ); |
146 |
|
|
#print(STDERR "Doing $fileUM\n"); |
147 |
|
|
# |
148 |
|
|
# skip special files |
149 |
|
|
# |
150 |
|
|
next if ( defined($m->{files}{$fileUM}) |
151 |
|
|
|| $file eq ".." |
152 |
|
|
|| $file eq "." |
153 |
|
|
|| $mangle && $file eq "attrib" ); |
154 |
|
|
# |
155 |
|
|
# skip directories in earlier backups (each backup always |
156 |
|
|
# has the complete directory tree). |
157 |
|
|
# |
158 |
|
|
my @s = stat("$path/$file"); |
159 |
|
|
next if ( $i < $m->{idx} && -d _ ); |
160 |
|
|
if ( defined($attr) && defined(my $a = $attr->get($fileUM)) ) { |
161 |
|
|
$m->{files}{$fileUM} = $a; |
162 |
|
|
$attr->set($fileUM, undef); |
163 |
|
|
} else { |
164 |
|
|
# |
165 |
|
|
# Very expensive in the non-attribute case when compresseion |
166 |
|
|
# is on. We have to stat the file and read compressed files |
167 |
|
|
# to determine their size. |
168 |
|
|
# |
169 |
|
|
$m->{files}{$fileUM} = { |
170 |
|
|
type => -d _ ? BPC_FTYPE_DIR : BPC_FTYPE_FILE, |
171 |
|
|
mode => $s[2], |
172 |
|
|
uid => $s[4], |
173 |
|
|
gid => $s[5], |
174 |
|
|
size => -f _ ? $s[7] : 0, |
175 |
|
|
mtime => $s[9], |
176 |
|
|
}; |
177 |
|
|
if ( $compress && -f _ ) { |
178 |
|
|
# |
179 |
|
|
# Compute the correct size by reading the whole file |
180 |
|
|
# |
181 |
|
|
my $f = BackupPC::FileZIO->open("$path/$file", |
182 |
|
|
0, $compress); |
183 |
|
|
if ( !defined($f) ) { |
184 |
|
|
$m->{error} = "Can't open $path/$file"; |
185 |
|
|
} else { |
186 |
|
|
my($data, $size); |
187 |
|
|
while ( $f->read(\$data, 65636 * 8) > 0 ) { |
188 |
|
|
$size += length($data); |
189 |
|
|
} |
190 |
|
|
$f->close; |
191 |
|
|
$m->{files}{$fileUM}{size} = $size; |
192 |
|
|
} |
193 |
|
|
} |
194 |
|
|
} |
195 |
|
|
$m->{files}{$fileUM}{relPath} = "$dir/$fileUM"; |
196 |
|
|
$m->{files}{$fileUM}{sharePathM} = "$sharePathM/$file"; |
197 |
|
|
$m->{files}{$fileUM}{fullPath} = "$path/$file"; |
198 |
|
|
$m->{files}{$fileUM}{backupNum} = $backupNum; |
199 |
|
|
$m->{files}{$fileUM}{compress} = $compress; |
200 |
|
|
$m->{files}{$fileUM}{nlink} = $s[3]; |
201 |
|
|
$m->{files}{$fileUM}{inode} = $s[1]; |
202 |
|
|
} |
203 |
|
|
# |
204 |
|
|
# Also include deleted files |
205 |
|
|
# |
206 |
|
|
if ( defined($attr) ) { |
207 |
|
|
my $a = $attr->get; |
208 |
|
|
foreach my $fileUM ( keys(%$a) ) { |
209 |
|
|
next if ( $a->{$fileUM}{type} != BPC_FTYPE_DELETED ); |
210 |
|
|
my $file = $fileUM; |
211 |
|
|
$file = $m->{bpc}->fileNameMangle($fileUM) if ( $mangle ); |
212 |
|
|
$m->{files}{$fileUM} = $a->{$fileUM}; |
213 |
|
|
$m->{files}{$fileUM}{relPath} = "$dir/$fileUM"; |
214 |
|
|
$m->{files}{$fileUM}{sharePathM} = "$sharePathM/$file"; |
215 |
|
|
$m->{files}{$fileUM}{fullPath} = "$path/$file"; |
216 |
|
|
$m->{files}{$fileUM}{backupNum} = $backupNum; |
217 |
|
|
$m->{files}{$fileUM}{compress} = $compress; |
218 |
|
|
$m->{files}{$fileUM}{nlink} = 0; |
219 |
|
|
$m->{files}{$fileUM}{inode} = 0; |
220 |
|
|
} |
221 |
|
|
} |
222 |
|
|
} |
223 |
|
|
# |
224 |
|
|
# Prune deleted files |
225 |
|
|
# |
226 |
|
|
foreach my $file ( keys(%{$m->{files}}) ) { |
227 |
|
|
next if ( $m->{files}{$file}{type} != BPC_FTYPE_DELETED ); |
228 |
|
|
delete($m->{files}{$file}); |
229 |
|
|
} |
230 |
|
|
#print STDERR "Returning:\n", Dumper($m->{files}); |
231 |
|
|
} |
232 |
|
|
|
233 |
|
|
# |
234 |
|
|
# Return list of shares for this backup |
235 |
|
|
# |
236 |
|
|
sub shareList |
237 |
|
|
{ |
238 |
|
|
my($m, $backupNum) = @_; |
239 |
|
|
my @shareList; |
240 |
|
|
|
241 |
|
|
$m->backupNumCache($backupNum) if ( $m->{num} != $backupNum ); |
242 |
|
|
return if ( $m->{idx} < 0 ); |
243 |
|
|
|
244 |
|
|
my $mangle = $m->{backups}[$m->{idx}]{mangle}; |
245 |
|
|
my $path = "$m->{topDir}/pc/$m->{host}/$backupNum/"; |
246 |
|
|
return if ( !opendir(DIR, $path) ); |
247 |
|
|
my @dir = readdir(DIR); |
248 |
|
|
closedir(DIR); |
249 |
|
|
foreach my $file ( @dir ) { |
250 |
|
|
$file = $1 if ( $file =~ /(.*)/ ); |
251 |
|
|
next if ( $file eq "attrib" && $mangle |
252 |
|
|
|| $file eq "." |
253 |
|
|
|| $file eq ".." ); |
254 |
|
|
my $fileUM = $file; |
255 |
|
|
$fileUM = $m->{bpc}->fileNameUnmangle($fileUM) if ( $mangle ); |
256 |
|
|
push(@shareList, $fileUM); |
257 |
|
|
} |
258 |
|
|
$m->{dir} = undef; |
259 |
|
|
return @shareList; |
260 |
|
|
} |
261 |
|
|
|
262 |
|
|
sub backupNumCache |
263 |
|
|
{ |
264 |
|
|
my($m, $backupNum) = @_; |
265 |
|
|
|
266 |
|
|
if ( $m->{num} != $backupNum ) { |
267 |
|
|
my $i; |
268 |
|
|
for ( $i = 0 ; $i < @{$m->{backups}} ; $i++ ) { |
269 |
|
|
last if ( $m->{backups}[$i]{num} == $backupNum ); |
270 |
|
|
} |
271 |
|
|
if ( $i >= @{$m->{backups}} ) { |
272 |
|
|
$m->{idx} = -1; |
273 |
|
|
return; |
274 |
|
|
} |
275 |
|
|
$m->{num} = $backupNum; |
276 |
|
|
$m->{idx} = $i; |
277 |
|
|
} |
278 |
|
|
} |
279 |
|
|
|
280 |
|
|
# |
281 |
|
|
# Return the attributes of a specific file |
282 |
|
|
# |
283 |
|
|
sub fileAttrib |
284 |
|
|
{ |
285 |
|
|
my($m, $backupNum, $share, $path) = @_; |
286 |
|
|
|
287 |
|
|
#print(STDERR "fileAttrib($backupNum, $share, $path)\n"); |
288 |
|
|
if ( $path =~ s{(.*)/+(.+)}{$1} ) { |
289 |
|
|
my $file = $2; |
290 |
|
|
$m->dirCache($backupNum, $share, $path); |
291 |
|
|
return $m->{files}{$file}; |
292 |
|
|
} else { |
293 |
|
|
#print STDERR "Got empty $path\n"; |
294 |
|
|
$m->dirCache($backupNum, "", ""); |
295 |
|
|
my $attr = $m->{files}{$share}; |
296 |
|
|
return if ( !defined($attr) ); |
297 |
|
|
$attr->{relPath} = "/"; |
298 |
|
|
return $attr; |
299 |
|
|
} |
300 |
|
|
} |
301 |
|
|
|
302 |
|
|
# |
303 |
|
|
# Return the contents of a directory |
304 |
|
|
# |
305 |
|
|
sub dirAttrib |
306 |
|
|
{ |
307 |
|
|
my($m, $backupNum, $share, $dir) = @_; |
308 |
|
|
|
309 |
|
|
$m->dirCache($backupNum, $share, $dir); |
310 |
|
|
return $m->{files}; |
311 |
|
|
} |
312 |
|
|
|
313 |
|
|
# |
314 |
|
|
# Return a listref of backup numbers that are merged to create this view |
315 |
|
|
# |
316 |
|
|
sub mergeNums |
317 |
|
|
{ |
318 |
|
|
my($m) = @_; |
319 |
|
|
|
320 |
|
|
return $m->{mergeNums}; |
321 |
|
|
} |
322 |
|
|
|
323 |
|
|
# |
324 |
|
|
# Return a list of backup indexes for which the directory exists |
325 |
|
|
# |
326 |
|
|
sub backupList |
327 |
|
|
{ |
328 |
|
|
my($m, $share, $dir) = @_; |
329 |
|
|
my($i, @backupList); |
330 |
|
|
|
331 |
|
|
$dir = "/$dir" if ( $dir !~ m{^/} ); |
332 |
|
|
$dir =~ s{/+$}{}; |
333 |
|
|
|
334 |
|
|
for ( $i = 0 ; $i < @{$m->{backups}} ; $i++ ) { |
335 |
|
|
my $backupNum = $m->{backups}[$i]{num}; |
336 |
|
|
my $mangle = $m->{backups}[$i]{mangle}; |
337 |
|
|
my $path = "$m->{topDir}/pc/$m->{host}/$backupNum/"; |
338 |
|
|
my $sharePathM; |
339 |
|
|
if ( $mangle ) { |
340 |
|
|
$sharePathM = $m->{bpc}->fileNameEltMangle($share) |
341 |
|
|
. $m->{bpc}->fileNameMangle($dir); |
342 |
|
|
} else { |
343 |
|
|
$sharePathM = $share . $dir; |
344 |
|
|
} |
345 |
|
|
$path .= $sharePathM; |
346 |
|
|
next if ( !-d $path ); |
347 |
|
|
push(@backupList, $i); |
348 |
|
|
} |
349 |
|
|
return @backupList; |
350 |
|
|
} |
351 |
|
|
|
352 |
|
|
# |
353 |
|
|
# Return the history of all backups for a particular directory |
354 |
|
|
# |
355 |
|
|
sub dirHistory |
356 |
|
|
{ |
357 |
|
|
my($m, $share, $dir) = @_; |
358 |
|
|
my($i, $level); |
359 |
|
|
my $files = {}; |
360 |
|
|
|
361 |
|
|
$dir = "/$dir" if ( $dir !~ m{^/} ); |
362 |
|
|
$dir =~ s{/+$}{}; |
363 |
|
|
|
364 |
|
|
# |
365 |
|
|
# merge backups, starting at the first one, and working |
366 |
|
|
# forward. |
367 |
|
|
# |
368 |
|
|
for ( $i = 0 ; $i < @{$m->{backups}} ; $i++ ) { |
369 |
|
|
$level = $m->{backups}[$i]{level}; |
370 |
|
|
my $backupNum = $m->{backups}[$i]{num}; |
371 |
|
|
my $mangle = $m->{backups}[$i]{mangle}; |
372 |
|
|
my $compress = $m->{backups}[$i]{compress}; |
373 |
|
|
my $path = "$m->{topDir}/pc/$m->{host}/$backupNum/"; |
374 |
|
|
my $sharePathM; |
375 |
|
|
if ( $mangle ) { |
376 |
|
|
$sharePathM = $m->{bpc}->fileNameEltMangle($share) |
377 |
|
|
. $m->{bpc}->fileNameMangle($dir); |
378 |
|
|
} else { |
379 |
|
|
$sharePathM = $share . $dir; |
380 |
|
|
} |
381 |
|
|
$path .= $sharePathM; |
382 |
|
|
#print(STDERR "Opening $path (share=$share)\n"); |
383 |
|
|
if ( !opendir(DIR, $path) ) { |
384 |
|
|
# |
385 |
|
|
# Oops, directory doesn't exist. |
386 |
|
|
# |
387 |
|
|
next; |
388 |
|
|
} |
389 |
|
|
my @dir = readdir(DIR); |
390 |
|
|
closedir(DIR); |
391 |
|
|
my $attr; |
392 |
|
|
if ( $mangle ) { |
393 |
|
|
$attr = BackupPC::Attrib->new({ compress => $compress }); |
394 |
|
|
if ( -f $attr->fileName($path) && !$attr->read($path) ) { |
395 |
|
|
$m->{error} = "Can't read attribute file in $path"; |
396 |
|
|
$attr = undef; |
397 |
|
|
} |
398 |
|
|
} |
399 |
|
|
foreach my $file ( @dir ) { |
400 |
|
|
$file = $1 if ( $file =~ /(.*)/ ); |
401 |
|
|
my $fileUM = $file; |
402 |
|
|
$fileUM = $m->{bpc}->fileNameUnmangle($fileUM) if ( $mangle ); |
403 |
|
|
#print(STDERR "Doing $fileUM\n"); |
404 |
|
|
# |
405 |
|
|
# skip special files |
406 |
|
|
# |
407 |
|
|
next if ( $file eq ".." |
408 |
|
|
|| $file eq "." |
409 |
|
|
|| $mangle && $file eq "attrib" |
410 |
|
|
|| defined($files->{$fileUM}[$i]) ); |
411 |
|
|
my @s = stat("$path/$file"); |
412 |
|
|
if ( defined($attr) && defined(my $a = $attr->get($fileUM)) ) { |
413 |
|
|
$files->{$fileUM}[$i] = $a; |
414 |
|
|
$attr->set($fileUM, undef); |
415 |
|
|
} else { |
416 |
|
|
# |
417 |
|
|
# Very expensive in the non-attribute case when compresseion |
418 |
|
|
# is on. We have to stat the file and read compressed files |
419 |
|
|
# to determine their size. |
420 |
|
|
# |
421 |
|
|
$files->{$fileUM}[$i] = { |
422 |
|
|
type => -d _ ? BPC_FTYPE_DIR : BPC_FTYPE_FILE, |
423 |
|
|
mode => $s[2], |
424 |
|
|
uid => $s[4], |
425 |
|
|
gid => $s[5], |
426 |
|
|
size => -f _ ? $s[7] : 0, |
427 |
|
|
mtime => $s[9], |
428 |
|
|
}; |
429 |
|
|
if ( $compress && -f _ ) { |
430 |
|
|
# |
431 |
|
|
# Compute the correct size by reading the whole file |
432 |
|
|
# |
433 |
|
|
my $f = BackupPC::FileZIO->open("$path/$file", |
434 |
|
|
0, $compress); |
435 |
|
|
if ( !defined($f) ) { |
436 |
|
|
$m->{error} = "Can't open $path/$file"; |
437 |
|
|
} else { |
438 |
|
|
my($data, $size); |
439 |
|
|
while ( $f->read(\$data, 65636 * 8) > 0 ) { |
440 |
|
|
$size += length($data); |
441 |
|
|
} |
442 |
|
|
$f->close; |
443 |
|
|
$files->{$fileUM}[$i]{size} = $size; |
444 |
|
|
} |
445 |
|
|
} |
446 |
|
|
} |
447 |
|
|
$files->{$fileUM}[$i]{relPath} = "$dir/$fileUM"; |
448 |
|
|
$files->{$fileUM}[$i]{sharePathM} = "$sharePathM/$file"; |
449 |
|
|
$files->{$fileUM}[$i]{fullPath} = "$path/$file"; |
450 |
|
|
$files->{$fileUM}[$i]{backupNum} = $backupNum; |
451 |
|
|
$files->{$fileUM}[$i]{compress} = $compress; |
452 |
|
|
$files->{$fileUM}[$i]{nlink} = $s[3]; |
453 |
|
|
$files->{$fileUM}[$i]{inode} = $s[1]; |
454 |
|
|
} |
455 |
|
|
|
456 |
|
|
# |
457 |
|
|
# Merge old backups. Don't merge directories from old |
458 |
|
|
# backups because every backup has an accurate directory |
459 |
|
|
# tree. |
460 |
|
|
# |
461 |
|
|
for ( my $k = $i - 1 ; $level > 0 && $k >= 0 ; $k-- ) { |
462 |
|
|
next if ( $m->{backups}[$k]{level} >= $level ); |
463 |
|
|
$level = $m->{backups}[$k]{level}; |
464 |
|
|
foreach my $fileUM ( keys(%$files) ) { |
465 |
|
|
next if ( !defined($files->{$fileUM}[$k]) |
466 |
|
|
|| defined($files->{$fileUM}[$i]) |
467 |
|
|
|| $files->{$fileUM}[$k]{type} == BPC_FTYPE_DIR ); |
468 |
|
|
$files->{$fileUM}[$i] = $files->{$fileUM}[$k]; |
469 |
|
|
} |
470 |
|
|
} |
471 |
|
|
|
472 |
|
|
# |
473 |
|
|
# Finally, remove deleted files |
474 |
|
|
# |
475 |
|
|
if ( defined($attr) ) { |
476 |
|
|
my $a = $attr->get; |
477 |
|
|
foreach my $fileUM ( keys(%$a) ) { |
478 |
|
|
next if ( $a->{$fileUM}{type} != BPC_FTYPE_DELETED ); |
479 |
|
|
$files->{$fileUM}[$i] = undef if ( defined($files->{$fileUM}) ); |
480 |
|
|
} |
481 |
|
|
} |
482 |
|
|
} |
483 |
|
|
#print STDERR "Returning:\n", Dumper($files); |
484 |
|
|
return $files; |
485 |
|
|
} |
486 |
|
|
|
487 |
|
|
|
488 |
|
|
# |
489 |
|
|
# Do a recursive find starting at the given path (either a file |
490 |
|
|
# or directory). The callback function $callback is called on each |
491 |
|
|
# file and directory. The function arguments are the attrs hashref, |
492 |
|
|
# and additional callback arguments. The search is depth-first if |
493 |
|
|
# depth is set. Returns -1 if $path does not exist. |
494 |
|
|
# |
495 |
|
|
sub find |
496 |
|
|
{ |
497 |
|
|
my($m, $backupNum, $share, $path, $depth, $callback, @callbackArgs) = @_; |
498 |
|
|
|
499 |
|
|
#print(STDERR "find: got $backupNum, $share, $path\n"); |
500 |
|
|
# |
501 |
|
|
# First call the callback on the given $path |
502 |
|
|
# |
503 |
|
|
my $attr = $m->fileAttrib($backupNum, $share, $path); |
504 |
|
|
return -1 if ( !defined($attr) ); |
505 |
|
|
&$callback($attr, @callbackArgs); |
506 |
|
|
return if ( $attr->{type} != BPC_FTYPE_DIR ); |
507 |
|
|
|
508 |
|
|
# |
509 |
|
|
# Now recurse into subdirectories |
510 |
|
|
# |
511 |
|
|
$m->findRecurse($backupNum, $share, $path, $depth, |
512 |
|
|
$callback, @callbackArgs); |
513 |
|
|
} |
514 |
|
|
|
515 |
|
|
# |
516 |
|
|
# Same as find(), except the callback is not called on the current |
517 |
|
|
# $path, only on the contents of $path. So if $path is a file then |
518 |
|
|
# no callback or recursion occurs. |
519 |
|
|
# |
520 |
|
|
sub findRecurse |
521 |
|
|
{ |
522 |
|
|
my($m, $backupNum, $share, $path, $depth, $callback, @callbackArgs) = @_; |
523 |
|
|
|
524 |
|
|
my $attr = $m->dirAttrib($backupNum, $share, $path); |
525 |
|
|
return if ( !defined($attr) ); |
526 |
|
|
foreach my $file ( sort(keys(%$attr)) ) { |
527 |
|
|
&$callback($attr->{$file}, @callbackArgs); |
528 |
|
|
next if ( !$depth || $attr->{$file}{type} != BPC_FTYPE_DIR ); |
529 |
|
|
# |
530 |
|
|
# For depth-first, recurse as we hit each directory |
531 |
|
|
# |
532 |
|
|
$m->findRecurse($backupNum, $share, "$path/$file", $depth, |
533 |
|
|
$callback, @callbackArgs); |
534 |
|
|
} |
535 |
|
|
if ( !$depth ) { |
536 |
|
|
# |
537 |
|
|
# For non-depth, recurse directories after we finish current dir |
538 |
|
|
# |
539 |
|
|
foreach my $file ( keys(%{$attr}) ) { |
540 |
|
|
next if ( $attr->{$file}{type} != BPC_FTYPE_DIR ); |
541 |
|
|
$m->findRecurse($backupNum, $share, "$path/$file", $depth, |
542 |
|
|
$callback, @callbackArgs); |
543 |
|
|
} |
544 |
|
|
} |
545 |
|
|
} |
546 |
|
|
|
547 |
|
|
1; |