0dc5fc1ef98841073401f97e4a31df0497deaefc
[debian/amanda] / server-src / amoverview.pl
1 #!@PERL@
2
3 # Catch for sh/csh on systems without #! ability.
4 eval '(exit $?0)' && eval 'exec @PERL@ -S $0 ${1+"$@"}'
5         & eval 'exec @PERL@ -S $0 $argv:q'
6                 if 0;
7
8 require 5.001;
9
10 use FileHandle;
11 use Getopt::Long;
12 use Text::ParseWords;
13 use Carp;
14 use POSIX;
15
16 delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV', 'PATH'};
17 $ENV{'PATH'} = "/usr/bin:/usr/sbin:/sbin:/bin";
18
19 sub Usage {
20     print STDERR <<END;
21 Usage: $0 [[-config] CONFIG] [-hostwidth width] [-diskwidth width] [-skipmissed] [-last] [-num0] [-togo0] [-verbose]
22
23 This script generates to standard output an overview of the filesystems
24 dumped over time and the type of dump done on a particular day, such as
25 a full dump, or an incremental, or if the dump failed.
26
27 You may override the default configuration `@DEFAULT_CONFIG@' by using
28 the -config command line option.  On larger installations, this script
29 will take a while to run.  In this case, run it with --verbose to see
30 how far along it is.
31 END
32     exit 1;
33 }
34
35 # Default paths for this installation of Amanda.
36 my $prefix='@prefix@';
37 $prefix=$prefix;                # avoid warnings about possible typo
38 my $exec_prefix="@exec_prefix@";
39 $exec_prefix=$exec_prefix;      # ditto
40 my $amlibexecdir="@amlibexecdir@";
41 my $sbindir="@sbindir@";
42  
43 # The directory where configurations can be found.
44 my $confdir="@CONFIG_DIR@";
45
46 # The default configuration.
47 my $config="@DEFAULT_CONFIG@";
48
49 # Get the version suffix.
50 my $USE_VERSION_SUFFIXES = '@USE_VERSION_SUFFIXES@';
51 my $suf = '';
52 if ( $USE_VERSION_SUFFIXES =~ /^yes$/i ) {
53         $suf='-@VERSION@';
54 }
55
56 my $amadmin     = "$sbindir/amadmin$suf";
57
58 # overrideable defaults
59 my $opt_config          = "$config";
60 my $opt_hostwidth       = 8;
61 my $opt_diskwidth       = 20;
62 my $opt_skipmissed      = 0;
63 my $opt_last            = 0;
64 my $opt_num0            = 0;
65 my $opt_togo0           = 0;
66 my $opt_verbose         = 0;
67
68 GetOptions('config=s'           => \$opt_config,
69            'hostwidth=i'        => \$opt_hostwidth,
70            'diskwidth=i'        => \$opt_diskwidth,
71            'skipmissed'         => \$opt_skipmissed,
72            'last'               => \$opt_last,
73            'num0'               => \$opt_num0,
74            'togo0'              => \$opt_togo0,
75            'verbose'            => \$opt_verbose)
76 or Usage();
77
78 if($#ARGV == 0) {
79   $opt_config = $ARGV[0];
80 }
81 elsif($#ARGV > 0) {
82   Usage();
83 }
84
85 #untaint user input $ARGV[0]
86
87 if ($opt_config =~ /^([\w.-]+)$/) {          # $1 is untainted
88    $opt_config = $1;
89 } else {
90     die "filename '$opt_config' has invalid characters.\n";
91 }
92
93
94 -d "$confdir/$opt_config" or
95         die "$0: directory `$confdir/$opt_config' does not exist.\n";
96
97 # read disklist
98 my %disks = ();
99 $::host = '';
100 $::disk = '';
101 $opt_verbose and
102     print STDERR "Running $amadmin $opt_config disklist\n";
103 my $dlfh = new FileHandle "$amadmin $opt_config disklist|" or
104     die "$0: error in opening `$amadmin $opt_config disklist' pipe: $!\n";
105 $/ = "";
106 while (<$dlfh>) {
107     ($host, $disk) = m/    host (.*?):\n.*    disk (.*?):\n.*strategy (STANDARD|NOFULL|NOINC|HANOI|INCRONLY).*ignore NO/ms;
108     next unless $host;
109     $disks{$host}{$disk}++;
110 }
111
112 $/ = "\n";
113 $dlfh->close or
114     die "$0: error in closing `$amadmin $opt_config disklist|' pipe: $!\n";
115
116 # Get backup dates
117 %::dates = ();
118 %::level = ();
119 $::level = '';
120 my ($date, $tape, $file, $status);
121 $opt_verbose and
122     print STDERR "Running $amadmin $opt_config find\n";
123 my $fh = new FileHandle "$amadmin $opt_config find|" or
124     die "$0: error in opening `$amadmin $opt_config find' pipe: $!\n";
125 <$fh>;
126 while (<$fh>) {
127     chomp;
128     next if /found Amanda directory/;
129     next if /skipping cruft directory/;
130     next if /skip-incr/;
131
132     ($date, $time, $host, $disk, $level, $tape, $file, $part, $status) = shellwords($_);
133
134     next if $date eq 'date';
135     next if $date eq 'Warning:';
136     next if $date eq 'Scanning';
137     next if $date eq "";
138
139     if($time !~/^\d\d:\d\d:\d\d$/) {
140         $status = $part;
141         $part = $file;
142         $file = $tape;
143         $tape = $level;
144         $level = $disk;
145         $disk = $host;
146         $host = $time;
147     }
148
149     if ($date =~ /^\d\d\d\d-\d\d-\d\d$/) {
150         if(defined $disks{$host}{$disk}) {
151             defined($level{$host}{$disk}{$date}) or
152                 $level{$host}{$disk}{$date} = '';
153             $level{$host}{$disk}{$date} .= ($status eq 'OK') ? $level : 'E';
154             $dates{$date}++;
155         }
156     }
157     else {
158         print "bad date $date in $_\n";
159     }
160 }
161 $fh->close or
162     die "$0: error in closing `$amadmin $opt_config find|' pipe: $!\n";
163
164 # Process the status to arrive at a "last" status
165 if ($opt_last) {
166     for $host (sort keys %disks) {
167         for $disk (sort keys %{$disks{$host}}) {
168             $level{$host}{$disk}{"0000-LA-ST"} = '';
169             for $date (sort keys %dates) {
170                 if ($level{$host}{$disk}{$date} eq "E"
171                      && $level{$host}{$disk}{"0000-LA-ST"} =~ /^\d/ ) {
172                     $level{$host}{$disk}{"0000-LA-ST"} .= $level{$host}{$disk}{$date};
173                 } elsif ($level{$host}{$disk}{$date} eq "") {
174                     $level{$host}{$disk}{"0000-LA-ST"} =~ s/E//;
175                 } else {
176                     $level{$host}{$disk}{"0000-LA-ST"} = $level{$host}{$disk}{$date};
177                 }
178             }
179         }
180     }
181 }
182
183 # Number of level 0 backups
184 if ($opt_num0) {
185     for $host (sort keys %disks) {
186         for $disk (sort keys %{$disks{$host}}) {
187             $level{$host}{$disk}{'0000-NM-L0'} = 0;
188             for $date (sort keys %dates) {
189                 if ($level{$host}{$disk}{$date} =~ /0/) {
190                     $level{$host}{$disk}{'0000-NM-L0'} += 1;
191                 }
192             }
193         }
194     }
195 }
196
197 # Runs to the last level 0
198 if ($opt_togo0) {
199     for $host (sort keys %disks) {
200         for $disk (sort keys %{$disks{$host}}) {
201             $level{$host}{$disk}{'0000-TO-GO'} = 0;
202             my $togo=0;
203             for $date (sort keys %dates) {
204                 if ($level{$host}{$disk}{$date} =~ /0/) {
205                     $level{$host}{$disk}{'0000-TO-GO'} = $togo;
206                 }
207                 $togo++;
208             }
209         }
210     }
211 }
212
213 unless ($opt_skipmissed)
214 # touch all the dates just in case whole days were missed.
215 {
216     my ($start, $finish) = 
217         map {
218             my($y,$m,$d) = split /-/, $_;
219             POSIX::mktime(0,0,0,$d,$m-1,$y-1900);
220         } (sort keys %dates)[0,-1];
221
222     while ($start < $finish) {
223         my @l = localtime $start;
224         $dates{sprintf("%d-%02d-%02d", 1900+$l[5], $l[4]+1, $l[3])}++;
225         $start += 86400;
226     }
227 }
228
229 #Add the "last" entry    
230 $dates{"0000-LA-ST"}=1 if ($opt_last);
231
232 #Add the "Number of Level 0s" entry
233 $dates{"0000-NM-L0"}=1 if ($opt_num0);
234
235 #Add the "Runs to go" entry
236 $dates{"0000-TO-GO"}=1 if ($opt_togo0);
237
238 # make formats
239
240 my $top_format = "format TOP =\n\n" .
241     sprintf("%-0${opt_hostwidth}s %-0${opt_diskwidth}s ", '', 'date') .
242     join(' ', map((split(/-/, $_))[1], sort keys %dates)) . "\n" .
243     sprintf("%-0${opt_hostwidth}s %-0${opt_diskwidth}s ", 'host', 'disk') .
244     join(' ', map((split(/-/, $_))[2], sort keys %dates)) . "\n" .
245     "\n.\n";
246
247 my $out_format = "format STDOUT =\n" .
248     "@" . "<" x ($opt_hostwidth - 1) . ' ' .
249     "@" . "<" x ($opt_diskwidth - 1) . ' ' .
250     '@> ' x scalar(keys %dates) . "\n" .
251     join(', ', '$host', '$disk', 
252          map("substr(\$level{\$host}{\$disk}{'$_'},-2)", sort keys %dates)) . "\n" .
253     ".\n";
254
255 eval $top_format;
256 die $@ if $@;
257 $^ = 'TOP';
258 eval $out_format;
259 die $@ if $@;
260
261 for $host (sort keys %disks) {
262     for $disk (sort keys %{$disks{$host}}) {
263         write;
264     }
265 }