Imported Upstream version 3.3.3
[debian/amanda] / server-src / amdump.pl
1 #! @PERL@
2 # Copyright (c) 2010-2012 Zmanda Inc.  All Rights Reserved.
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11 # or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 # for more details.
13 #
14 # You should have received a copy of the GNU General Public License along
15 # with this program; if not, write to the Free Software Foundation, Inc.,
16 # 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
17 #
18 # Contact information: Zmanda Inc., 465 S. Mathilda Ave., Suite 300
19 # Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
20
21 use lib '@amperldir@';
22 use strict;
23 use warnings;
24
25 use Getopt::Long;
26 use POSIX qw(WIFEXITED WEXITSTATUS strftime);
27 use File::Glob qw( :glob );
28
29 use Amanda::Config qw( :init :getconf );
30 use Amanda::Util qw( :constants );
31 use Amanda::Logfile qw( :logtype_t log_add );
32 use Amanda::Debug qw( debug );
33 use Amanda::Paths;
34
35 ##
36 # Main
37
38 sub usage {
39     my ($msg) = @_;
40     print STDERR <<EOF;
41 Usage: amdump <conf> [--no-taper] [--from-client] [--exact-match] [-o configoption]* [host/disk]*
42 EOF
43     print STDERR "$msg\n" if $msg;
44     exit 1;
45 }
46
47 Amanda::Util::setup_application("amdump", "server", $CONTEXT_DAEMON);
48
49 my $config_overrides = new_config_overrides($#ARGV+1);
50 my @config_overrides_opts;
51
52 my $opt_no_taper = 0;
53 my $opt_from_client = 0;
54 my $opt_exact_match = 0;
55
56 debug("Arguments: " . join(' ', @ARGV));
57 Getopt::Long::Configure(qw(bundling));
58 GetOptions(
59     'version' => \&Amanda::Util::version_opt,
60     'help|usage|?' => \&usage,
61     'no-taper' => \$opt_no_taper,
62     'from-client' => \$opt_from_client,
63     'exact-match' => \$opt_exact_match,
64     'o=s' => sub {
65         push @config_overrides_opts, "-o" . $_[1];
66         add_config_override_opt($config_overrides, $_[1]);
67     },
68 ) or usage();
69
70 usage("No config specified") if (@ARGV < 1);
71
72 my $config_name = shift @ARGV;
73 set_config_overrides($config_overrides);
74 config_init($CONFIG_INIT_EXPLICIT_NAME, $config_name);
75 my ($cfgerr_level, @cfgerr_errors) = config_errors();
76 if ($cfgerr_level >= $CFGERR_WARNINGS) {
77     config_print_errors();
78     if ($cfgerr_level >= $CFGERR_ERRORS) {
79         die("errors processing config file");
80     }
81 }
82
83 Amanda::Util::finish_setup($RUNNING_AS_DUMPUSER);
84
85 # useful info for below
86 my @hostdisk = @ARGV;
87 my $logdir = getconf($CNF_LOGDIR);
88 my @now = localtime;
89 my $longdate = strftime "%a %b %e %H:%M:%S %Z %Y", @now;
90 my $timestamp = strftime "%Y%m%d%H%M%S", @now;
91 my $datestamp = strftime "%Y%m%d", @now;
92 my $starttime_locale_independent = strftime "%Y-%m-%d %H:%M:%S %Z", @now;
93 my $trace_log_filename = "$logdir/log";
94 my $amdump_log_filename_default = "$logdir/amdump";
95 my $amdump_log_filename = "$logdir/amdump.$timestamp";
96 my $exit_code = 0;
97 my $amdump_log = \*STDERR;
98
99 ##
100 # subs for below
101
102 sub amdump_log {
103     print $amdump_log "amdump: ", @_, "\n";
104 }
105
106 sub check_exec {
107     my ($prog) = @_;
108     return if -x $prog;
109
110     log_add($L_ERROR, "Can't execute $prog");
111 }
112
113 sub run_subprocess {
114     my ($proc, @args) = @_;
115     check_exec($proc);
116
117     my $pid = POSIX::fork();
118     if ($pid == 0) {
119         my $null = POSIX::open("/dev/null", POSIX::O_RDWR);
120         POSIX::dup2($null, 0);
121         POSIX::dup2($null, 1);
122         POSIX::dup2(fileno($amdump_log), 2);
123         close($amdump_log);
124         exec $proc, @args;
125         die "Could not exec $proc: $!";
126     }
127     waitpid($pid, 0);
128     my $s = $? >> 8;
129     debug("$proc exited with code $s");
130     if ($?) {
131         if ($exit_code == 0) {
132             debug("ignoring failing exit code $s from $proc");
133         } else {
134             debug("recording failing exit code $s from $proc for amdump exit");
135             $exit_code = $s;
136         }
137     }
138 }
139
140 sub wait_for_hold {
141     my $holdfile = "$CONFIG_DIR/$config_name/hold";
142     if (-f $holdfile) {
143         debug("waiting for hold file '$holdfile' to be removed");
144         while (-f $holdfile) {
145             sleep(60);
146         }
147     }
148 }
149
150 sub bail_already_running {
151     my $msg = "An Amanda process is already running - please run amcleanup manually";
152     debug($msg);
153     amdump_log($msg);
154
155     # put together a fake logfile and send an amreport
156     my $fakelogfile = "$AMANDA_TMPDIR/fakelog.$$";
157     open(my $fakelog, ">", $fakelogfile)
158         or die("cannot open a fake log to send an report - situation is dire");
159     print $fakelog <<EOF;
160 INFO amdump amdump pid $$
161 START planner date $timestamp
162 START driver date $timestamp
163 ERROR amdump $msg
164 EOF
165     run_subprocess("$sbindir/amreport", $config_name, '--from-amdump', '-l', $fakelogfile, @config_overrides_opts);
166     unlink($fakelogfile);
167
168     # and we're done here
169     exit 1;
170 }
171
172 sub do_amcleanup {
173     return unless -f $amdump_log_filename_default || -f $trace_log_filename;
174
175     # logfiles are still around.  First, try an amcleanup -p to see if
176     # the actual processes are already dead
177     debug("runing amcleanup -p");
178     run_subprocess("$sbindir/amcleanup", '-p', $config_name, @config_overrides_opts);
179
180     # and check again
181     return unless -f $amdump_log_filename_default || -f $trace_log_filename;
182
183     bail_already_running();
184 }
185
186 sub start_logfiles {
187     debug("beginning trace log");
188     # start the trace log by simply writing an INFO line to it
189     log_add($L_INFO, "amdump pid $$");
190
191     # but not so fast!  What if another process has also appended such a line?
192     open(my $tl, "<", $trace_log_filename)
193         or die("could not open trace log file '$trace_log_filename': $!");
194     if (<$tl> !~ /^INFO amdump amdump pid $$/) {
195         # we didn't get there first, so bail out
196         debug("another amdump raced with this one, and won");
197         bail_already_running();
198     }
199     close($tl);
200
201     # redirect the amdump_log to the proper filename instead of stderr
202     # note that perl will overwrite STDERR if we don't set $amdump_log to
203     # undef first.. stupid perl.
204     debug("beginning amdump log");
205     $amdump_log = undef;
206     # Must be opened in append so that all subprocess can write to it.
207     open($amdump_log, ">>", $amdump_log_filename)
208         or die("could not open amdump log file '$amdump_log_filename': $!");
209     unlink $amdump_log_filename_default;
210     symlink $amdump_log_filename, $amdump_log_filename_default;
211 }
212
213 sub planner_driver_pipeline {
214     my $planner = "$amlibexecdir/planner";
215     my $driver = "$amlibexecdir/driver";
216     my @no_taper = $opt_no_taper? ('--no-taper'):();
217     my @from_client = $opt_from_client? ('--from-client'):();
218     my @exact_match = $opt_exact_match? ('--exact-match'):();
219
220     check_exec($planner);
221     check_exec($driver);
222
223     # Perl's open3 is an embarassment to the language.  We'll do this manually.
224     debug("invoking planner | driver");
225     my ($rpipe, $wpipe) = POSIX::pipe();
226
227     my $pl_pid = POSIX::fork();
228     if ($pl_pid == 0) {
229         ## child
230         my $null = POSIX::open("/dev/null", POSIX::O_RDWR);
231         POSIX::dup2($null, 0);
232         POSIX::close($null);
233         POSIX::dup2($wpipe, 1);
234         POSIX::close($rpipe);
235         POSIX::close($wpipe);
236         POSIX::dup2(fileno($amdump_log), 2);
237         close($amdump_log);
238         exec $planner,
239             # note that @no_taper must follow --starttime
240             $config_name, '--starttime', $timestamp, @no_taper, @from_client, @exact_match, @config_overrides_opts, @hostdisk;
241         die "Could not exec $planner: $!";
242     }
243     debug(" planner: $pl_pid");
244
245     my $dr_pid = POSIX::fork();
246     if ($dr_pid == 0) {
247         ## child
248         my $null = POSIX::open("/dev/null", POSIX::O_RDWR);
249         POSIX::dup2($rpipe, 0);
250         POSIX::close($rpipe);
251         POSIX::close($wpipe);
252         POSIX::dup2(fileno($amdump_log), 1); # driver does lots of logging to stdout..
253         POSIX::close($null);
254         POSIX::dup2(fileno($amdump_log), 2);
255         close($amdump_log);
256         exec $driver,
257             $config_name, @no_taper, @from_client, @config_overrides_opts;
258         die "Could not exec $driver: $!";
259     }
260     debug(" driver: $dr_pid");
261
262     POSIX::close($rpipe);
263     POSIX::close($wpipe);
264
265     my $first_bad_exit = 0;
266     for (my $i = 0; $i < 2; $i++) {
267         my $dead = wait();
268         die("Error waiting: $!") if ($dead <= 0);
269         my $s = $? >> 8;
270         debug("planner finished with exit code $s") if $dead == $pl_pid;
271         debug("driver finished with exit code $s") if $dead == $dr_pid;
272         my $exit = WIFEXITED($?)? WEXITSTATUS($?) : 1;
273         $first_bad_exit = $exit if ($exit && !$first_bad_exit)
274     }
275     $exit_code |= $first_bad_exit;
276 }
277
278 sub do_amreport {
279     debug("running amreport");
280     run_subprocess("$sbindir/amreport", $config_name, '--from-amdump', @config_overrides_opts);
281 }
282
283 sub roll_trace_logs {
284     my $t = getconf($CNF_USETIMESTAMPS)? $timestamp : $datestamp;
285     debug("renaming trace log");
286     Amanda::Logfile::log_rename($t)
287 }
288
289 sub trim_trace_logs {
290     debug("trimming old trace logs");
291     run_subprocess("$amlibexecdir/amtrmlog", $config_name, @config_overrides_opts);
292 }
293
294 sub trim_indexes {
295     debug("trimming old indexes");
296     run_subprocess("$amlibexecdir/amtrmidx", $config_name, @config_overrides_opts);
297 }
298
299 sub roll_amdump_logs {
300     debug("renaming amdump log and trimming old amdump logs (beyond tapecycle+2)");
301
302     unlink "$amdump_log_filename_default.1";
303     rename $amdump_log_filename_default, "$amdump_log_filename_default.1";
304
305     # keep the latest tapecycle files.
306     my @files = sort {-M $b <=> -M $a} grep { !/^\./ && -f "$_"} <$logdir/amdump.*>;
307     my $days = getconf($CNF_TAPECYCLE) + 2;
308     for (my $i = $days-1; $i >= 1; $i--) {
309         my $a = pop @files;
310     }
311     foreach my $name (@files) {
312         unlink $name;
313         amdump_log("unlink $name");
314     }
315 }
316
317 # now do the meat of the amdump work; these operations are ported directly
318 # from the old amdump.sh script
319
320 # wait for $confdir/hold to disappear
321 wait_for_hold();
322
323 # look for a current logfile, and if found run amcleanup -p, and if that fails
324 # bail out
325 do_amcleanup();
326
327 my $crtl_c = 0;
328 $SIG{INT} = \&interrupt;
329
330 sub interrupt {
331     $crtl_c = 1;
332 }
333
334 # start up the log file
335 start_logfiles();
336
337 # amstatus needs a lot of forms of the time, I guess
338 amdump_log("start at $longdate");
339 amdump_log("datestamp $datestamp");
340 amdump_log("starttime $timestamp");
341 amdump_log("starttime-locale-independent $starttime_locale_independent");
342
343 # run the planner and driver, the one piped to the other
344 planner_driver_pipeline();
345
346 if ($crtl_c == 1) {
347     print "Caught a ctrl-c\n";
348     log_add($L_FATAL, "amdump killed by ctrl-c");
349     debug("Caught a ctrl-c");
350     $exit_code = 1;
351 }
352 $SIG{INT} = 'DEFAULT';
353
354 my $end_longdate = strftime "%a %b %e %H:%M:%S %Z %Y", localtime;
355 amdump_log("end at $end_longdate");
356
357 # send the dump report
358 do_amreport();
359
360 # do some house-keeping
361 roll_trace_logs();
362 trim_trace_logs();
363 trim_indexes();
364 roll_amdump_logs();
365
366 debug("exiting with code $exit_code");
367 exit($exit_code);