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