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