Imported Upstream version 3.3.3
[debian/amanda] / perl / Amanda / Curinfo / Info.pm
1 # Copyright (c) 2010-2012 Zmanda, Inc.  All Rights Reserved.
2 #
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
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 94085, USA, or: http://www.zmanda.com
19
20
21 =head1 NAME
22
23 Amanda::Curinfo::Info - Perl extension for representing dump
24 information
25
26 =head1 SYNOPSIS
27
28    use Amanda::Curinfo::Info;
29
30    my $info = Amanda::Curinfo::Info->new($infofile);
31
32 =head1 DESCRIPTION
33
34 C<Amanda::Curinfo::Info> is the format representation for the curinfo
35 database.  It handles the reading and writing of the individual
36 entries, while the entry management is left to C<Amanda::Curinfo>.
37 Further parsing is also dispatched to C<Amanda::Curinfo::History>,
38 C<Amanda::Curinfo::Stats>, and C<Amanda::Curinfo::Perf>.
39
40 =head1 INTERFACE
41
42 The constructor for a new info object is very simple.
43
44    my $info = Amanda::Curinfo::Info->new();
45
46 Will return an empty info object with the necessary fields all blank.
47
48 Given an existing C<$info> object, for example, as provided by
49 C<Amanda::Curinfo::get_info>, there are other functions present in this
50 library, but they are helper functions to the previously described
51 methods, and not to be used directly.
52
53 It should also be noted that the reading and writing methods of
54 C<Amanda::Curinfo::Info> are not meant to be used directly, and should be
55 left to L<Amanda::Curinfo>.
56
57 Reading a previously stored info object is handled with the same
58 subroutine.
59
60    my $info = Amanda::Curinfo::Info->new($infofile);
61
62 Here, C<$info> will contain all the information that was stored in
63 C<$infofile>.
64
65 To write the file to a new location, use the following command:
66
67    $info->write_to_file($infofile);
68
69 There are also three corresponding container classes that hold data
70 and perform parsing functions.  They should only be used when actually
71 writing info file data.
72
73    my $history =
74      Amanda::Curinfo::History->new( $level, $size, $csize, $date, $secs );
75    my $stats =
76      Amanda::Curinfo::Stats->new( $level, $size, $csize, $secs, $date, $filenum,
77        $label );
78
79    my $perf = Amanda::Curinfo::Perf->new();
80    $perf->set_rate( $pct1, $pct2, $pct3 );
81    $perf->set_comp( $dbl1, $dbl2, $dbl3 );
82
83 Note that C<Amanda::Curinfo::Perf> is different.  This is because its
84 structure is broken up into two lines in the infofile format, and the
85 length of the C<rate> and C<comp> arrays maybe subject to change in
86 the future.
87
88 You can also instantiate these objects directly from a
89 properly-formatted line in an infofile:
90
91    my $history = Amanda::Curinfo::History->from_line($hist_line);
92    my $stats   = Amanda::Curinfo::Stats->from_line($stat_line);
93
94    my $perf = Amanda::Curinfo::Perf->new();
95    $perf->set_rate_from_line($rate_line);
96    $perf->set_comp_from_line($comp_line);
97
98 Again, creating C<Amanda::Curinfo::Perf> is broken into two calls
99 because its object appears on two lines.
100
101 Writing these objects back to the info file, however, are all identical:
102
103    print $infofh $history->to_line();
104    print $infofh $stats->to_line();
105    print $infofh $perf_full->to_line("full");
106    print $infofh $perf_incr->to_line("incr");
107
108 Additionally, the C<$perf> object accepts a prefix to the line.
109
110 =head1 SEE ALSO
111
112 This package is meant to replace the file reading and writing portions
113 of server-src/infofile.h.  If you notice any bugs or compatibility
114 issues, please report them.
115
116 =head1 AUTHOR
117
118 Paul C. Mantz E<lt>pcmantz@zmanda.comE<gt>
119
120 =cut
121
122 my $numdot = qr{[.\d]};
123
124 package Amanda::Curinfo::Info;
125
126 use strict;
127 use warnings;
128 use Carp;
129
130 use Amanda::Config;
131
132 sub new
133 {
134     my ($class, $infofile) = @_;
135
136     my $self = {
137         command => undef,
138         full    => Amanda::Curinfo::Perf->new(),
139         incr    => Amanda::Curinfo::Perf->new(),
140         inf              => [],      # contains Amanda::Curinfo::Stats
141         history          => [],      # contains Amanda::Curinfo::History
142         last_level       => undef,
143         consecutive_runs => undef,
144     };
145
146     bless $self, $class;
147     $self->read_infofile($infofile) if -e $infofile;
148
149     return $self;
150 }
151
152 sub get_dumpdate
153 {
154     my ( $self, $level ) = @_;
155     my $inf  = $self->{inf};
156     my $date = 0;            # Ideally should be set to the epoch, but 0 is fine
157
158     for ( my $l = 0 ; $l < $level ; $l++ ) {
159
160         my $this_date = $inf->[$l]->{date};
161         $date = $this_date if ( $this_date > $date );
162     }
163
164     my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) =
165       gmtime $date;
166
167     my $dumpdate = sprintf(
168         '%d:%d:%d:%d:%d:%d',
169         $year + 1900,
170         $mon + 1, $mday, $hour, $min, $sec
171     );
172
173     return $dumpdate;
174 }
175
176 sub read_infofile
177 {
178     my ( $self, $infofile ) = @_;
179
180     open my $fh, "<", $infofile or croak "couldn't open $infofile: $!";
181
182     ## read in the fixed-length data
183     $self->read_infofile_perfs($fh);
184
185     ## read in the stats data
186     $self->read_infofile_stats($fh);
187
188     ## read in the history data
189     $self->read_infofile_history($fh);
190
191     close $fh;
192
193     return 1;
194 }
195
196 sub read_infofile_perfs
197 {
198     my ($self, $fh) = @_;
199
200     my $fail = sub {
201         my ($line) = @_;
202         croak "error: malformed infofile header in $self->infofile:$line\n";
203     };
204
205     my $skip_blanks = sub {
206         my $line = "";
207         while ($line eq "") {
208             croak "error: infofile ended prematurely" if eof($fh);
209             $line = <$fh>;
210         }
211         return $line;
212     };
213
214     # version not paid attention to right now
215     my $line = $skip_blanks->();
216     ($line =~ /^version: ($numdot+)/) ? 1 : $fail->($line);
217
218     $line = $skip_blanks->();
219     ($line =~ /^command: ($numdot+)/) ? $self->{command} = $1 : $fail->($line);
220
221     $line = $skip_blanks->();
222     ($line =~ /^full-rate: ($numdot+) ($numdot+) ($numdot+)/)
223       ? $self->{full}->set_rate($1, $2, $3)
224       : $fail->($line);
225
226     $line = $skip_blanks->();
227     ($line =~ /^full-comp: ($numdot+) ($numdot+) ($numdot+)/)
228       ? $self->{full}->set_comp($1, $2, $3)
229       : $fail->($line);
230
231     $line = $skip_blanks->();
232     ($line =~ /^incr-rate: ($numdot+) ($numdot+) ($numdot+)/)
233       ? $self->{incr}->set_rate($1, $2, $3)
234       : $fail->($line);
235
236     $line = $skip_blanks->();
237     ($line =~ /^incr-comp: ($numdot+) ($numdot+) ($numdot+)/)
238       ? $self->{incr}->set_comp($1, $2, $3)
239       : $fail->($line);
240
241     return 1;
242 }
243
244 sub read_infofile_stats
245 {
246     my ( $self, $fh ) = @_;
247
248     my $inf = $self->{inf};
249
250     while ( my $line = <$fh> ) {
251
252         ## try next line if blank
253         if ( $line eq "" ) {
254             next;
255
256         } elsif ( $line =~ m{^//} ) {
257             croak "unexpected end of data in stats section (received //)\n";
258
259         } elsif ( $line =~ m{^history:} ) {
260             croak "history line before end of stats section\n";
261
262         } elsif ( $line =~ m{^stats:} ) {
263
264             ## make a new Stats object and push it on to the queue
265             my $stats = Amanda::Curinfo::Stats->from_line($line);
266             push @$inf, $stats;
267
268         } elsif ( $line =~ m{^last_level: (\d+) (\d+)$} ) {
269
270             $self->{last_level}       = $1;
271             $self->{consecutive_runs} = $2;
272             last;
273
274         } else {
275             croak "bad line in read_infofile_stats: $line";
276         }
277     }
278
279     return 1;
280 }
281
282 sub read_infofile_history
283 {
284     my ( $self, $fh ) = @_;
285
286     my $history = $self->{history};
287
288     while ( my $line = <$fh> ) {
289
290         if ( $line =~ m{^//} ) {
291             return;
292
293         } elsif ( $line =~ m{^history:} ) {
294             my $hist = Amanda::Curinfo::History->from_line($line);
295             push @$history, $hist;
296
297         } else {
298             croak "bad line found in history section:$line\n";
299         }
300     }
301
302     #
303     # TODO: make sure there were the right number of history lines
304     #
305
306     return 1;
307 }
308
309 sub write_to_file
310 {
311     my ( $self, $infofile ) = @_;
312
313     unlink $infofile if -f $infofile;
314
315     open my $fh, ">", $infofile or die "error: couldn't open $infofile: $!";
316
317     ## print basics
318
319     print $fh "version: 0\n";    # 0 for now, may change in future
320     print $fh "command: $self->{command}\n";
321     print $fh $self->{full}->to_line("full");
322     print $fh $self->{incr}->to_line("incr");
323
324     ## print stats
325
326     foreach my $stat ( @{ $self->{inf} } ) {
327         print $fh $stat->to_line();
328     }
329     print $fh "last_level: $self->{last_level} $self->{consecutive_runs}\n";
330
331     foreach my $hist ( @{ $self->{history} } ) {
332         print $fh $hist->to_line();
333     }
334     print $fh "//\n";
335
336     return 1;
337 }
338
339 1;
340
341 #
342 #
343 #
344
345 package Amanda::Curinfo::History;
346
347 use strict;
348 use warnings;
349 use Carp;
350
351 sub new
352 {
353     my $class = shift;
354     my ( $level, $size, $csize, $date, $secs ) = @_;
355
356     my $self = {
357         level => $level,
358         size  => $size,
359         csize => $csize,
360         date  => $date,
361         secs  => $secs,
362     };
363
364     return bless $self, $class;
365 }
366
367 sub from_line
368 {
369     my ( $class, $line ) = @_;
370
371     my $self = undef;
372
373     if (
374         $line =~ m{^history:    \s+
375                      (\d+)      \s+  # level
376                      ($numdot+) \s+  # size
377                      ($numdot+) \s+  # csize
378                      ($numdot+) \s+  # date
379                      ($numdot+) $    # secs
380                   }x
381       ) {
382         $self = {
383             level => $1,
384             size  => $2,
385             csize => $3,
386             date  => $4,
387             secs  => $5,
388         };
389     } else {
390         croak "bad history line: $line";
391     }
392
393     return bless $self, $class;
394 }
395
396 sub to_line
397 {
398     my ($self) = @_;
399     return
400 "history: $self->{level} $self->{size} $self->{csize} $self->{date} $self->{secs}\n";
401 }
402
403 1;
404
405 #
406 #
407 #
408
409 package Amanda::Curinfo::Perf;
410
411 use strict;
412 use warnings;
413 use Carp;
414
415 use Amanda::Config;
416
417 sub new
418 {
419     my ($class) = @_;
420
421     my $self = {
422         rate => undef,
423         comp => undef,
424     };
425
426     return bless $self, $class;
427 }
428
429 sub set_rate
430 {
431     my ( $self, @rate ) = @_;
432     $self->{rate} = \@rate;
433 }
434
435 sub set_comp
436 {
437     my ( $self, @comp ) = @_;
438     $self->{comp} = \@comp;
439 }
440
441 sub set_rate_from_line
442 {
443     my ( $self, $line ) = @_;
444     return $self->set_field_from_line( $self, $line, "rate" );
445
446 }
447
448 sub set_comp_from_line
449 {
450     my ( $self, $line ) = @_;
451     return $self->set_field_from_line( $self, $line, "comp" );
452
453 }
454
455 sub set_field_from_line
456 {
457     my ( $self, $line, $field ) = @_;
458
459     if (
460         $line =~ m{\w+-$field\: \s+
461                       ($numdot) \s+
462                       ($numdot) \s+
463                       ($numdot) $
464                    }x
465       ) {
466         $self->{$field} = [ $1, $2, $3 ];
467
468     } else {
469         croak "bad perf $field line: $line";
470     }
471
472     return;
473 }
474
475 sub to_line
476 {
477     my ( $self, $lvl ) = @_;
478     return
479         "$lvl-rate: "
480       . join( " ", @{ $self->{rate} } ) . "\n"
481       . "$lvl-comp: "
482       . join( " ", @{ $self->{comp} } ) . "\n";
483 }
484
485 1;
486
487 #
488 #
489 #
490
491 package Amanda::Curinfo::Stats;
492
493 use strict;
494 use warnings;
495 use Carp;
496
497 sub new
498 {
499     my $class = shift;
500     my ( $level, $size, $csize, $secs, $date, $filenum, $label ) = @_;
501
502     my $self = {
503         level   => $level,
504         size    => $size,
505         csize   => $csize,
506         secs    => $secs,
507         date    => $date,
508         filenum => $filenum,
509         label   => $label,
510     };
511
512     bless $self, $class;
513     return $self;
514 }
515
516 sub from_line
517 {
518     my ( $class, $line ) = @_;
519     my $self = undef;
520
521     $line =~ m{^stats:      \s+
522                      (\d+)      \s+   # level
523                      ($numdot+) \s+   # size
524                      ($numdot+) \s+   # csize
525                      ($numdot+) \s+   # sec
526                      ($numdot+) \s+   # date
527                      ($numdot+) \s+   # filenum
528                      (.*) $           # label
529               }x
530       or croak "bad stats line: $line";
531
532     $self = {
533         level   => $1,
534         size    => $2,
535         csize   => $3,
536         secs    => $4,
537         date    => $5,
538         filenum => $6,
539         label   => $7,
540     };
541     return bless $self, $class;
542 }
543
544 sub to_line
545 {
546     my ($self) = @_;
547     return join( " ",
548         "stats:",      $self->{level}, $self->{size},    $self->{csize},
549         $self->{secs}, $self->{date},  $self->{filenum}, $self->{label} )
550       . "\n";
551 }
552
553 1;