1 |
dpavlin |
1 |
#!/bin/perl |
2 |
|
|
#============================================================= -*-perl-*- |
3 |
|
|
# |
4 |
|
|
# BackupPC_dump: Dump a single client. |
5 |
|
|
# |
6 |
|
|
# DESCRIPTION |
7 |
|
|
# |
8 |
|
|
# Usage: BackupPC_dump [-i] [-f] [-d] [-e] [-v] <client> |
9 |
|
|
# |
10 |
|
|
# Flags: |
11 |
|
|
# |
12 |
|
|
# -i Do an incremental dump, overriding any scheduling (but a full |
13 |
|
|
# dump will be done if no dumps have yet succeeded) |
14 |
|
|
# |
15 |
|
|
# -f Do a full dump, overriding any scheduling. |
16 |
|
|
# |
17 |
|
|
# -d Host is a DHCP pool address, and the client argument |
18 |
|
|
# just an IP address. We lookup the NetBios name from |
19 |
|
|
# the IP address. |
20 |
|
|
# |
21 |
|
|
# -e Just do an dump expiry check for the client. Don't do anything |
22 |
|
|
# else. This is used periodically by BackupPC to make sure that |
23 |
|
|
# dhcp hosts have correctly expired old backups. Without this, |
24 |
|
|
# dhcp hosts that are no longer on the network will not expire |
25 |
|
|
# old backups. |
26 |
|
|
# |
27 |
|
|
# -v verbose. for manual usage: prints failure reasons in more detail. |
28 |
|
|
# |
29 |
|
|
# BackupPC_dump is run periodically by BackupPC to backup $client. |
30 |
|
|
# The file $TopDir/pc/$client/backups is read to decide whether a |
31 |
|
|
# full or incremental backup needs to be run. If no backup is |
32 |
|
|
# scheduled, or a ping to $client fails, then BackupPC_dump quits. |
33 |
|
|
# |
34 |
|
|
# The backup is done using the selected XferMethod (smb, tar, rsync etc), |
35 |
|
|
# extracting the dump into $TopDir/pc/$client/new. The xfer output is |
36 |
|
|
# put into $TopDir/pc/$client/XferLOG. |
37 |
|
|
# |
38 |
|
|
# If the dump succeeds (based on parsing the output of the XferMethod): |
39 |
|
|
# - $TopDir/pc/$client/new is renamed to $TopDir/pc/$client/nnn, where |
40 |
|
|
# nnn is the next sequential dump number. |
41 |
|
|
# - $TopDir/pc/$client/XferLOG is renamed to $TopDir/pc/$client/XferLOG.nnn. |
42 |
|
|
# - $TopDir/pc/$client/backups is updated. |
43 |
|
|
# |
44 |
|
|
# If the dump fails: |
45 |
|
|
# - $TopDir/pc/$client/new is moved to $TopDir/trash for later removal. |
46 |
|
|
# - $TopDir/pc/$client/XferLOG is renamed to $TopDir/pc/$client/XferLOG.bad |
47 |
|
|
# for later viewing. |
48 |
|
|
# |
49 |
|
|
# BackupPC_dump communicates to BackupPC via printing to STDOUT. |
50 |
|
|
# |
51 |
|
|
# AUTHOR |
52 |
|
|
# Craig Barratt <cbarratt@users.sourceforge.net> |
53 |
|
|
# |
54 |
|
|
# COPYRIGHT |
55 |
|
|
# Copyright (C) 2001-2003 Craig Barratt |
56 |
|
|
# |
57 |
|
|
# This program is free software; you can redistribute it and/or modify |
58 |
|
|
# it under the terms of the GNU General Public License as published by |
59 |
|
|
# the Free Software Foundation; either version 2 of the License, or |
60 |
|
|
# (at your option) any later version. |
61 |
|
|
# |
62 |
|
|
# This program is distributed in the hope that it will be useful, |
63 |
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
64 |
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
65 |
|
|
# GNU General Public License for more details. |
66 |
|
|
# |
67 |
|
|
# You should have received a copy of the GNU General Public License |
68 |
|
|
# along with this program; if not, write to the Free Software |
69 |
|
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
70 |
|
|
# |
71 |
|
|
#======================================================================== |
72 |
|
|
# |
73 |
|
|
# Version 2.1.0, released 20 Jun 2004. |
74 |
|
|
# |
75 |
|
|
# See http://backuppc.sourceforge.net. |
76 |
|
|
# |
77 |
|
|
#======================================================================== |
78 |
|
|
|
79 |
|
|
use strict; |
80 |
|
|
no utf8; |
81 |
|
|
use lib "__INSTALLDIR__/lib"; |
82 |
|
|
use BackupPC::Lib; |
83 |
|
|
use BackupPC::FileZIO; |
84 |
|
|
use BackupPC::Xfer::Smb; |
85 |
|
|
use BackupPC::Xfer::Tar; |
86 |
|
|
use BackupPC::Xfer::Rsync; |
87 |
|
|
use Socket; |
88 |
|
|
use File::Path; |
89 |
|
|
use File::Find; |
90 |
|
|
use Getopt::Std; |
91 |
|
|
|
92 |
|
|
########################################################################### |
93 |
|
|
# Initialize |
94 |
|
|
########################################################################### |
95 |
|
|
|
96 |
|
|
die("BackupPC::Lib->new failed\n") if ( !(my $bpc = BackupPC::Lib->new) ); |
97 |
|
|
my $TopDir = $bpc->TopDir(); |
98 |
|
|
my $BinDir = $bpc->BinDir(); |
99 |
|
|
my %Conf = $bpc->Conf(); |
100 |
|
|
my $NeedPostCmd; |
101 |
|
|
my $Hosts; |
102 |
|
|
my $SigName; |
103 |
|
|
my $Abort; |
104 |
|
|
|
105 |
|
|
$bpc->ChildInit(); |
106 |
|
|
|
107 |
|
|
my %opts; |
108 |
|
|
if ( !getopts("defiv", \%opts) || @ARGV != 1 ) { |
109 |
|
|
print("usage: $0 [-d] [-e] [-f] [-i] [-v] <client>\n"); |
110 |
|
|
exit(1); |
111 |
|
|
} |
112 |
|
|
if ( $ARGV[0] !~ /^([\w\.\s-]+)$/ ) { |
113 |
|
|
print("$0: bad client name '$ARGV[0]'\n"); |
114 |
|
|
exit(1); |
115 |
|
|
} |
116 |
|
|
my $client = $1; # BackupPC's client name (might not be real host name) |
117 |
|
|
my $hostIP; # this is the IP address |
118 |
|
|
my $host; # this is the real host name |
119 |
|
|
|
120 |
|
|
my($clientURI, $user); |
121 |
|
|
|
122 |
|
|
$bpc->verbose(1) if ( $opts{v} ); |
123 |
|
|
|
124 |
|
|
if ( $opts{d} ) { |
125 |
|
|
# |
126 |
|
|
# The client name $client is simply a DHCP address. We need to check |
127 |
|
|
# if there is any machine at this address, and if so, get the actual |
128 |
|
|
# host name via NetBios using nmblookup. |
129 |
|
|
# |
130 |
|
|
$hostIP = $client; |
131 |
|
|
if ( $bpc->CheckHostAlive($hostIP) < 0 ) { |
132 |
|
|
print(STDERR "Exiting because CheckHostAlive($hostIP) failed\n") |
133 |
|
|
if ( $opts{v} ); |
134 |
|
|
exit(1); |
135 |
|
|
} |
136 |
|
|
if ( $Conf{NmbLookupCmd} eq "" ) { |
137 |
|
|
print(STDERR "Exiting because \$Conf{NmbLookupCmd} is empty\n") |
138 |
|
|
if ( $opts{v} ); |
139 |
|
|
exit(1); |
140 |
|
|
} |
141 |
|
|
($client, $user) = $bpc->NetBiosInfoGet($hostIP); |
142 |
|
|
if ( $client !~ /^([\w\.\s-]+)$/ ) { |
143 |
|
|
print(STDERR "Exiting because NetBiosInfoGet($hostIP) returned" |
144 |
|
|
. " '$client', an invalid host name\n") if ( $opts{v} ); |
145 |
|
|
exit(1) |
146 |
|
|
} |
147 |
|
|
$Hosts = $bpc->HostInfoRead($client); |
148 |
|
|
$host = $client; |
149 |
|
|
} else { |
150 |
|
|
$Hosts = $bpc->HostInfoRead($client); |
151 |
|
|
} |
152 |
|
|
if ( !defined($Hosts->{$client}) ) { |
153 |
|
|
print(STDERR "Exiting because host $client does not exist in the" |
154 |
|
|
. " hosts file\n") if ( $opts{v} ); |
155 |
|
|
exit(1) |
156 |
|
|
} |
157 |
|
|
|
158 |
|
|
my $Dir = "$TopDir/pc/$client"; |
159 |
|
|
my @xferPid = (); |
160 |
|
|
my $tarPid = -1; |
161 |
|
|
|
162 |
|
|
# |
163 |
|
|
# Re-read config file, so we can include the PC-specific config |
164 |
|
|
# |
165 |
|
|
$clientURI = $bpc->uriEsc($client); |
166 |
|
|
if ( defined(my $error = $bpc->ConfigRead($client)) ) { |
167 |
|
|
print("dump failed: Can't read PC's config file: $error\n"); |
168 |
|
|
exit(1); |
169 |
|
|
} |
170 |
|
|
%Conf = $bpc->Conf(); |
171 |
|
|
|
172 |
|
|
# |
173 |
|
|
# Catch various signals |
174 |
|
|
# |
175 |
|
|
$SIG{INT} = \&catch_signal; |
176 |
|
|
$SIG{ALRM} = \&catch_signal; |
177 |
|
|
$SIG{TERM} = \&catch_signal; |
178 |
|
|
$SIG{PIPE} = \&catch_signal; |
179 |
|
|
$SIG{STOP} = \&catch_signal; |
180 |
|
|
$SIG{TSTP} = \&catch_signal; |
181 |
|
|
$SIG{TTIN} = \&catch_signal; |
182 |
|
|
my $Pid = $$; |
183 |
|
|
|
184 |
|
|
# |
185 |
|
|
# Make sure we eventually timeout if there is no activity from |
186 |
|
|
# the data transport program. |
187 |
|
|
# |
188 |
|
|
alarm($Conf{ClientTimeout}); |
189 |
|
|
|
190 |
|
|
mkpath($Dir, 0, 0777) if ( !-d $Dir ); |
191 |
|
|
if ( !-f "$Dir/LOCK" ) { |
192 |
|
|
open(LOCK, ">", "$Dir/LOCK") && close(LOCK); |
193 |
|
|
} |
194 |
|
|
open(LOG, ">>", "$Dir/LOG"); |
195 |
|
|
select(LOG); $| = 1; select(STDOUT); |
196 |
|
|
|
197 |
|
|
# |
198 |
|
|
# For the -e option we just expire backups and quit |
199 |
|
|
# |
200 |
|
|
if ( $opts{e} ) { |
201 |
|
|
BackupExpire($client); |
202 |
|
|
exit(0); |
203 |
|
|
} |
204 |
|
|
|
205 |
|
|
# |
206 |
|
|
# For archive hosts we don't bother any further |
207 |
|
|
# |
208 |
|
|
if ($Conf{XferMethod} eq "archive" ) { |
209 |
|
|
print(STDERR "Exiting because the XferMethod is set to archive\n") |
210 |
|
|
if ( $opts{v} ); |
211 |
|
|
exit(0); |
212 |
|
|
} |
213 |
|
|
|
214 |
|
|
if ( !$opts{d} ) { |
215 |
|
|
# |
216 |
|
|
# In the non-DHCP case, make sure the host can be looked up |
217 |
|
|
# via NS, or otherwise find the IP address via NetBios. |
218 |
|
|
# |
219 |
|
|
if ( $Conf{ClientNameAlias} ne "" ) { |
220 |
|
|
$host = $Conf{ClientNameAlias}; |
221 |
|
|
} else { |
222 |
|
|
$host = $client; |
223 |
|
|
} |
224 |
|
|
if ( !defined(gethostbyname($host)) ) { |
225 |
|
|
# |
226 |
|
|
# Ok, NS doesn't know about it. Maybe it is a NetBios name |
227 |
|
|
# instead. |
228 |
|
|
# |
229 |
|
|
print(STDERR "Name server doesn't know about $host; trying NetBios\n") |
230 |
|
|
if ( $opts{v} ); |
231 |
|
|
if ( !defined($hostIP = $bpc->NetBiosHostIPFind($host)) ) { |
232 |
|
|
print(LOG $bpc->timeStamp, "Can't find host $host via netbios\n"); |
233 |
|
|
print("host not found\n"); |
234 |
|
|
exit(1); |
235 |
|
|
} |
236 |
|
|
} else { |
237 |
|
|
$hostIP = $host; |
238 |
|
|
} |
239 |
|
|
} |
240 |
|
|
|
241 |
|
|
########################################################################### |
242 |
|
|
# Figure out what to do and do it |
243 |
|
|
########################################################################### |
244 |
|
|
|
245 |
|
|
# |
246 |
|
|
# See if we should skip this host during a certain range |
247 |
|
|
# of times. |
248 |
|
|
# |
249 |
|
|
my $err = $bpc->ServerConnect($Conf{ServerHost}, $Conf{ServerPort}); |
250 |
|
|
if ( $err ne "" ) { |
251 |
|
|
print("Can't connect to server ($err)\n"); |
252 |
|
|
print(LOG $bpc->timeStamp, "Can't connect to server ($err)\n"); |
253 |
|
|
exit(1); |
254 |
|
|
} |
255 |
|
|
my $reply = $bpc->ServerMesg("status host($clientURI)"); |
256 |
|
|
$reply = $1 if ( $reply =~ /(.*)/s ); |
257 |
|
|
my(%StatusHost); |
258 |
|
|
eval($reply); |
259 |
|
|
$bpc->ServerDisconnect(); |
260 |
|
|
|
261 |
|
|
# |
262 |
|
|
# For DHCP tell BackupPC which host this is |
263 |
|
|
# |
264 |
|
|
if ( $opts{d} ) { |
265 |
|
|
if ( $StatusHost{activeJob} ) { |
266 |
|
|
# oops, something is already running for this host |
267 |
|
|
print(STDERR "Exiting because backup is already running for $client\n") |
268 |
|
|
if ( $opts{v} ); |
269 |
|
|
exit(0); |
270 |
|
|
} |
271 |
|
|
print("DHCP $hostIP $clientURI\n"); |
272 |
|
|
} |
273 |
|
|
|
274 |
|
|
my($needLink, @Backups, $type, $lastBkupNum, $lastFullBkupNum); |
275 |
|
|
my $lastFull = 0; |
276 |
|
|
my $lastIncr = 0; |
277 |
|
|
my $partialIdx = -1; |
278 |
|
|
my $partialNum; |
279 |
|
|
my $lastPartial = 0; |
280 |
|
|
|
281 |
|
|
if ( $Conf{FullPeriod} == -1 && !$opts{f} && !$opts{i} |
282 |
|
|
|| $Conf{FullPeriod} == -2 ) { |
283 |
|
|
print(STDERR "Exiting because backups are disabled with" |
284 |
|
|
. " \$Conf{FullPeriod} = $Conf{FullPeriod}\n") if ( $opts{v} ); |
285 |
|
|
# |
286 |
|
|
# Tell BackupPC to ignore old failed backups on hosts that |
287 |
|
|
# have backups disabled. |
288 |
|
|
# |
289 |
|
|
print("backups disabled\n") |
290 |
|
|
if ( defined($StatusHost{errorTime}) |
291 |
|
|
&& $StatusHost{reason} ne "Reason_backup_done" |
292 |
|
|
&& time - $StatusHost{errorTime} > 4 * 24 * 3600 ); |
293 |
|
|
NothingToDo($needLink); |
294 |
|
|
} |
295 |
|
|
|
296 |
|
|
if ( !$opts{i} && !$opts{f} && $Conf{BlackoutGoodCnt} >= 0 |
297 |
|
|
&& $StatusHost{aliveCnt} >= $Conf{BlackoutGoodCnt} ) { |
298 |
|
|
my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); |
299 |
|
|
my($currHours) = $hour + $min / 60 + $sec / 3600; |
300 |
|
|
my $blackout; |
301 |
|
|
|
302 |
|
|
# |
303 |
|
|
# Handle backward compatibility with original separate scalar |
304 |
|
|
# parameters. |
305 |
|
|
# |
306 |
|
|
if ( defined($Conf{BlackoutHourBegin}) ) { |
307 |
|
|
push(@{$Conf{BlackoutPeriods}}, |
308 |
|
|
{ |
309 |
|
|
hourBegin => $Conf{BlackoutHourBegin}, |
310 |
|
|
hourEnd => $Conf{BlackoutHourEnd}, |
311 |
|
|
weekDays => $Conf{BlackoutWeekDays}, |
312 |
|
|
} |
313 |
|
|
); |
314 |
|
|
} |
315 |
|
|
foreach my $p ( @{$Conf{BlackoutPeriods}} ) { |
316 |
|
|
# |
317 |
|
|
# Allow blackout to span midnight (specified by BlackoutHourBegin |
318 |
|
|
# being greater than BlackoutHourEnd) |
319 |
|
|
# |
320 |
|
|
next if ( ref($p->{weekDays}) ne "ARRAY" |
321 |
|
|
|| !defined($p->{hourBegin}) |
322 |
|
|
|| !defined($p->{hourEnd}) |
323 |
|
|
); |
324 |
|
|
if ( $p->{hourBegin} > $p->{hourEnd} ) { |
325 |
|
|
$blackout = $p->{hourBegin} <= $currHours |
326 |
|
|
|| $currHours <= $p->{hourEnd}; |
327 |
|
|
if ( $currHours <= $p->{hourEnd} ) { |
328 |
|
|
# |
329 |
|
|
# This is after midnight, so decrement the weekday for the |
330 |
|
|
# weekday check (eg: Monday 11pm-1am means Monday 2300 to |
331 |
|
|
# Tuesday 0100, not Monday 2300-2400 plus Monday 0000-0100). |
332 |
|
|
# |
333 |
|
|
$wday--; |
334 |
|
|
$wday += 7 if ( $wday < 0 ); |
335 |
|
|
} |
336 |
|
|
} else { |
337 |
|
|
$blackout = $p->{hourBegin} <= $currHours |
338 |
|
|
&& $currHours <= $p->{hourEnd}; |
339 |
|
|
} |
340 |
|
|
if ( $blackout && grep($_ == $wday, @{$p->{weekDays}}) ) { |
341 |
|
|
# print(LOG $bpc->timeStamp, "skipping because of blackout" |
342 |
|
|
# . " (alive $StatusHost{aliveCnt} times)\n"); |
343 |
|
|
print(STDERR "Skipping $client because of blackout\n") |
344 |
|
|
if ( $opts{v} ); |
345 |
|
|
NothingToDo($needLink); |
346 |
|
|
} |
347 |
|
|
} |
348 |
|
|
} |
349 |
|
|
|
350 |
|
|
if ( !$opts{i} && !$opts{f} && $StatusHost{backoffTime} > time ) { |
351 |
|
|
printf(LOG "%sskipping because of user requested delay (%.1f hours left)", |
352 |
|
|
$bpc->timeStamp, ($StatusHost{backoffTime} - time) / 3600); |
353 |
|
|
NothingToDo($needLink); |
354 |
|
|
} |
355 |
|
|
|
356 |
|
|
# |
357 |
|
|
# Now see if there are any old backups we should delete |
358 |
|
|
# |
359 |
|
|
BackupExpire($client); |
360 |
|
|
|
361 |
|
|
# |
362 |
|
|
# Read Backup information, and find times of the most recent full and |
363 |
|
|
# incremental backups |
364 |
|
|
# |
365 |
|
|
@Backups = $bpc->BackupInfoRead($client); |
366 |
|
|
for ( my $i = 0 ; $i < @Backups ; $i++ ) { |
367 |
|
|
$needLink = 1 if ( $Backups[$i]{nFilesNew} eq "" |
368 |
|
|
|| -f "$Dir/NewFileList.$Backups[$i]{num}" ); |
369 |
|
|
$lastBkupNum = $Backups[$i]{num}; |
370 |
|
|
if ( $Backups[$i]{type} eq "full" ) { |
371 |
|
|
if ( $lastFull < $Backups[$i]{startTime} ) { |
372 |
|
|
$lastFull = $Backups[$i]{startTime}; |
373 |
|
|
$lastFullBkupNum = $Backups[$i]{num}; |
374 |
|
|
} |
375 |
|
|
} elsif ( $Backups[$i]{type} eq "incr" ) { |
376 |
|
|
$lastIncr = $Backups[$i]{startTime} |
377 |
|
|
if ( $lastIncr < $Backups[$i]{startTime} ); |
378 |
|
|
} elsif ( $Backups[$i]{type} eq "partial" ) { |
379 |
|
|
$partialIdx = $i; |
380 |
|
|
$lastPartial = $Backups[$i]{startTime}; |
381 |
|
|
$partialNum = $Backups[$i]{num}; |
382 |
|
|
} |
383 |
|
|
} |
384 |
|
|
|
385 |
|
|
# |
386 |
|
|
# Decide whether we do nothing, or a full or incremental backup. |
387 |
|
|
# |
388 |
|
|
if ( @Backups == 0 |
389 |
|
|
|| $opts{f} |
390 |
|
|
|| (!$opts{i} && (time - $lastFull > $Conf{FullPeriod} * 24*3600 |
391 |
|
|
&& time - $lastIncr > $Conf{IncrPeriod} * 24*3600)) ) { |
392 |
|
|
$type = "full"; |
393 |
|
|
} elsif ( $opts{i} || (time - $lastIncr > $Conf{IncrPeriod} * 24*3600 |
394 |
|
|
&& time - $lastFull > $Conf{IncrPeriod} * 24*3600) ) { |
395 |
|
|
$type = "incr"; |
396 |
|
|
} else { |
397 |
|
|
NothingToDo($needLink); |
398 |
|
|
} |
399 |
|
|
|
400 |
|
|
# |
401 |
|
|
# Check if $host is alive |
402 |
|
|
# |
403 |
|
|
my $delay = $bpc->CheckHostAlive($hostIP); |
404 |
|
|
if ( $delay < 0 ) { |
405 |
|
|
print(LOG $bpc->timeStamp, "no ping response\n"); |
406 |
|
|
print("no ping response\n"); |
407 |
|
|
print("link $clientURI\n") if ( $needLink ); |
408 |
|
|
exit(1); |
409 |
|
|
} elsif ( $delay > $Conf{PingMaxMsec} ) { |
410 |
|
|
printf(LOG "%sping too slow: %.4gmsec\n", $bpc->timeStamp, $delay); |
411 |
|
|
printf("ping too slow: %.4gmsec (threshold is %gmsec)\n", |
412 |
|
|
$delay, $Conf{PingMaxMsec}); |
413 |
|
|
print("link $clientURI\n") if ( $needLink ); |
414 |
|
|
exit(1); |
415 |
|
|
} |
416 |
|
|
|
417 |
|
|
# |
418 |
|
|
# Make sure it is really the machine we expect (only for fixed addresses, |
419 |
|
|
# since we got the DHCP address above). |
420 |
|
|
# |
421 |
|
|
if ( !$opts{d} && (my $errMsg = CorrectHostCheck($hostIP, $host)) ) { |
422 |
|
|
print(LOG $bpc->timeStamp, "dump failed: $errMsg\n"); |
423 |
|
|
print("dump failed: $errMsg\n"); |
424 |
|
|
exit(1); |
425 |
|
|
} elsif ( $opts{d} ) { |
426 |
|
|
print(LOG $bpc->timeStamp, "$host is dhcp $hostIP, user is $user\n"); |
427 |
|
|
} |
428 |
|
|
|
429 |
|
|
# |
430 |
|
|
# Get a clean directory $Dir/new |
431 |
|
|
# |
432 |
|
|
$bpc->RmTreeDefer("$TopDir/trash", "$Dir/new") if ( -d "$Dir/new" ); |
433 |
|
|
|
434 |
|
|
# |
435 |
|
|
# Setup file extension for compression and open XferLOG output file |
436 |
|
|
# |
437 |
|
|
$Conf{CompressLevel} = 0 if ( !BackupPC::FileZIO->compOk ); |
438 |
|
|
my $fileExt = $Conf{CompressLevel} > 0 ? ".z" : ""; |
439 |
|
|
my $XferLOG = BackupPC::FileZIO->open("$Dir/XferLOG$fileExt", 1, |
440 |
|
|
$Conf{CompressLevel}); |
441 |
|
|
if ( !defined($XferLOG) ) { |
442 |
|
|
print(LOG $bpc->timeStamp, "dump failed: unable to open/create" |
443 |
|
|
. " $Dir/XferLOG$fileExt\n"); |
444 |
|
|
print("dump failed: unable to open/create $Dir/XferLOG$fileExt\n"); |
445 |
|
|
exit(1); |
446 |
|
|
} |
447 |
|
|
|
448 |
|
|
# |
449 |
|
|
# Ignore the partial dump in the case of an incremental |
450 |
|
|
# or when the partial is too old. A partial is a partial full. |
451 |
|
|
# |
452 |
|
|
if ( $type ne "full" || time - $lastPartial > $Conf{PartialAgeMax} * 24*3600 ) { |
453 |
|
|
$partialNum = undef; |
454 |
|
|
$partialIdx = -1; |
455 |
|
|
} |
456 |
|
|
|
457 |
|
|
# |
458 |
|
|
# If this is a partial, copy the old XferLOG file |
459 |
|
|
# |
460 |
|
|
if ( $partialNum ) { |
461 |
|
|
my($compress, $fileName); |
462 |
|
|
if ( -f "$Dir/XferLOG.$partialNum.z" ) { |
463 |
|
|
$fileName = "$Dir/XferLOG.$partialNum.z"; |
464 |
|
|
$compress = 1; |
465 |
|
|
} elsif ( -f "$Dir/XferLOG.$partialNum" ) { |
466 |
|
|
$fileName = "$Dir/XferLOG.$partialNum"; |
467 |
|
|
$compress = 0; |
468 |
|
|
} |
469 |
|
|
if ( my $oldLOG = BackupPC::FileZIO->open($fileName, 0, $compress) ) { |
470 |
|
|
my $data; |
471 |
|
|
while ( $oldLOG->read(\$data, 65536) > 0 ) { |
472 |
|
|
$XferLOG->write(\$data); |
473 |
|
|
} |
474 |
|
|
$oldLOG->close; |
475 |
|
|
} |
476 |
|
|
} |
477 |
|
|
|
478 |
|
|
$XferLOG->writeTeeStderr(1) if ( $opts{v} ); |
479 |
|
|
unlink("$Dir/NewFileList") if ( -f "$Dir/NewFileList" ); |
480 |
|
|
|
481 |
|
|
my $startTime = time(); |
482 |
|
|
my $tarErrs = 0; |
483 |
|
|
my $nFilesExist = 0; |
484 |
|
|
my $sizeExist = 0; |
485 |
|
|
my $sizeExistComp = 0; |
486 |
|
|
my $nFilesTotal = 0; |
487 |
|
|
my $sizeTotal = 0; |
488 |
|
|
my($logMsg, %stat, $xfer, $ShareNames, $noFilesErr); |
489 |
|
|
my $newFilesFH; |
490 |
|
|
|
491 |
|
|
if ( $Conf{XferMethod} eq "tar" ) { |
492 |
|
|
$ShareNames = $Conf{TarShareName}; |
493 |
|
|
} elsif ( $Conf{XferMethod} eq "rsync" || $Conf{XferMethod} eq "rsyncd" ) { |
494 |
|
|
$ShareNames = $Conf{RsyncShareName}; |
495 |
|
|
} else { |
496 |
|
|
$ShareNames = $Conf{SmbShareName}; |
497 |
|
|
} |
498 |
|
|
|
499 |
|
|
$ShareNames = [ $ShareNames ] unless ref($ShareNames) eq "ARRAY"; |
500 |
|
|
|
501 |
|
|
# |
502 |
|
|
# Run an optional pre-dump command |
503 |
|
|
# |
504 |
|
|
UserCommandRun("DumpPreUserCmd"); |
505 |
|
|
$NeedPostCmd = 1; |
506 |
|
|
|
507 |
|
|
# |
508 |
|
|
# Now backup each of the shares |
509 |
|
|
# |
510 |
|
|
for my $shareName ( @$ShareNames ) { |
511 |
|
|
local(*RH, *WH); |
512 |
|
|
|
513 |
|
|
$stat{xferOK} = $stat{hostAbort} = undef; |
514 |
|
|
$stat{hostError} = $stat{lastOutputLine} = undef; |
515 |
|
|
if ( -d "$Dir/new/$shareName" ) { |
516 |
|
|
print(LOG $bpc->timeStamp, |
517 |
|
|
"unexpected repeated share name $shareName skipped\n"); |
518 |
|
|
next; |
519 |
|
|
} |
520 |
|
|
|
521 |
|
|
if ( $Conf{XferMethod} eq "tar" ) { |
522 |
|
|
# |
523 |
|
|
# Use tar (eg: tar/ssh) as the transport program. |
524 |
|
|
# |
525 |
|
|
$xfer = BackupPC::Xfer::Tar->new($bpc); |
526 |
|
|
} elsif ( $Conf{XferMethod} eq "rsync" || $Conf{XferMethod} eq "rsyncd" ) { |
527 |
|
|
# |
528 |
|
|
# Use rsync as the transport program. |
529 |
|
|
# |
530 |
|
|
if ( !defined($xfer = BackupPC::Xfer::Rsync->new($bpc)) ) { |
531 |
|
|
my $errStr = BackupPC::Xfer::Rsync::errStr; |
532 |
|
|
print(LOG $bpc->timeStamp, "dump failed: $errStr\n"); |
533 |
|
|
print("dump failed: $errStr\n"); |
534 |
|
|
UserCommandRun("DumpPostUserCmd") if ( $NeedPostCmd ); |
535 |
|
|
exit(1); |
536 |
|
|
} |
537 |
|
|
} else { |
538 |
|
|
# |
539 |
|
|
# Default is to use smbclient (smb) as the transport program. |
540 |
|
|
# |
541 |
|
|
$xfer = BackupPC::Xfer::Smb->new($bpc); |
542 |
|
|
} |
543 |
|
|
|
544 |
|
|
my $useTar = $xfer->useTar; |
545 |
|
|
|
546 |
|
|
if ( $useTar ) { |
547 |
|
|
# |
548 |
|
|
# This xfer method outputs a tar format file, so we start a |
549 |
|
|
# BackupPC_tarExtract to extract the data. |
550 |
|
|
# |
551 |
|
|
# Create a socketpair to connect the Xfer method to BackupPC_tarExtract |
552 |
|
|
# WH is the write handle for writing, provided to the transport |
553 |
|
|
# program, and RH is the other end of the socket for reading, |
554 |
|
|
# provided to BackupPC_tarExtract. |
555 |
|
|
# |
556 |
|
|
if ( socketpair(RH, WH, AF_UNIX, SOCK_STREAM, PF_UNSPEC) ) { |
557 |
|
|
shutdown(RH, 1); # no writing to this socket |
558 |
|
|
shutdown(WH, 0); # no reading from this socket |
559 |
|
|
setsockopt(RH, SOL_SOCKET, SO_RCVBUF, 8 * 65536); |
560 |
|
|
setsockopt(WH, SOL_SOCKET, SO_SNDBUF, 8 * 65536); |
561 |
|
|
} else { |
562 |
|
|
# |
563 |
|
|
# Default to pipe() if socketpair() doesn't work. |
564 |
|
|
# |
565 |
|
|
pipe(RH, WH); |
566 |
|
|
} |
567 |
|
|
|
568 |
|
|
# |
569 |
|
|
# fork a child for BackupPC_tarExtract. TAR is a file handle |
570 |
|
|
# on which we (the parent) read the stdout & stderr from |
571 |
|
|
# BackupPC_tarExtract. |
572 |
|
|
# |
573 |
|
|
if ( !defined($tarPid = open(TAR, "-|")) ) { |
574 |
|
|
print(LOG $bpc->timeStamp, "can't fork to run tar\n"); |
575 |
|
|
print("can't fork to run tar\n"); |
576 |
|
|
close(RH); |
577 |
|
|
close(WH); |
578 |
|
|
last; |
579 |
|
|
} |
580 |
|
|
binmode(TAR); |
581 |
|
|
if ( !$tarPid ) { |
582 |
|
|
# |
583 |
|
|
# This is the tar child. Close the write end of the pipe, |
584 |
|
|
# clone STDERR to STDOUT, clone STDIN from RH, and then |
585 |
|
|
# exec BackupPC_tarExtract. |
586 |
|
|
# |
587 |
|
|
setpgrp 0,0; |
588 |
|
|
close(WH); |
589 |
|
|
close(STDERR); |
590 |
|
|
open(STDERR, ">&STDOUT"); |
591 |
|
|
close(STDIN); |
592 |
|
|
open(STDIN, "<&RH"); |
593 |
|
|
alarm(0); |
594 |
|
|
exec("$BinDir/BackupPC_tarExtract", $client, $shareName, |
595 |
|
|
$Conf{CompressLevel}); |
596 |
|
|
print(LOG $bpc->timeStamp, |
597 |
|
|
"can't exec $BinDir/BackupPC_tarExtract\n"); |
598 |
|
|
exit(0); |
599 |
|
|
} |
600 |
|
|
} elsif ( !defined($newFilesFH) ) { |
601 |
|
|
# |
602 |
|
|
# We need to create the NewFileList output file |
603 |
|
|
# |
604 |
|
|
local(*NEW_FILES); |
605 |
|
|
open(NEW_FILES, ">", "$TopDir/pc/$client/NewFileList") |
606 |
|
|
|| die("can't open $TopDir/pc/$client/NewFileList"); |
607 |
|
|
$newFilesFH = *NEW_FILES; |
608 |
|
|
binmode(NEW_FILES); |
609 |
|
|
} |
610 |
|
|
|
611 |
|
|
# |
612 |
|
|
# Run the transport program |
613 |
|
|
# |
614 |
|
|
$xfer->args({ |
615 |
|
|
host => $host, |
616 |
|
|
client => $client, |
617 |
|
|
hostIP => $hostIP, |
618 |
|
|
shareName => $shareName, |
619 |
|
|
pipeRH => *RH, |
620 |
|
|
pipeWH => *WH, |
621 |
|
|
XferLOG => $XferLOG, |
622 |
|
|
newFilesFH => $newFilesFH, |
623 |
|
|
outDir => $Dir, |
624 |
|
|
type => $type, |
625 |
|
|
lastFull => $lastFull, |
626 |
|
|
lastBkupNum => $lastBkupNum, |
627 |
|
|
lastFullBkupNum => $lastFullBkupNum, |
628 |
|
|
backups => \@Backups, |
629 |
|
|
compress => $Conf{CompressLevel}, |
630 |
|
|
XferMethod => $Conf{XferMethod}, |
631 |
|
|
logLevel => $Conf{XferLogLevel}, |
632 |
|
|
pidHandler => \&pidHandler, |
633 |
|
|
partialNum => $partialNum, |
634 |
|
|
}); |
635 |
|
|
|
636 |
|
|
if ( !defined($logMsg = $xfer->start()) ) { |
637 |
|
|
print(LOG $bpc->timeStamp, "xfer start failed: ", $xfer->errStr, "\n"); |
638 |
|
|
print("dump failed: ", $xfer->errStr, "\n"); |
639 |
|
|
print("link $clientURI\n") if ( $needLink ); |
640 |
|
|
# |
641 |
|
|
# kill off the tar process, first nicely then forcefully |
642 |
|
|
# |
643 |
|
|
if ( $tarPid > 0 ) { |
644 |
|
|
kill($bpc->sigName2num("INT"), $tarPid); |
645 |
|
|
sleep(1); |
646 |
|
|
kill($bpc->sigName2num("KILL"), $tarPid); |
647 |
|
|
} |
648 |
|
|
if ( @xferPid ) { |
649 |
|
|
kill($bpc->sigName2num("INT"), @xferPid); |
650 |
|
|
sleep(1); |
651 |
|
|
kill($bpc->sigName2num("KILL"), @xferPid); |
652 |
|
|
} |
653 |
|
|
UserCommandRun("DumpPostUserCmd") if ( $NeedPostCmd ); |
654 |
|
|
exit(1); |
655 |
|
|
} |
656 |
|
|
|
657 |
|
|
@xferPid = $xfer->xferPid; |
658 |
|
|
|
659 |
|
|
if ( $useTar ) { |
660 |
|
|
# |
661 |
|
|
# The parent must close both handles on the pipe since the children |
662 |
|
|
# are using these handles now. |
663 |
|
|
# |
664 |
|
|
close(RH); |
665 |
|
|
close(WH); |
666 |
|
|
} |
667 |
|
|
print(LOG $bpc->timeStamp, $logMsg, "\n"); |
668 |
|
|
print("started $type dump, share=$shareName\n"); |
669 |
|
|
|
670 |
|
|
pidHandler(@xferPid); |
671 |
|
|
|
672 |
|
|
if ( $useTar ) { |
673 |
|
|
# |
674 |
|
|
# Parse the output of the transfer program and BackupPC_tarExtract |
675 |
|
|
# while they run. Since we might be reading from two or more children |
676 |
|
|
# we use a select. |
677 |
|
|
# |
678 |
|
|
my($FDread, $tarOut, $mesg); |
679 |
|
|
vec($FDread, fileno(TAR), 1) = 1 if ( $useTar ); |
680 |
|
|
$xfer->setSelectMask(\$FDread); |
681 |
|
|
|
682 |
|
|
SCAN: while ( 1 ) { |
683 |
|
|
my $ein = $FDread; |
684 |
|
|
last if ( $FDread =~ /^\0*$/ ); |
685 |
|
|
select(my $rout = $FDread, undef, $ein, undef); |
686 |
|
|
if ( $useTar ) { |
687 |
|
|
if ( vec($rout, fileno(TAR), 1) ) { |
688 |
|
|
if ( sysread(TAR, $mesg, 8192) <= 0 ) { |
689 |
|
|
vec($FDread, fileno(TAR), 1) = 0; |
690 |
|
|
close(TAR); |
691 |
|
|
} else { |
692 |
|
|
$tarOut .= $mesg; |
693 |
|
|
} |
694 |
|
|
} |
695 |
|
|
while ( $tarOut =~ /(.*?)[\n\r]+(.*)/s ) { |
696 |
|
|
$_ = $1; |
697 |
|
|
$tarOut = $2; |
698 |
|
|
if ( /^ / ) { |
699 |
|
|
$XferLOG->write(\"$_\n"); |
700 |
|
|
} else { |
701 |
|
|
$XferLOG->write(\"tarExtract: $_\n"); |
702 |
|
|
} |
703 |
|
|
if ( /^BackupPC_tarExtact aborting \((.*)\)/ ) { |
704 |
|
|
$stat{hostError} = $1; |
705 |
|
|
} |
706 |
|
|
if ( /^Done: (\d+) errors, (\d+) filesExist, (\d+) sizeExist, (\d+) sizeExistComp, (\d+) filesTotal, (\d+) sizeTotal/ ) { |
707 |
|
|
$tarErrs += $1; |
708 |
|
|
$nFilesExist += $2; |
709 |
|
|
$sizeExist += $3; |
710 |
|
|
$sizeExistComp += $4; |
711 |
|
|
$nFilesTotal += $5; |
712 |
|
|
$sizeTotal += $6; |
713 |
|
|
} |
714 |
|
|
} |
715 |
|
|
} |
716 |
|
|
last if ( !$xfer->readOutput(\$FDread, $rout) ); |
717 |
|
|
while ( my $str = $xfer->logMsgGet ) { |
718 |
|
|
print(LOG $bpc->timeStamp, "xfer: $str\n"); |
719 |
|
|
} |
720 |
|
|
if ( $xfer->getStats->{fileCnt} == 1 ) { |
721 |
|
|
# |
722 |
|
|
# Make sure it is still the machine we expect. We do this while |
723 |
|
|
# the transfer is running to avoid a potential race condition if |
724 |
|
|
# the ip address was reassigned by dhcp just before we started |
725 |
|
|
# the transfer. |
726 |
|
|
# |
727 |
|
|
if ( my $errMsg = CorrectHostCheck($hostIP, $host) ) { |
728 |
|
|
$stat{hostError} = $errMsg if ( $stat{hostError} eq "" ); |
729 |
|
|
last SCAN; |
730 |
|
|
} |
731 |
|
|
} |
732 |
|
|
} |
733 |
|
|
} else { |
734 |
|
|
# |
735 |
|
|
# otherwise the xfer module does everything for us |
736 |
|
|
# |
737 |
|
|
my @results = $xfer->run(); |
738 |
|
|
$tarErrs += $results[0]; |
739 |
|
|
$nFilesExist += $results[1]; |
740 |
|
|
$sizeExist += $results[2]; |
741 |
|
|
$sizeExistComp += $results[3]; |
742 |
|
|
$nFilesTotal += $results[4]; |
743 |
|
|
$sizeTotal += $results[5]; |
744 |
|
|
} |
745 |
|
|
|
746 |
|
|
# |
747 |
|
|
# Merge the xfer status (need to accumulate counts) |
748 |
|
|
# |
749 |
|
|
my $newStat = $xfer->getStats; |
750 |
|
|
if ( $newStat->{fileCnt} == 0 ) { |
751 |
|
|
$noFilesErr ||= "No files dumped for share $shareName"; |
752 |
|
|
} |
753 |
|
|
foreach my $k ( (keys(%stat), keys(%$newStat)) ) { |
754 |
|
|
next if ( !defined($newStat->{$k}) ); |
755 |
|
|
if ( $k =~ /Cnt$/ ) { |
756 |
|
|
$stat{$k} += $newStat->{$k}; |
757 |
|
|
delete($newStat->{$k}); |
758 |
|
|
next; |
759 |
|
|
} |
760 |
|
|
if ( !defined($stat{$k}) ) { |
761 |
|
|
$stat{$k} = $newStat->{$k}; |
762 |
|
|
delete($newStat->{$k}); |
763 |
|
|
next; |
764 |
|
|
} |
765 |
|
|
} |
766 |
|
|
$stat{xferOK} = 0 if ( $stat{hostError} || $stat{hostAbort} ); |
767 |
|
|
if ( !$stat{xferOK} ) { |
768 |
|
|
# |
769 |
|
|
# kill off the tranfer program, first nicely then forcefully |
770 |
|
|
# |
771 |
|
|
if ( @xferPid ) { |
772 |
|
|
kill($bpc->sigName2num("INT"), @xferPid); |
773 |
|
|
sleep(1); |
774 |
|
|
kill($bpc->sigName2num("KILL"), @xferPid); |
775 |
|
|
} |
776 |
|
|
# |
777 |
|
|
# kill off the tar process, first nicely then forcefully |
778 |
|
|
# |
779 |
|
|
if ( $tarPid > 0 ) { |
780 |
|
|
kill($bpc->sigName2num("INT"), $tarPid); |
781 |
|
|
sleep(1); |
782 |
|
|
kill($bpc->sigName2num("KILL"), $tarPid); |
783 |
|
|
} |
784 |
|
|
# |
785 |
|
|
# don't do any more shares on this host |
786 |
|
|
# |
787 |
|
|
last; |
788 |
|
|
} |
789 |
|
|
} |
790 |
|
|
|
791 |
|
|
# |
792 |
|
|
# If this is a full, and any share had zero files then consider the dump bad |
793 |
|
|
# |
794 |
|
|
if ( $type eq "full" && $stat{hostError} eq "" |
795 |
|
|
&& length($noFilesErr) && $Conf{BackupZeroFilesIsFatal} ) { |
796 |
|
|
$stat{hostError} = $noFilesErr; |
797 |
|
|
$stat{xferOK} = 0; |
798 |
|
|
} |
799 |
|
|
|
800 |
|
|
$stat{xferOK} = 0 if ( $Abort ); |
801 |
|
|
|
802 |
|
|
# |
803 |
|
|
# Do one last check to make sure it is still the machine we expect. |
804 |
|
|
# |
805 |
|
|
if ( $stat{xferOK} && (my $errMsg = CorrectHostCheck($hostIP, $host)) ) { |
806 |
|
|
$stat{hostError} = $errMsg; |
807 |
|
|
$stat{xferOK} = 0; |
808 |
|
|
} |
809 |
|
|
|
810 |
|
|
UserCommandRun("DumpPostUserCmd") if ( $NeedPostCmd ); |
811 |
|
|
close($newFilesFH) if ( defined($newFilesFH) ); |
812 |
|
|
|
813 |
|
|
my $endTime = time(); |
814 |
|
|
|
815 |
|
|
# |
816 |
|
|
# If the dump failed, clean up |
817 |
|
|
# |
818 |
|
|
if ( !$stat{xferOK} ) { |
819 |
|
|
$stat{hostError} = $stat{lastOutputLine} if ( $stat{hostError} eq "" ); |
820 |
|
|
if ( $stat{hostError} ) { |
821 |
|
|
print(LOG $bpc->timeStamp, |
822 |
|
|
"Got fatal error during xfer ($stat{hostError})\n"); |
823 |
|
|
$XferLOG->write(\"Got fatal error during xfer ($stat{hostError})\n"); |
824 |
|
|
} |
825 |
|
|
if ( !$Abort ) { |
826 |
|
|
# |
827 |
|
|
# wait a short while and see if the system is still alive |
828 |
|
|
# |
829 |
|
|
sleep(5); |
830 |
|
|
if ( $bpc->CheckHostAlive($hostIP) < 0 ) { |
831 |
|
|
$stat{hostAbort} = 1; |
832 |
|
|
} |
833 |
|
|
if ( $stat{hostAbort} ) { |
834 |
|
|
$stat{hostError} = "lost network connection during backup"; |
835 |
|
|
} |
836 |
|
|
print(LOG $bpc->timeStamp, "Backup aborted ($stat{hostError})\n"); |
837 |
|
|
$XferLOG->write(\"Backup aborted ($stat{hostError})\n"); |
838 |
|
|
} else { |
839 |
|
|
$XferLOG->write(\"Backup aborted by user signal\n"); |
840 |
|
|
} |
841 |
|
|
|
842 |
|
|
# |
843 |
|
|
# Close the log file and call BackupFailCleanup, which exits. |
844 |
|
|
# |
845 |
|
|
BackupFailCleanup(); |
846 |
|
|
} |
847 |
|
|
|
848 |
|
|
my $newNum = BackupSave(); |
849 |
|
|
|
850 |
|
|
my $otherCount = $stat{xferErrCnt} - $stat{xferBadFileCnt} |
851 |
|
|
- $stat{xferBadShareCnt}; |
852 |
|
|
print(LOG $bpc->timeStamp, |
853 |
|
|
"$type backup $newNum complete, $stat{fileCnt} files," |
854 |
|
|
. " $stat{byteCnt} bytes," |
855 |
|
|
. " $stat{xferErrCnt} xferErrs ($stat{xferBadFileCnt} bad files," |
856 |
|
|
. " $stat{xferBadShareCnt} bad shares, $otherCount other)\n"); |
857 |
|
|
|
858 |
|
|
BackupExpire($client); |
859 |
|
|
|
860 |
|
|
print("$type backup complete\n"); |
861 |
|
|
|
862 |
|
|
########################################################################### |
863 |
|
|
# Subroutines |
864 |
|
|
########################################################################### |
865 |
|
|
|
866 |
|
|
sub NothingToDo |
867 |
|
|
{ |
868 |
|
|
my($needLink) = @_; |
869 |
|
|
|
870 |
|
|
print("nothing to do\n"); |
871 |
|
|
print("link $clientURI\n") if ( $needLink ); |
872 |
|
|
exit(0); |
873 |
|
|
} |
874 |
|
|
|
875 |
|
|
sub catch_signal |
876 |
|
|
{ |
877 |
|
|
my $sigName = shift; |
878 |
|
|
|
879 |
|
|
# |
880 |
|
|
# The first time we receive a signal we try to gracefully |
881 |
|
|
# abort the backup. This allows us to keep a partial dump |
882 |
|
|
# with the in-progress file deleted and attribute caches |
883 |
|
|
# flushed to disk etc. |
884 |
|
|
# |
885 |
|
|
if ( !length($SigName) ) { |
886 |
|
|
my $reason; |
887 |
|
|
if ( $sigName eq "INT" ) { |
888 |
|
|
$reason = "aborted by user (signal=$sigName)"; |
889 |
|
|
} else { |
890 |
|
|
$reason = "aborted by signal=$sigName"; |
891 |
|
|
} |
892 |
|
|
if ( $Pid == $$ ) { |
893 |
|
|
# |
894 |
|
|
# Parent logs a message |
895 |
|
|
# |
896 |
|
|
print(LOG $bpc->timeStamp, |
897 |
|
|
"Aborting backup up after signal $sigName\n"); |
898 |
|
|
|
899 |
|
|
# |
900 |
|
|
# Tell xfer to abort |
901 |
|
|
# |
902 |
|
|
$xfer->abort($reason); |
903 |
|
|
|
904 |
|
|
# |
905 |
|
|
# Send ALRMs to BackupPC_tarExtract if we are using it |
906 |
|
|
# |
907 |
|
|
if ( $tarPid > 0 ) { |
908 |
|
|
kill($bpc->sigName2num("ARLM"), $tarPid); |
909 |
|
|
} |
910 |
|
|
|
911 |
|
|
# |
912 |
|
|
# Schedule a 20 second timer in case the clean |
913 |
|
|
# abort doesn't complete |
914 |
|
|
# |
915 |
|
|
alarm(20); |
916 |
|
|
} else { |
917 |
|
|
# |
918 |
|
|
# Children ignore anything other than ALRM and INT |
919 |
|
|
# |
920 |
|
|
if ( $sigName ne "ALRM" && $sigName ne "INT" ) { |
921 |
|
|
return; |
922 |
|
|
} |
923 |
|
|
|
924 |
|
|
# |
925 |
|
|
# The child also tells xfer to abort |
926 |
|
|
# |
927 |
|
|
$xfer->abort($reason); |
928 |
|
|
|
929 |
|
|
# |
930 |
|
|
# Schedule a 15 second timer in case the clean |
931 |
|
|
# abort doesn't complete |
932 |
|
|
# |
933 |
|
|
alarm(15); |
934 |
|
|
} |
935 |
|
|
$SigName = $sigName; |
936 |
|
|
$Abort = 1; |
937 |
|
|
return; |
938 |
|
|
} |
939 |
|
|
|
940 |
|
|
# |
941 |
|
|
# This is a second signal: time to clean up. |
942 |
|
|
# |
943 |
|
|
if ( $Pid != $$ && ($sigName eq "ALRM" || $sigName eq "INT") ) { |
944 |
|
|
# |
945 |
|
|
# Children quit quietly on ALRM or INT |
946 |
|
|
# |
947 |
|
|
exit(1) |
948 |
|
|
} |
949 |
|
|
|
950 |
|
|
# |
951 |
|
|
# Ignore other signals in children |
952 |
|
|
# |
953 |
|
|
return if ( $Pid != $$ ); |
954 |
|
|
|
955 |
|
|
$SIG{$sigName} = 'IGNORE'; |
956 |
|
|
UserCommandRun("DumpPostUserCmd") if ( $NeedPostCmd ); |
957 |
|
|
$XferLOG->write(\"exiting after signal $sigName\n"); |
958 |
|
|
if ( @xferPid ) { |
959 |
|
|
kill($bpc->sigName2num("INT"), @xferPid); |
960 |
|
|
sleep(1); |
961 |
|
|
kill($bpc->sigName2num("KILL"), @xferPid); |
962 |
|
|
} |
963 |
|
|
if ( $tarPid > 0 ) { |
964 |
|
|
kill($bpc->sigName2num("INT"), $tarPid); |
965 |
|
|
sleep(1); |
966 |
|
|
kill($bpc->sigName2num("KILL"), $tarPid); |
967 |
|
|
} |
968 |
|
|
if ( $sigName eq "INT" ) { |
969 |
|
|
$stat{hostError} = "aborted by user (signal=$sigName)"; |
970 |
|
|
} else { |
971 |
|
|
$stat{hostError} = "received signal=$sigName"; |
972 |
|
|
} |
973 |
|
|
BackupFailCleanup(); |
974 |
|
|
} |
975 |
|
|
|
976 |
|
|
sub CheckForNewFiles |
977 |
|
|
{ |
978 |
|
|
if ( -f _ && $File::Find::name !~ /\/fattrib$/ ) { |
979 |
|
|
$nFilesTotal++; |
980 |
|
|
} elsif ( -d _ ) { |
981 |
|
|
# |
982 |
|
|
# No need to check entire tree |
983 |
|
|
# |
984 |
|
|
$File::Find::prune = 1 if ( $nFilesTotal ); |
985 |
|
|
} |
986 |
|
|
} |
987 |
|
|
|
988 |
|
|
sub BackupFailCleanup |
989 |
|
|
{ |
990 |
|
|
my $fileExt = $Conf{CompressLevel} > 0 ? ".z" : ""; |
991 |
|
|
my $keepPartial = 0; |
992 |
|
|
|
993 |
|
|
# |
994 |
|
|
# We keep this backup if it is a full and we actually backed |
995 |
|
|
# up some files. |
996 |
|
|
# |
997 |
|
|
if ( $type eq "full" ) { |
998 |
|
|
if ( $nFilesTotal == 0 && $xfer->getStats->{fileCnt} == 0 ) { |
999 |
|
|
# |
1000 |
|
|
# Xfer didn't report any files, but check in the new |
1001 |
|
|
# directory just in case. |
1002 |
|
|
# |
1003 |
|
|
find(\&CheckForNewFiles, "$Dir/new"); |
1004 |
|
|
$keepPartial = 1 if ( $nFilesTotal ); |
1005 |
|
|
} else { |
1006 |
|
|
# |
1007 |
|
|
# Xfer reported some files |
1008 |
|
|
# |
1009 |
|
|
$keepPartial = 1; |
1010 |
|
|
} |
1011 |
|
|
} |
1012 |
|
|
|
1013 |
|
|
# |
1014 |
|
|
# Don't keep partials if they are disabled |
1015 |
|
|
# |
1016 |
|
|
$keepPartial = 0 if ( $Conf{PartialAgeMax} < 0 ); |
1017 |
|
|
|
1018 |
|
|
if ( !$keepPartial ) { |
1019 |
|
|
# |
1020 |
|
|
# No point in saving this dump; get rid of eveything. |
1021 |
|
|
# |
1022 |
|
|
$XferLOG->close(); |
1023 |
|
|
unlink("$Dir/timeStamp.level0") if ( -f "$Dir/timeStamp.level0" ); |
1024 |
|
|
unlink("$Dir/SmbLOG.bad") if ( -f "$Dir/SmbLOG.bad" ); |
1025 |
|
|
unlink("$Dir/SmbLOG.bad$fileExt") if ( -f "$Dir/SmbLOG.bad$fileExt" ); |
1026 |
|
|
unlink("$Dir/XferLOG.bad") if ( -f "$Dir/XferLOG.bad" ); |
1027 |
|
|
unlink("$Dir/XferLOG.bad$fileExt") if ( -f "$Dir/XferLOG.bad$fileExt" ); |
1028 |
|
|
unlink("$Dir/NewFileList") if ( -f "$Dir/NewFileList" ); |
1029 |
|
|
rename("$Dir/XferLOG$fileExt", "$Dir/XferLOG.bad$fileExt"); |
1030 |
|
|
$bpc->RmTreeDefer("$TopDir/trash", "$Dir/new") if ( -d "$Dir/new" ); |
1031 |
|
|
print("dump failed: $stat{hostError}\n"); |
1032 |
|
|
$XferLOG->close(); |
1033 |
|
|
print("link $clientURI\n") if ( $needLink ); |
1034 |
|
|
exit(1); |
1035 |
|
|
} |
1036 |
|
|
# |
1037 |
|
|
# Ok, now we should save this as a partial dump |
1038 |
|
|
# |
1039 |
|
|
$type = "partial"; |
1040 |
|
|
my $newNum = BackupSave(); |
1041 |
|
|
print("dump failed: $stat{hostError}\n"); |
1042 |
|
|
print("link $clientURI\n") if ( $needLink ); |
1043 |
|
|
print(LOG $bpc->timeStamp, "Saved partial dump $newNum\n"); |
1044 |
|
|
exit(2); |
1045 |
|
|
} |
1046 |
|
|
|
1047 |
|
|
# |
1048 |
|
|
# Decide which old backups should be expired by moving them |
1049 |
|
|
# to $TopDir/trash. |
1050 |
|
|
# |
1051 |
|
|
sub BackupExpire |
1052 |
|
|
{ |
1053 |
|
|
my($client) = @_; |
1054 |
|
|
my($Dir) = "$TopDir/pc/$client"; |
1055 |
|
|
my(@Backups) = $bpc->BackupInfoRead($client); |
1056 |
|
|
my($cntFull, $cntIncr, $firstFull, $firstIncr, $oldestIncr, $oldestFull); |
1057 |
|
|
|
1058 |
|
|
if ( $Conf{FullKeepCnt} <= 0 ) { |
1059 |
|
|
print(LOG $bpc->timeStamp, |
1060 |
|
|
"Invalid value for \$Conf{FullKeepCnt}=$Conf{FullKeepCnt}\n"); |
1061 |
|
|
print(STDERR |
1062 |
|
|
"Invalid value for \$Conf{FullKeepCnt}=$Conf{FullKeepCnt}\n") |
1063 |
|
|
if ( $opts{v} ); |
1064 |
|
|
return; |
1065 |
|
|
} |
1066 |
|
|
while ( 1 ) { |
1067 |
|
|
$cntFull = $cntIncr = 0; |
1068 |
|
|
$oldestIncr = $oldestFull = 0; |
1069 |
|
|
for ( my $i = 0 ; $i < @Backups ; $i++ ) { |
1070 |
|
|
if ( $Backups[$i]{type} eq "full" ) { |
1071 |
|
|
$firstFull = $i if ( $cntFull == 0 ); |
1072 |
|
|
$cntFull++; |
1073 |
|
|
} else { |
1074 |
|
|
$firstIncr = $i if ( $cntIncr == 0 ); |
1075 |
|
|
$cntIncr++; |
1076 |
|
|
} |
1077 |
|
|
} |
1078 |
|
|
$oldestIncr = (time - $Backups[$firstIncr]{startTime}) / (24 * 3600) |
1079 |
|
|
if ( $cntIncr > 0 ); |
1080 |
|
|
$oldestFull = (time - $Backups[$firstFull]{startTime}) / (24 * 3600) |
1081 |
|
|
if ( $cntFull > 0 ); |
1082 |
|
|
if ( $cntIncr > $Conf{IncrKeepCnt} |
1083 |
|
|
|| ($cntIncr > $Conf{IncrKeepCntMin} |
1084 |
|
|
&& $oldestIncr > $Conf{IncrAgeMax}) |
1085 |
|
|
&& (@Backups <= $firstIncr + 1 |
1086 |
|
|
|| $Backups[$firstIncr]{noFill} |
1087 |
|
|
|| !$Backups[$firstIncr + 1]{noFill}) ) { |
1088 |
|
|
# |
1089 |
|
|
# Only delete an incr backup if the Conf settings are satisfied. |
1090 |
|
|
# We also must make sure that either this backup is the most |
1091 |
|
|
# recent one, or it is not filled, or the next backup is filled. |
1092 |
|
|
# (We can't deleted a filled incr if the next backup is not |
1093 |
|
|
# filled.) |
1094 |
|
|
# |
1095 |
|
|
print(LOG $bpc->timeStamp, |
1096 |
|
|
"removing incr backup $Backups[$firstIncr]{num}\n"); |
1097 |
|
|
BackupRemove($client, \@Backups, $firstIncr); |
1098 |
|
|
next; |
1099 |
|
|
} |
1100 |
|
|
|
1101 |
|
|
# |
1102 |
|
|
# Delete any old full backups, according to $Conf{FullKeepCntMin} |
1103 |
|
|
# and $Conf{FullAgeMax}. |
1104 |
|
|
# |
1105 |
|
|
# First make sure that $Conf{FullAgeMax} is at least bigger |
1106 |
|
|
# than $Conf{FullPeriod} * $Conf{FullKeepCnt}, including |
1107 |
|
|
# the exponential array case. |
1108 |
|
|
# |
1109 |
|
|
my $fullKeepCnt = $Conf{FullKeepCnt}; |
1110 |
|
|
$fullKeepCnt = [$fullKeepCnt] if ( ref($fullKeepCnt) ne "ARRAY" ); |
1111 |
|
|
my $fullAgeMax; |
1112 |
|
|
my $fullPeriod = int(0.5 + $Conf{FullPeriod}); |
1113 |
|
|
for ( my $i = 0 ; $i < @$fullKeepCnt ; $i++ ) { |
1114 |
|
|
$fullAgeMax += $fullKeepCnt->[$i] * $fullPeriod; |
1115 |
|
|
$fullPeriod *= 2; |
1116 |
|
|
} |
1117 |
|
|
$fullAgeMax += $fullPeriod; # add some buffer |
1118 |
|
|
|
1119 |
|
|
if ( $cntFull > $Conf{FullKeepCntMin} |
1120 |
|
|
&& $oldestFull > $Conf{FullAgeMax} |
1121 |
|
|
&& $oldestFull > $fullAgeMax |
1122 |
|
|
&& $Conf{FullKeepCntMin} > 0 |
1123 |
|
|
&& $Conf{FullAgeMax} > 0 |
1124 |
|
|
&& (@Backups <= $firstFull + 1 |
1125 |
|
|
|| !$Backups[$firstFull + 1]{noFill}) ) { |
1126 |
|
|
# |
1127 |
|
|
# Only delete a full backup if the Conf settings are satisfied. |
1128 |
|
|
# We also must make sure that either this backup is the most |
1129 |
|
|
# recent one, or the next backup is filled. |
1130 |
|
|
# (We can't deleted a full backup if the next backup is not |
1131 |
|
|
# filled.) |
1132 |
|
|
# |
1133 |
|
|
print(LOG $bpc->timeStamp, |
1134 |
|
|
"removing old full backup $Backups[$firstFull]{num}\n"); |
1135 |
|
|
BackupRemove($client, \@Backups, $firstFull); |
1136 |
|
|
next; |
1137 |
|
|
} |
1138 |
|
|
|
1139 |
|
|
# |
1140 |
|
|
# Do new-style full backup expiry, which includes the the case |
1141 |
|
|
# where $Conf{FullKeepCnt} is an array. |
1142 |
|
|
# |
1143 |
|
|
last if ( !BackupFullExpire($client, \@Backups) ); |
1144 |
|
|
} |
1145 |
|
|
$bpc->BackupInfoWrite($client, @Backups); |
1146 |
|
|
} |
1147 |
|
|
|
1148 |
|
|
# |
1149 |
|
|
# Handle full backup expiry, using exponential periods. |
1150 |
|
|
# |
1151 |
|
|
sub BackupFullExpire |
1152 |
|
|
{ |
1153 |
|
|
my($client, $Backups) = @_; |
1154 |
|
|
my $fullCnt = 0; |
1155 |
|
|
my $fullPeriod = $Conf{FullPeriod}; |
1156 |
|
|
my $origFullPeriod = $fullPeriod; |
1157 |
|
|
my $fullKeepCnt = $Conf{FullKeepCnt}; |
1158 |
|
|
my $fullKeepIdx = 0; |
1159 |
|
|
my(@delete, @fullList); |
1160 |
|
|
|
1161 |
|
|
# |
1162 |
|
|
# Don't delete anything if $Conf{FullPeriod} or $Conf{FullKeepCnt} are |
1163 |
|
|
# not defined - possibly a corrupted config.pl file. |
1164 |
|
|
# |
1165 |
|
|
return if ( !defined($Conf{FullPeriod}) || !defined($Conf{FullKeepCnt}) ); |
1166 |
|
|
|
1167 |
|
|
# |
1168 |
|
|
# If regular backups are still disabled with $Conf{FullPeriod} < 0, |
1169 |
|
|
# we still expire backups based on a typical FullPeriod value - weekly. |
1170 |
|
|
# |
1171 |
|
|
$fullPeriod = 7 if ( $fullPeriod <= 0 ); |
1172 |
|
|
|
1173 |
|
|
$fullKeepCnt = [$fullKeepCnt] if ( ref($fullKeepCnt) ne "ARRAY" ); |
1174 |
|
|
|
1175 |
|
|
for ( my $i = 0 ; $i < @$Backups ; $i++ ) { |
1176 |
|
|
next if ( $Backups->[$i]{type} ne "full" ); |
1177 |
|
|
push(@fullList, $i); |
1178 |
|
|
} |
1179 |
|
|
for ( my $k = @fullList - 1 ; $k >= 0 ; $k-- ) { |
1180 |
|
|
my $i = $fullList[$k]; |
1181 |
|
|
my $prevFull = $fullList[$k-1] if ( $k > 0 ); |
1182 |
|
|
# |
1183 |
|
|
# Don't delete any full that is followed by an unfilled backup, |
1184 |
|
|
# since it is needed for restore. |
1185 |
|
|
# |
1186 |
|
|
my $noDelete = $i + 1 < @$Backups ? $Backups->[$i+1]{noFill} : 0; |
1187 |
|
|
|
1188 |
|
|
if ( !$noDelete && |
1189 |
|
|
($fullKeepIdx >= @$fullKeepCnt |
1190 |
|
|
|| $k > 0 |
1191 |
|
|
&& $fullKeepIdx > 0 |
1192 |
|
|
&& $Backups->[$i]{startTime} - $Backups->[$prevFull]{startTime} |
1193 |
|
|
< ($fullPeriod - $origFullPeriod / 2) * 24 * 3600 |
1194 |
|
|
) |
1195 |
|
|
) { |
1196 |
|
|
# |
1197 |
|
|
# Delete the full backup |
1198 |
|
|
# |
1199 |
|
|
#printf("Deleting backup $i ($prevFull)\n"); |
1200 |
|
|
push(@delete, $i); |
1201 |
|
|
} else { |
1202 |
|
|
$fullCnt++; |
1203 |
|
|
while ( $fullKeepIdx < @$fullKeepCnt |
1204 |
|
|
&& $fullCnt >= $fullKeepCnt->[$fullKeepIdx] ) { |
1205 |
|
|
$fullKeepIdx++; |
1206 |
|
|
$fullCnt = 0; |
1207 |
|
|
$fullPeriod = 2 * $fullPeriod; |
1208 |
|
|
} |
1209 |
|
|
} |
1210 |
|
|
} |
1211 |
|
|
# |
1212 |
|
|
# Now actually delete the backups |
1213 |
|
|
# |
1214 |
|
|
for ( my $i = @delete - 1 ; $i >= 0 ; $i-- ) { |
1215 |
|
|
print(LOG $bpc->timeStamp, |
1216 |
|
|
"removing full backup $Backups->[$delete[$i]]{num}\n"); |
1217 |
|
|
BackupRemove($client, $Backups, $delete[$i]); |
1218 |
|
|
} |
1219 |
|
|
return @delete; |
1220 |
|
|
} |
1221 |
|
|
|
1222 |
|
|
# |
1223 |
|
|
# Removes any partial backups |
1224 |
|
|
# |
1225 |
|
|
sub BackupPartialRemove |
1226 |
|
|
{ |
1227 |
|
|
my($client, $Backups) = @_; |
1228 |
|
|
|
1229 |
|
|
for ( my $i = @$Backups - 1 ; $i >= 0 ; $i-- ) { |
1230 |
|
|
next if ( $Backups->[$i]{type} ne "partial" ); |
1231 |
|
|
BackupRemove($client, $Backups, $i); |
1232 |
|
|
} |
1233 |
|
|
} |
1234 |
|
|
|
1235 |
|
|
sub BackupSave |
1236 |
|
|
{ |
1237 |
|
|
my @Backups = $bpc->BackupInfoRead($client); |
1238 |
|
|
my $num = -1; |
1239 |
|
|
|
1240 |
|
|
# |
1241 |
|
|
# Since we got a good backup we should remove any partial dumps |
1242 |
|
|
# (the new backup might also be a partial, but that's ok). |
1243 |
|
|
# |
1244 |
|
|
BackupPartialRemove($client, \@Backups); |
1245 |
|
|
|
1246 |
|
|
# |
1247 |
|
|
# Number the new backup |
1248 |
|
|
# |
1249 |
|
|
for ( my $i = 0 ; $i < @Backups ; $i++ ) { |
1250 |
|
|
$num = $Backups[$i]{num} if ( $num < $Backups[$i]{num} ); |
1251 |
|
|
} |
1252 |
|
|
$num++; |
1253 |
|
|
$bpc->RmTreeDefer("$TopDir/trash", "$Dir/$num") if ( -d "$Dir/$num" ); |
1254 |
|
|
if ( !rename("$Dir/new", "$Dir/$num") ) { |
1255 |
|
|
print(LOG $bpc->timeStamp, "Rename $Dir/new -> $Dir/$num failed\n"); |
1256 |
|
|
$stat{xferOK} = 0; |
1257 |
|
|
} |
1258 |
|
|
$needLink = 1 if ( -f "$Dir/NewFileList" ); |
1259 |
|
|
|
1260 |
|
|
# |
1261 |
|
|
# Add the new backup information to the backup file |
1262 |
|
|
# |
1263 |
|
|
my $i = @Backups; |
1264 |
|
|
$Backups[$i]{num} = $num; |
1265 |
|
|
$Backups[$i]{type} = $type; |
1266 |
|
|
$Backups[$i]{startTime} = $startTime; |
1267 |
|
|
$Backups[$i]{endTime} = $endTime; |
1268 |
|
|
$Backups[$i]{size} = $sizeTotal; |
1269 |
|
|
$Backups[$i]{nFiles} = $nFilesTotal; |
1270 |
|
|
$Backups[$i]{xferErrs} = $stat{xferErrCnt} || 0; |
1271 |
|
|
$Backups[$i]{xferBadFile} = $stat{xferBadFileCnt} || 0; |
1272 |
|
|
$Backups[$i]{xferBadShare} = $stat{xferBadShareCnt} || 0; |
1273 |
|
|
$Backups[$i]{nFilesExist} = $nFilesExist; |
1274 |
|
|
$Backups[$i]{sizeExist} = $sizeExist; |
1275 |
|
|
$Backups[$i]{sizeExistComp} = $sizeExistComp; |
1276 |
|
|
$Backups[$i]{tarErrs} = $tarErrs; |
1277 |
|
|
$Backups[$i]{compress} = $Conf{CompressLevel}; |
1278 |
|
|
$Backups[$i]{noFill} = $type eq "incr" ? 1 : 0; |
1279 |
|
|
$Backups[$i]{level} = $type eq "incr" ? 1 : 0; |
1280 |
|
|
$Backups[$i]{mangle} = 1; # name mangling always on for v1.04+ |
1281 |
|
|
$bpc->BackupInfoWrite($client, @Backups); |
1282 |
|
|
|
1283 |
|
|
unlink("$Dir/timeStamp.level0") if ( -f "$Dir/timeStamp.level0" ); |
1284 |
|
|
foreach my $ext ( qw(bad bad.z) ) { |
1285 |
|
|
next if ( !-f "$Dir/XferLOG.$ext" ); |
1286 |
|
|
unlink("$Dir/XferLOG.$ext.old") if ( -f "$Dir/XferLOG.$ext" ); |
1287 |
|
|
rename("$Dir/XferLOG.$ext", "$Dir/XferLOG.$ext.old"); |
1288 |
|
|
} |
1289 |
|
|
|
1290 |
|
|
# |
1291 |
|
|
# Now remove the bad files, replacing them if possible with links to |
1292 |
|
|
# earlier backups. |
1293 |
|
|
# |
1294 |
|
|
foreach my $f ( $xfer->getBadFiles ) { |
1295 |
|
|
my $j; |
1296 |
|
|
my $shareM = $bpc->fileNameEltMangle($f->{share}); |
1297 |
|
|
my $fileM = $bpc->fileNameMangle($f->{file}); |
1298 |
|
|
unlink("$Dir/$num/$shareM/$fileM"); |
1299 |
|
|
for ( $j = $i - 1 ; $j >= 0 ; $j-- ) { |
1300 |
|
|
my $file; |
1301 |
|
|
if ( $Backups[$j]{mangle} ) { |
1302 |
|
|
$file = "$shareM/$fileM"; |
1303 |
|
|
} else { |
1304 |
|
|
$file = "$f->{share}/$f->{file}"; |
1305 |
|
|
} |
1306 |
|
|
next if ( !-f "$Dir/$Backups[$j]{num}/$file" ); |
1307 |
|
|
if ( !link("$Dir/$Backups[$j]{num}/$file", |
1308 |
|
|
"$Dir/$num/$shareM/$fileM") ) { |
1309 |
|
|
my $str = \"Unable to link $num/$f->{share}/$f->{file} to" |
1310 |
|
|
. " $Backups[$j]{num}/$f->{share}/$f->{file}\n"; |
1311 |
|
|
$XferLOG->write(\$str); |
1312 |
|
|
} else { |
1313 |
|
|
my $str = "Bad file $num/$f->{share}/$f->{file} replaced" |
1314 |
|
|
. " by link to" |
1315 |
|
|
. " $Backups[$j]{num}/$f->{share}/$f->{file}\n"; |
1316 |
|
|
$XferLOG->write(\$str); |
1317 |
|
|
} |
1318 |
|
|
last; |
1319 |
|
|
} |
1320 |
|
|
if ( $j < 0 ) { |
1321 |
|
|
my $str = "Removed bad file $num/$f->{share}/$f->{file}" |
1322 |
|
|
. " (no older copy to link to)\n"; |
1323 |
|
|
$XferLOG->write(\$str); |
1324 |
|
|
} |
1325 |
|
|
} |
1326 |
|
|
$XferLOG->close(); |
1327 |
|
|
rename("$Dir/XferLOG$fileExt", "$Dir/XferLOG.$num$fileExt"); |
1328 |
|
|
rename("$Dir/NewFileList", "$Dir/NewFileList.$num"); |
1329 |
|
|
|
1330 |
|
|
return $num; |
1331 |
|
|
} |
1332 |
|
|
|
1333 |
|
|
# |
1334 |
|
|
# Removes a specific backup |
1335 |
|
|
# |
1336 |
|
|
sub BackupRemove |
1337 |
|
|
{ |
1338 |
|
|
my($client, $Backups, $idx) = @_; |
1339 |
|
|
my($Dir) = "$TopDir/pc/$client"; |
1340 |
|
|
|
1341 |
|
|
$bpc->RmTreeDefer("$TopDir/trash", |
1342 |
|
|
"$Dir/$Backups->[$idx]{num}"); |
1343 |
|
|
unlink("$Dir/SmbLOG.$Backups->[$idx]{num}") |
1344 |
|
|
if ( -f "$Dir/SmbLOG.$Backups->[$idx]{num}" ); |
1345 |
|
|
unlink("$Dir/SmbLOG.$Backups->[$idx]{num}.z") |
1346 |
|
|
if ( -f "$Dir/SmbLOG.$Backups->[$idx]{num}.z" ); |
1347 |
|
|
unlink("$Dir/XferLOG.$Backups->[$idx]{num}") |
1348 |
|
|
if ( -f "$Dir/XferLOG.$Backups->[$idx]{num}" ); |
1349 |
|
|
unlink("$Dir/XferLOG.$Backups->[$idx]{num}.z") |
1350 |
|
|
if ( -f "$Dir/XferLOG.$Backups->[$idx]{num}.z" ); |
1351 |
|
|
splice(@{$Backups}, $idx, 1); |
1352 |
|
|
} |
1353 |
|
|
|
1354 |
|
|
sub CorrectHostCheck |
1355 |
|
|
{ |
1356 |
|
|
my($hostIP, $host) = @_; |
1357 |
|
|
return if ( $hostIP eq $host && !$Conf{FixedIPNetBiosNameCheck} |
1358 |
|
|
|| $Conf{NmbLookupCmd} eq "" ); |
1359 |
|
|
my($netBiosHost, $netBiosUser) = $bpc->NetBiosInfoGet($hostIP); |
1360 |
|
|
return "host $host has mismatching netbios name $netBiosHost" |
1361 |
|
|
if ( $netBiosHost ne $host ); |
1362 |
|
|
return; |
1363 |
|
|
} |
1364 |
|
|
|
1365 |
|
|
# |
1366 |
|
|
# The Xfer method might tell us from time to time about processes |
1367 |
|
|
# it forks. We tell BackupPC about this (for status displays) and |
1368 |
|
|
# keep track of the pids in case we cancel the backup |
1369 |
|
|
# |
1370 |
|
|
sub pidHandler |
1371 |
|
|
{ |
1372 |
|
|
@xferPid = @_; |
1373 |
|
|
@xferPid = grep(/./, @xferPid); |
1374 |
|
|
return if ( !@xferPid && $tarPid < 0 ); |
1375 |
|
|
my @pids = @xferPid; |
1376 |
|
|
push(@pids, $tarPid) if ( $tarPid > 0 ); |
1377 |
|
|
my $str = join(",", @pids); |
1378 |
|
|
$XferLOG->write(\"Xfer PIDs are now $str\n") if ( defined($XferLOG) ); |
1379 |
|
|
print("xferPids $str\n"); |
1380 |
|
|
} |
1381 |
|
|
|
1382 |
|
|
# |
1383 |
|
|
# Run an optional pre- or post-dump command |
1384 |
|
|
# |
1385 |
|
|
sub UserCommandRun |
1386 |
|
|
{ |
1387 |
|
|
my($cmdType) = @_; |
1388 |
|
|
|
1389 |
|
|
return if ( !defined($Conf{$cmdType}) ); |
1390 |
|
|
my $vars = { |
1391 |
|
|
xfer => $xfer, |
1392 |
|
|
client => $client, |
1393 |
|
|
host => $host, |
1394 |
|
|
hostIP => $hostIP, |
1395 |
|
|
user => $Hosts->{$client}{user}, |
1396 |
|
|
moreUsers => $Hosts->{$client}{moreUsers}, |
1397 |
|
|
share => $ShareNames->[0], |
1398 |
|
|
shares => $ShareNames, |
1399 |
|
|
XferMethod => $Conf{XferMethod}, |
1400 |
|
|
sshPath => $Conf{SshPath}, |
1401 |
|
|
LOG => *LOG, |
1402 |
|
|
XferLOG => $XferLOG, |
1403 |
|
|
stat => \%stat, |
1404 |
|
|
xferOK => $stat{xferOK} || 0, |
1405 |
|
|
hostError => $stat{hostError}, |
1406 |
|
|
type => $type, |
1407 |
|
|
cmdType => $cmdType, |
1408 |
|
|
}; |
1409 |
|
|
my $cmd = $bpc->cmdVarSubstitute($Conf{$cmdType}, $vars); |
1410 |
|
|
$XferLOG->write(\"Executing $cmdType: @$cmd\n"); |
1411 |
|
|
# |
1412 |
|
|
# Run the user's command, dumping the stdout/stderr into the |
1413 |
|
|
# Xfer log file. Also supply the optional $vars and %Conf in |
1414 |
|
|
# case the command is really perl code instead of a shell |
1415 |
|
|
# command. |
1416 |
|
|
# |
1417 |
|
|
$bpc->cmdSystemOrEval($cmd, |
1418 |
|
|
sub { |
1419 |
|
|
$XferLOG->write(\$_[0]); |
1420 |
|
|
}, |
1421 |
|
|
$vars, \%Conf); |
1422 |
|
|
} |