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 Mathlida Ave, Suite 300
18 # Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
20 use lib '@amperldir@';
26 use Cwd qw( abs_path );
30 use Amanda::Config qw( :init :getconf config_dir_relative );
31 use Amanda::Util qw( :constants );
34 use Amanda::Constants;
35 use Amanda::Debug qw( debug warning );
37 use Amanda::Report::human;
38 use Amanda::Logfile qw( find_latest_log);
40 # constants for dealing with outputs
41 use constant FORMAT => 0;
42 use constant FMT_TYP => 0;
43 use constant FMT_TEMPLATE => 1;
45 use constant OUTPUT => 1;
46 use constant OUT_TYP => 0;
47 use constant OUT_DST => 1;
49 # what mode is this running in? MODE_SCRIPT is when run from scripts like
50 # amdump, while MODE_CMDLINE is when run from the command line
51 use constant MODE_NONE => 0;
52 use constant MODE_SCRIPT => 1;
53 use constant MODE_CMDLINE => 2;
58 my ($opt_mailto, $opt_filename, $opt_logfname, $opt_psfname, $opt_xml);
59 my ($config_name, $report, $outfh);
62 # list of [ report-spec, output-spec ]
63 my (@outputs, @output_queue);
65 ## Program subroutines
70 Usage: amreport conf [--version] [--help] [-o configoption]
71 command-line mode options:
72 [--log=logfile] [--ps=filename] [--text=filename] [--xml=filename]
73 [--print=printer] [--mail-text=recipient]
75 [-i] [-M address] [-f output-file] [-l logfile] [-p postscript-file]
78 Amreport uses short options for use from shell scripts (e.g., amreport), or
79 long options for use on the command line.
81 If the printer is omitted, the printer from the configuration is used. If the
82 filename is omitted or is "-", output is to stdout. If the recipient is
83 omitted, then the default mailto from the configuration is used.
85 If no options are given, a text report is printed to stdout. The --from-amdump
86 option triggers script mode, and is used by amdump.
93 my ( $error_msg, $exit_code ) = @_;
94 warning("error: $error_msg");
95 print STDERR "$error_msg\n";
103 if ($mode != MODE_NONE && $mode != $new_mode) {
104 error("cannot mix long options (command-line mode), and "
105 . "short options (script mode) with each other", 1);
111 # Takes a string specifying an option name (e.g. "M") and a reference to a
112 # scalar variable. It's return values are suitable for use in the middle of
113 # option specification, e.g. GetOptions("foo" => \$foo, opt_set_var("bar", \$bar)
114 # It will only let the option be specified (at most) once, though, and will
115 # print an error message and exit otherwise.
118 my ($opt, $ref) = @_;
119 error("must pass scalar ref to opt_set_var", 1)
120 unless (ref($ref) eq "SCALAR");
127 # all short options are legacy options
128 set_mode(MODE_SCRIPT);
130 if (defined($$ref)) {
131 error("you may specify at most one -$op\n", 1);
144 unless ((ref $output eq "ARRAY")
145 && (ref $output->[0] eq "ARRAY")
146 && (ref $output->[1] eq "ARRAY")) {
147 die "error: bad argument to opt_push_queue()";
150 # all queue-pushing options are command-line options
151 set_mode(MODE_CMDLINE);
153 push @output_queue, $output;
156 sub get_default_logfile
158 my $logdir = config_dir_relative(getconf($CNF_LOGDIR));
159 my $logfile = "$logdir/log";
164 } elsif ($mode == MODE_CMDLINE) {
166 $logfile = "$logdir/" . find_latest_log($logdir);
167 return $logfile if -f $logfile;
170 # otherwise, bail out
171 error("nothing to report on!", 1);
174 sub apply_output_defaults
176 my $ttyp = getconf($CNF_TAPETYPE);
177 my $tt = lookup_tapetype($ttyp) if $ttyp;
178 my $cfg_template = "" . tapetype_getconf($tt, $TAPETYPE_LBL_TEMPL) if $tt;
180 my $cfg_printer = getconf($CNF_PRINTER);
181 my $cfg_mailto = getconf_seen($CNF_MAILTO) ? getconf($CNF_MAILTO) : undef;
183 foreach my $job (@output_queue) {
185 # supply the configured template if none was given.
186 if ( $job->[FORMAT]->[FMT_TYP] eq 'postscript'
187 && !$job->[FORMAT]->[FMT_TEMPLATE]) {
188 $job->[FORMAT]->[FMT_TEMPLATE] = $cfg_template;
191 # apply default destinations for each destination type
192 if (!$job->[OUTPUT][OUT_DST]) {
193 $job->[OUTPUT][OUT_DST] =
194 ($job->[OUTPUT]->[OUT_TYP] eq 'printer') ? $cfg_printer
195 : ($job->[OUTPUT]->[OUT_TYP] eq 'mail') ? $cfg_mailto
196 : ($job->[OUTPUT]->[OUT_TYP] eq 'file') ? '-'
197 : undef; # will result in error
205 sub calculate_legacy_outputs {
206 # Part of the "options" is the configuration. Do we have a template? And a
207 # mailto? And mailer?
209 my $ttyp = getconf($CNF_TAPETYPE);
210 my $tt = lookup_tapetype($ttyp) if $ttyp;
211 my $cfg_template = "" . tapetype_getconf($tt, $TAPETYPE_LBL_TEMPL) if $tt;
213 my $cfg_mailer = getconf($CNF_MAILER);
214 my $cfg_printer = getconf($CNF_PRINTER);
215 my $cfg_mailto = getconf_seen($CNF_MAILTO) ? getconf($CNF_MAILTO) : undef;
217 if (!defined $opt_mailto) {
218 # ignore the default value for mailto
219 $opt_mailto = getconf_seen($CNF_MAILTO)? getconf($CNF_MAILTO) : undef;
220 # (note that we still may not send mail if CNF_MAILER is not set)
222 # check that mailer is defined if we got an explicit -M, but go on
223 # processing (we will probably do nothing..)
225 warning("a mailer is not defined; will not send mail");
226 print "Warning: a mailer is not defined";
230 # should we send a mail?
231 if ($cfg_mailer and $opt_mailto) {
232 # -i and -f override this
233 if (!$opt_nomail and !$opt_filename) {
234 push @outputs, [ [ 'human' ], [ 'mail', $opt_mailto ] ];
238 # human/xml output to a file?
241 push @outputs, [ [ 'xml' ], [ 'file', $opt_filename ] ];
243 push @outputs, [ [ 'human' ], [ 'file', $opt_filename ] ];
247 # postscript output to a printer?
248 # (this is just silly)
249 if ($Amanda::Constants::LPR and $cfg_template) {
250 # oddly, -i ($opt_nomail) will disable printing, but -i -f prints.
251 if ((!$opt_nomail and !$opt_psfname) or ($opt_nomail and $opt_filename)) {
252 # but we don't print if the text report isn't going anywhere
253 unless ((!$cfg_mailer or !$opt_mailto) and !($opt_filename and !$opt_xml)) {
254 push @outputs, [ [ 'postscript', $cfg_template ], [ 'printer', $cfg_printer ] ]
259 # postscript output to a file?
260 if ($opt_psfname and $cfg_template) {
261 push @outputs, [ [ 'postscript', $cfg_template ], [ 'file', $opt_psfname ] ];
265 sub legacy_send_amreport
268 my $cfg_send = getconf($CNF_SEND_AMREPORT_ON);
270 ## only check $cfg_send if we are in script mode and sending mail
271 return 1 if ($mode != MODE_SCRIPT);
272 return 1 if !($output->[OUTPUT]->[OUT_TYP] eq "mail");
274 ## do not bother checking for errors or stranges if set to 'all' or 'never'
275 return 1 if ($cfg_send == $SEND_AMREPORT_ALL);
276 return 0 if ($cfg_send == $SEND_AMREPORT_NEVER);
278 my $output_name = join(" ", @{ $output->[FORMAT] }, @{ $output->[OUTPUT] });
279 my $send_amreport = 0;
281 debug("testingamreport_send_on=$cfg_send, output:$output_name");
283 if ($cfg_send == $SEND_AMREPORT_STRANGE) {
285 if ( !$report->get_flag("got_finish")
286 || ($report->get_flag("dump_failed") != 0)
287 || ($report->get_flag("results_missing") != 0)
288 || ($report->get_flag("dump_strange") != 0)) {
290 debug("send-amreport-on=$cfg_send, condition filled for $output_name");
295 debug("send-amreport-on=$cfg_send, condition not filled for $output_name");
299 } elsif ($cfg_send = $SEND_AMREPORT_ERROR) {
301 if ( !$report->get_flag("got_finish")
302 || ($report->get_flag("exit_status") != 0)
303 || ($report->get_flag("dump_failed") != 0)
304 || ($report->get_flag("results_missing") != 0)
305 || ($report->get_flag("dump_strange") != 0)) {
307 debug("send-amreport-on=$cfg_send, condition filled for $output_name");
312 debug("send-amreport-on=$cfg_send, condition not filled for $output_name");
317 return $send_amreport;
320 sub open_file_output {
321 my ($report, $outputspec) = @_;
323 my $filename = $outputspec->[1];
324 $filename = Amanda::Util::get_original_cwd() . "/$filename"
325 unless ($filename eq "-" || $filename =~ m{^/});
327 if ($filename eq "-") {
330 open my $fh, ">", $filename or die "Cannot open '$filename': $!";
335 sub open_printer_output
337 my ($report, $outputspec) = @_;
338 my $printer = $outputspec->[1];
341 if ($printer and $Amanda::Constants::LPRFLAG) {
342 @cmd = ( $Amanda::Constants::LPR, $Amanda::Constants::LPRFLAG, $printer );
344 @cmd = ( $Amanda::Constants::LPR );
347 debug("invoking printer: " . join(" ", @cmd));
349 # redirect stdout/stderr to stderr, which is usually the amdump log
350 my $pid = open3( my $fh, ">&2", ">&2", @cmd)
351 or error("cannot start $cmd[0]: $!", 1);
357 my ($report, $outputspec) = @_;
358 my $mailto = $outputspec->[1];
360 if ($mailto =~ /[*<>()\[\];:\\\/"!$|]/) {
361 error("mail addresses have invalid characters", 1);
365 $report->get_program_info(
366 $report->get_flag("amflush_run") ? "amflush" : "planner", "start" );
368 $datestamp /= 1000000 if $datestamp > 99999999;
369 $datestamp = int($datestamp);
370 my $year = int( $datestamp / 10000 ) - 1900;
371 my $month = int( ( $datestamp / 100 ) % 100 ) - 1;
372 my $day = int( $datestamp % 100 );
373 my $date = POSIX::strftime( '%B %e, %Y', 0, 0, 0, $day, $month, $year );
377 if ( !$report->get_flag("got_finish")
378 || $report->get_flag("dump_failed") != 0) {
380 } elsif ($report->get_flag("results_missing") != 0) {
382 } elsif ($report->get_flag("dump_strange") != 0) {
387 getconf($CNF_ORG) . $done
388 . ( $report->get_flag("amflush_run") ? " AMFLUSH" :
389 $report->get_flag("amvault_run") ? " AMVAULT" : " AMANDA" )
390 . " MAIL REPORT FOR "
393 my $cfg_mailer = getconf($CNF_MAILER);
395 my @cmd = ("$cfg_mailer", "-s", $subj_str, split(/ +/, $mailto));
396 debug("invoking mail app: " . join(" ", @cmd));
400 eval { $pid = open3($fh, ">&2", ">&2", @cmd); 1; } or do {
402 ($pid, $fh) = (0, undef);
404 "error: could not run command: " . join(" ", @cmd) . ": $@";
406 if ($mode == MODE_SCRIPT) {
418 my ($reportspec, $outputspec) = @$output;
422 if ($outputspec->[0] eq 'file') {
423 $fh = open_file_output($report, $outputspec);
424 } elsif ($outputspec->[0] eq 'printer') {
425 ($pid, $fh) = open_printer_output($report, $outputspec);
426 } elsif ($outputspec->[0] eq 'mail') {
427 ($pid, $fh) = open_mail_output($report, $outputspec);
430 # TODO: add some generic error handling here. must be compatible
431 # with legacy behavior.
433 # TODO: modularize these better
434 if ($reportspec->[0] eq 'xml') {
435 print $fh $report->xml_output("" . getconf($CNF_ORG), $config_name);
436 } elsif ($reportspec->[0] eq 'human') {
438 Amanda::Report::human->new( $report, $fh, $config_name, $opt_logfname );
439 $hr->print_human_amreport();
440 } elsif ($reportspec->[0] eq 'postscript') {
441 use Amanda::Report::postscript;
443 Amanda::Report::postscript->new( $report, $config_name, $opt_logfname );
444 $rep->write_report($fh);
449 # clean up any subprocess
451 debug("waiting for child process to finish..");
454 warning("child exited with status $?");
460 ## Application initialization
462 Amanda::Util::setup_application("amreport", "server", $CONTEXT_CMDLINE);
464 my $config_overrides = new_config_overrides( scalar(@ARGV) + 1 );
466 Getopt::Long::Configure(qw/bundling/);
469 ## old legacy configuration opts
470 "i" => sub { set_mode(MODE_SCRIPT); $opt_nomail = 1; },
471 opt_set_var("M", \$opt_mailto),
472 opt_set_var("f", \$opt_filename),
473 opt_set_var("l", \$opt_logfname),
474 opt_set_var("p", \$opt_psfname),
476 "o=s" => sub { add_config_override_opt($config_overrides, $_[1]); },
478 ## trigger default amdump behavior
479 "from-amdump" => sub { set_mode(MODE_SCRIPT) },
481 ## new configuration opts
482 "log=s" => sub { set_mode(MODE_CMDLINE); $opt_logfname = $_[1]; },
483 "ps:s" => sub { opt_push_queue([ ['postscript'], [ 'file', $_[1] ] ]); },
484 "mail-text:s" => sub { opt_push_queue([ ['human'], [ 'mail', $_[1] ] ]); },
485 "text:s" => sub { opt_push_queue([ ['human'], [ 'file', $_[1] ] ]); },
486 "xml:s" => sub { opt_push_queue([ ['xml'], [ 'file', $_[1] ] ]); },
487 "print:s" => sub { opt_push_queue([ [ 'postscript' ], [ 'printer', $_[1] ] ]); },
489 'version' => \&Amanda::Util::version_opt,
493 # set command line mode if no options were given
494 $mode = MODE_CMDLINE if ($mode == MODE_NONE);
496 if ($mode == MODE_CMDLINE) {
497 (scalar @ARGV == 1) or usage();
498 } else { # MODE_SCRIPT
499 (scalar @ARGV > 0) or usage();
502 $config_name = shift @ARGV; # only use first argument
503 $config_name ||= '.'; # default config is current dir
505 set_config_overrides($config_overrides);
506 config_init( $CONFIG_INIT_EXPLICIT_NAME, $config_name );
508 my ( $cfgerr_level, @cfgerr_errors ) = config_errors();
509 if ( $cfgerr_level >= $CFGERR_WARNINGS ) {
510 config_print_errors();
511 if ( $cfgerr_level >= $CFGERR_ERRORS ) {
512 error( "errors processing config file", 1 );
516 Amanda::Util::finish_setup($RUNNING_AS_DUMPUSER);
519 my $tl_file = config_dir_relative(getconf($CNF_TAPELIST));
520 my $tl = Amanda::Tapelist->new($tl_file);
523 my $diskfile = config_dir_relative(getconf($CNF_DISKFILE));
524 $cfgerr_level += Amanda::Disklist::read_disklist('filename' => $diskfile);
525 ($cfgerr_level < $CFGERR_ERRORS) || die "Errors processing disklist";
527 # shim for installchecks
528 $Amanda::Constants::LPR = $ENV{'INSTALLCHECK_MOCK_LPR'}
529 if exists $ENV{'INSTALLCHECK_MOCK_LPR'};
531 # calculate the logfile to read from
532 $opt_logfname = Amanda::Util::get_original_cwd() . "/" . $opt_logfname
533 if defined $opt_logfname and $opt_logfname !~ /^\//;
534 my $logfile = $opt_logfname || get_default_logfile();
535 my $historical = defined $opt_logfname;
536 debug("using logfile: $logfile" . ($historical? " (historical)" : ""));
538 if ($mode == MODE_CMDLINE) {
539 debug("operating in cmdline mode");
540 apply_output_defaults();
541 push @outputs, [ ['human'], [ 'file', '-' ] ] if !@outputs;
543 debug("operating in script mode");
544 calculate_legacy_outputs();
547 ## Parse the report & set output
549 $report = Amanda::Report->new($logfile, $historical);
550 my $exit_status = $report->get_flag("exit_status");
552 ## filter outputs by errors & stranges
554 @outputs = grep { legacy_send_amreport($_) } @outputs;
556 for my $output (@outputs) {
557 debug("planned output: " . join(" ", @{ $output->[FORMAT] }, @{ $output->[OUTPUT] }));
562 for my $output (@outputs) {
566 Amanda::Util::finish_application();