2 # Copyright (c) 2010 Zmanda Inc. All Rights Reserved.
4 # This program is free software; you can redistribute it and/or modify it
5 # under the terms of the GNU General Public License version 2 as published
6 # by the Free Software Foundation.
8 # This program is distributed in the hope that it will be useful, but
9 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 # You should have received a copy of the GNU General Public License along
14 # with this program; if not, write to the Free Software Foundation, Inc.,
15 # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 # Contact information: Zmanda Inc., 465 S. Mathilda Ave., Suite 300
18 # Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
20 use lib '@amperldir@';
25 use POSIX qw(WIFEXITED WEXITSTATUS strftime);
27 use Amanda::Config qw( :init :getconf );
28 use Amanda::Util qw( :constants );
29 use Amanda::Logfile qw( :logtype_t log_add );
30 use Amanda::Debug qw( debug );
39 Usage: amdump <conf> [--no-taper] [-o configoption]* [host/disk]*
41 print STDERR "$msg\n" if $msg;
45 Amanda::Util::setup_application("amdump", "server", $CONTEXT_DAEMON);
47 my $config_overrides = new_config_overrides($#ARGV+1);
48 my @config_overrides_opts;
51 Getopt::Long::Configure(qw(bundling));
53 'help|usage|?' => \&usage,
54 'no-taper' => \$opt_no_taper,
56 push @config_overrides_opts, "-o" . $_[1];
57 add_config_override_opt($config_overrides, $_[1]);
61 usage("No config specified") if (@ARGV < 1);
63 my $config_name = shift @ARGV;
64 set_config_overrides($config_overrides);
65 config_init($CONFIG_INIT_EXPLICIT_NAME, $config_name);
66 my ($cfgerr_level, @cfgerr_errors) = config_errors();
67 if ($cfgerr_level >= $CFGERR_WARNINGS) {
68 config_print_errors();
69 if ($cfgerr_level >= $CFGERR_ERRORS) {
70 die("errors processing config file");
74 Amanda::Util::finish_setup($RUNNING_AS_DUMPUSER);
76 # useful info for below
78 my $logdir = getconf($CNF_LOGDIR);
80 my $longdate = strftime "%a %b %e %H:%M:%S %Z %Y", @now;
81 my $timestamp = strftime "%Y%m%d%H%M%S", @now;
82 my $datestamp = strftime "%Y%m%d", @now;
83 my $starttime_locale_independent = strftime "%Y-%m-%d %H:%M:%S %Z", @now;
84 my $trace_log_filename = "$logdir/log";
85 my $amdump_log_filename = "$logdir/amdump";
87 my $amdump_log = \*STDERR;
93 print $amdump_log "amdump: ", @_, "\n";
100 log_add($L_ERROR, "Can't execute $prog");
104 my ($proc, @args) = @_;
107 my $pid = POSIX::fork();
109 my $null = POSIX::open("/dev/null", POSIX::O_RDWR);
110 POSIX::dup2($null, 0);
111 POSIX::dup2($null, 1);
112 POSIX::dup2(fileno($amdump_log), 2);
115 die "Could not exec $proc: $!";
119 debug("$proc exited with code $s");
121 if ($exit_code == 0) {
122 debug("ignoring failing exit code $s from $proc");
124 debug("recording failing exit code $s from $proc for amdump exit");
131 my $holdfile = "$CONFIG_DIR/$config_name/hold";
133 debug("waiting for hold file '$holdfile' to be removed");
134 while (-f $holdfile) {
140 sub bail_already_running {
141 my $msg = "An Amanda process is already running - please run amcleanup manually";
145 # put together a fake logfile and send an amreport
146 my $fakelogfile = "$AMANDA_TMPDIR/fakelog.$$";
147 open(my $fakelog, ">", $fakelogfile)
148 or die("cannot open a fake log to send an report - situation is dire");
149 print $fakelog <<EOF;
150 INFO amdump amdump pid $$
151 START driver date $timestamp
154 run_subprocess("$sbindir/amreport", $config_name, '--from-amdump', '-l', $fakelogfile, @config_overrides_opts);
155 unlink($fakelogfile);
157 # and we're done here
162 return unless -f $amdump_log_filename || -f $trace_log_filename;
164 # logfiles are still around. First, try an amcleanup -p to see if
165 # the actual processes are already dead
166 debug("runing amcleanup -p");
167 run_subprocess("$sbindir/amcleanup", '-p', $config_name, @config_overrides_opts);
170 return unless -f $amdump_log_filename || -f $trace_log_filename;
172 bail_already_running();
176 debug("beginning trace log");
177 # start the trace log by simply writing an INFO line to it
178 log_add($L_INFO, "amdump pid $$");
180 # but not so fast! What if another process has also appended such a line?
181 open(my $tl, "<", $trace_log_filename)
182 or die("could not open trace log file '$trace_log_filename': $!");
183 if (<$tl> !~ /^INFO amdump amdump pid $$/) {
184 # we didn't get there first, so bail out
185 debug("another amdump raced with this one, and won");
186 bail_already_running();
190 # redirect the amdump_log to the proper filename instead of stderr
191 # note that perl will overwrite STDERR if we don't set $amdump_log to
192 # undef first.. stupid perl.
193 debug("beginning amdump log");
195 # Must be opened in append so that all subprocess can write to it.
196 open($amdump_log, ">>", $amdump_log_filename)
197 or die("could not open amdump log file '$amdump_log_filename': $!");
200 sub planner_driver_pipeline {
201 my $planner = "$amlibexecdir/planner";
202 my $driver = "$amlibexecdir/driver";
203 my @no_taper = $opt_no_taper? ('--no-taper'):();
205 check_exec($planner);
208 # Perl's open3 is an embarassment to the language. We'll do this manually.
209 debug("invoking planner | driver");
210 my ($rpipe, $wpipe) = POSIX::pipe();
212 my $pl_pid = POSIX::fork();
215 my $null = POSIX::open("/dev/null", POSIX::O_RDWR);
216 POSIX::dup2($null, 0);
218 POSIX::dup2($wpipe, 1);
219 POSIX::close($rpipe);
220 POSIX::close($wpipe);
221 POSIX::dup2(fileno($amdump_log), 2);
224 # note that @no_taper must follow --starttime
225 $config_name, '--starttime', $timestamp, @no_taper, @config_overrides_opts, @hostdisk;
226 die "Could not exec $planner: $!";
228 debug(" planner: $pl_pid");
230 my $dr_pid = POSIX::fork();
233 my $null = POSIX::open("/dev/null", POSIX::O_RDWR);
234 POSIX::dup2($rpipe, 0);
235 POSIX::close($rpipe);
236 POSIX::close($wpipe);
237 POSIX::dup2(fileno($amdump_log), 1); # driver does lots of logging to stdout..
239 POSIX::dup2(fileno($amdump_log), 2);
242 $config_name, @no_taper, @config_overrides_opts;
243 die "Could not exec $driver: $!";
245 debug(" driver: $dr_pid");
247 POSIX::close($rpipe);
248 POSIX::close($wpipe);
250 my $first_bad_exit = 0;
251 for (my $i = 0; $i < 2; $i++) {
253 die("Error waiting: $!") if ($dead <= 0);
255 debug("planner finished with exit code $s") if $dead == $pl_pid;
256 debug("driver finished with exit code $s") if $dead == $dr_pid;
257 my $exit = WIFEXITED($?)? WEXITSTATUS($?) : 1;
258 $first_bad_exit = $exit if ($exit && !$first_bad_exit)
260 $exit_code |= $first_bad_exit;
264 debug("running amreport");
265 run_subprocess("$sbindir/amreport", $config_name, '--from-amdump', @config_overrides_opts);
268 sub roll_trace_logs {
269 my $t = getconf($CNF_USETIMESTAMPS)? $timestamp : $datestamp;
270 debug("renaming trace log");
271 Amanda::Logfile::log_rename($t)
274 sub trim_trace_logs {
275 debug("trimming old trace logs");
276 run_subprocess("$amlibexecdir/amtrmlog", $config_name, @config_overrides_opts);
280 debug("trimming old indexes");
281 run_subprocess("$amlibexecdir/amtrmidx", $config_name, @config_overrides_opts);
284 sub roll_amdump_logs {
285 debug("renaming amdump log and trimming old amdump logs (beyond tapecycle+2)");
287 # rename all the way along the tapecycle
288 my $days = getconf($CNF_TAPECYCLE) + 2;
289 for (my $i = $days-1; $i >= 1; $i--) {
290 next unless -f "$amdump_log_filename.$i";
291 rename("$amdump_log_filename.$i", "$amdump_log_filename.".($i+1));
294 # now swap the current logfile in
295 rename("$amdump_log_filename", "$amdump_log_filename.1");
298 # now do the meat of the amdump work; these operations are ported directly
299 # from the old amdump.sh script
301 # wait for $confdir/hold to disappear
304 # look for a current logfile, and if found run amcleanup -p, and if that fails
308 # start up the log file
311 # amstatus needs a lot of forms of the time, I guess
312 amdump_log("start at $longdate");
313 amdump_log("datestamp $datestamp");
314 amdump_log("starttime $timestamp");
315 amdump_log("starttime-locale-independent $starttime_locale_independent");
317 # run the planner and driver, the one piped to the other
318 planner_driver_pipeline();
320 my $end_longdate = strftime "%a %b %e %H:%M:%S %Z %Y", localtime;
321 amdump_log("end at $end_longdate");
323 # send the dump report
326 # do some house-keeping
332 debug("exiting with code $exit_code");