--- /dev/null
+package LogRotate::StateFile;
+
+# $Id$
+# $URL$
+
+=head1 NAME
+
+B<LogRotate/StateFile.pm> - Logrotate Statusdatei Modul
+
+=head1 SYNOPSIS
+
+ my $statefile = new LogRotate::StateFile();
+
+ $statefile->verbose(2);
+ $statefile->file('/var/lib/logrotate.status');
+
+ if ( $statefile->check() ) { ...
+
+ $last_rotated_files = $statefile->read();
+
+ ...
+
+ $statefile->write_logfile('/var/log/messages');
+
+=cut
+
+use strict;
+use 5.8.0;
+use warnings;
+
+use Carp qw(:DEFAULT cluck);
+
+#------------------------------------------------------------------------------------
+
+use Data::Dumper;
+use POSIX qw( mktime );
+use Cwd qw(cwd getcwd abs_path);
+
+our $AUTOLOAD;
+our %ok_field;
+
+my $MainVersion = "2.0";
+
+my $Revis = <<'ENDE';
+ $Revision$
+ENDE
+$Revis =~ s/^.*:\s*(\S+)\s*\$.*/$1/s;
+our $VERSION = $MainVersion . "." . $Revis;
+
+$Data::Dumper::Indent = 1;
+$Data::Dumper::Sortkeys = 1;
+
+use constant default_firstline_statusfile => "Logrotate State -- Version 3";
+
+my @ValidFields = qw( parent verbose file test );
+
+for my $attr ( @ValidFields ) {
+ $ok_field{$attr}++;
+}
+
+#------------------------------------------------------------------------------------
+
+=head1 Funktionen
+
+=head2 sub new() - Konstruktor
+
+Wird aufgerufen, um ein neues LogRotate::StateFile-Objekt zu erstellen.
+
+=cut
+
+sub new {
+
+ my $invocant = shift;
+ my $class = ref($invocant) || $invocant;
+ my ( $res, $cmd );
+
+ my $self = {
+ 'verbose' => 0,
+ 'file' => '/val/lib/logrotate.status',
+ @_
+ };
+
+ $res = bless $self, $class;
+
+ my $p = $self->verbose() ? __PACKAGE__ . "::new(): " : "";
+
+ return $res;
+
+}
+
+#------------------------------------------------------------------------------------------
+
+=head2 AUTOLOAD()
+
+Autoload-Methode zum Zugriff auf alle möglichen Elemente.
+
+=cut
+
+sub AUTOLOAD {
+
+ my $self = shift;
+ my $attr = $AUTOLOAD;
+ my ( $val );
+
+ $attr =~ s/.*:://;
+ $attr = lc($attr);
+
+ croak "Ungueltige Attributmethode ->$attr()" unless $ok_field{$attr};
+
+ return $self->file(@_) if $attr eq "file";
+ return $self->test(@_) if $attr eq "test";
+ return $self->verbose(@_) if $attr eq "verbose";
+
+ if ( @_ ) {
+ $val = shift;
+ $self->{uc($attr)} = $val;
+ }
+ return $self->{uc($attr)};
+
+}
+
+#------------------------------------------------------------------------------------------
+
+=head2 check( [$file] )
+
+
+
+=cut
+
+sub check($;$) {
+
+ my $self = shift;
+ my $file = shift;
+ my $p = $self->verbose() ? __PACKAGE__ . "::check(): " : "";
+
+ print $p . "Aufgerufen mit '" . ( defined $file ? $file : "<undef>" ) . "'.\n" if $self->verbose() > 2;
+
+ my $res = $self->read($file);
+
+ return undef unless $res;
+ $file = $self->{'file'};
+
+ unless ( $self->test() ) {
+ if ( open FILE, ">>$file" ) {
+ close FILE;
+ } else {
+ warn $p . "Fehler beim Oeffnen der Status-Datei '$file' zum Schreiben: $!\n";
+ return undef;
+ }
+ }
+
+ return $res;
+
+}
+
+#------------------------------------------------------------------------------------------
+
+=head2 read( [$file] )
+
+Liest die uebergebene Datei in die Konfiguration ein.
+
+Wenn der Parameter $file nicht uebergeben wurde, wird statt dessen $self->{'file'}
+verwendet, welches auch gesetzt wird, wenn $file uebergeben wurde.
+
+Bei Erfolg wird eine Datenstruktur als Hash-Ref uebergeben, deren
+Keys die aus der Datei gelesenen Logdateien sind und deren Values die Zeitstempel
+der rotierten Logdateien.
+
+Bei Misserfolg wird C<undef> zurueckgegeben.
+
+=cut
+
+sub read($;$) {
+
+ my $self = shift;
+ my $file = shift;
+ my $p = $self->verbose() ? __PACKAGE__ . "::read(): " : "";
+
+ my $res = {};
+
+ print $p . "Aufgerufen mit '" . ( defined $file ? $file : "<undef>" ) . "'.\n" if $self->verbose() > 2;
+
+ my ( $f, $dir, $real_dir, $logfile, $date, $time_t );
+
+ $file = $self->{'file'} unless $file;
+ unless ( $file ) {
+ carp $p . "Keine Datei uebergeben.\n";
+ return undef;
+ }
+
+ if ( $file =~ m#/# ) {
+ ( $dir, $f ) = $file =~ m#(.*)/([^/]+)$#;
+ } else {
+ $dir = ".";
+ $f = $file;
+ }
+ $real_dir = abs_path( $dir );
+ print $p . "Real-Path: '$real_dir', Basename: '$f'\n" if $self->{'verbose'} > 2;
+ $f = $real_dir . "/" . $f;
+
+
+ unless ( -f $file ) {
+ warn $p . "Datei '$file' existiert nicht oder ist keine normale Datei.\n";
+ $self->{'file'} = $f;
+ return $res;
+ }
+
+ print $p . "Lese Datei '$f' ...\n" if $self->verbose();
+ unless ( open FILE, "<$f" ) {
+ warn $p . "Konnte Datei '$f' nicht oeffnen: $!\n";
+ return undef;
+ }
+
+ my $i = 0;
+ while ( <FILE> ) {
+
+ $i++;
+ s/^\s+//;
+ s/\s+$//;
+
+ if ( $i == 1 ) {
+ if ( /^logrotate\s+state\s+-+\s+version\s+[123]$/i ) {
+ next;
+ } else {
+ warn $p . "Inkompatible Version der Statusdatei '$f'.\n";
+ close FILE;
+ return undef;
+ }
+ }
+
+ next unless $_;
+
+ ( $logfile, $date ) = $self->parts( $_ );
+ if ( $logfile and $date ) {
+ my @Date = $date =~ /^\s*(\d+)[_\-](\d+)[_\-](\d+)(?:[\s\-_]+(\d+)[_\-:](\d+)[_\-:](\d+))?/;
+ unless ( @Date ) {
+ warn $p . "Konnte Datum nicht erkennen: '$date' (Datei '$f', Zeile $.).\n";
+ close FILE;
+ return undef;
+ }
+ $time_t = mktime( $Date[5] || 0, $Date[4] || 0, $Date[3] || 0, $Date[2], $Date[1] - 1, $Date[0] - 1900 );
+ unless ( $time_t ) {
+ warn $p . "Unbekanntes Datumsformat: '$date' (Datei '$f', Zeile $.).\n";
+ close FILE;
+ return undef;
+ }
+ }
+
+ $res->{$logfile} = $time_t;
+
+ }
+
+ close FILE;
+ $self->{'file'} = $f;
+ return $res;
+
+}
+
+#------------------------------------------------------------------------------------------
+
+=head2 file()
+
+Setzt bzw. gibt die Status-Datei dieses Moduls zurueck.
+
+=cut
+
+sub file($;$) {
+
+ my $self = shift;
+ my $nv;
+ if ( @_ ) {
+ $nv = shift;
+ $self->{'file'} = $nv if defined $nv;
+ }
+ return $self->{'file'};
+
+}
+
+#------------------------------------------------------------------------------------
+
+=head2 parts( $string )
+
+Zerlegt einen String an Whitespaces in seine Bestandteile unter Beachtung
+von Quotierung und gibt diese als Array zurueck.
+
+=cut
+
+sub parts($$) {
+
+ my $self = shift;
+ my $p = $self->verbose() ? __PACKAGE__ . "::parts(): " : "";
+
+ my $term = shift;
+ my @Parts = ();
+ my $part;
+
+ while ( $term =~ /"([^"\\]*(?:\\.[^"\\]*)*)"|(\S+)/g ) {
+ $part = $1 || $2;
+ $part =~ s/\\"/"/g;
+ push @Parts, $part;
+ }
+
+ return @Parts;
+
+}
+
+#------------------------------------------------------------------------------------------
+
+=head2 test()
+
+Setzt bzw. gibt den Test-Modus dieses Moduls zurueck.
+
+=cut
+
+sub test($;$) {
+
+ my $self = shift;
+ my $nv;
+ if ( @_ ) {
+ $nv = shift;
+ $self->{'test'} = $nv;
+ }
+ return $self->{'test'};
+
+}
+
+#------------------------------------------------------------------------------------------
+
+=head2 verbose()
+
+Setzt bzw. gibt den Verbose-Level dieses Moduls zurueck.
+
+=cut
+
+sub verbose($;$) {
+
+ my $self = shift;
+ my $nv;
+ if ( @_ ) {
+ $nv = shift;
+ $nv = defined $nv ? ( $nv =~ /(\d+)/ ? $1 : 0 ) : 0;
+ $self->{'verbose'} = $nv;
+ }
+ return $self->{'verbose'};
+
+}
+
+#------------------------------------------------------------------------------------------
+
+1;
+
+__END__
+
+#------------------------------------------------------------------------------------------
+
+=head1 AUTHOR
+
+Frank Brehm <frank@brehm-online.com>
+
+=cut
+
+