=head1 SYNOPSIS
-B<logrotate.pl> [OPTIONS] config_file+
+ $lf = new LogRotate();
+
+ if ( $lf->check_state() ) {
+ $lf->rotate();
+
+ ...
+ }
=cut
#------------------------------------------------------------------------------------
+=head1 Funktionen
+
+Die Funktionen (Methoden) dieses Objekts.
+
+=head2 new( )
+
+Der Konstruktor dieses LogRotate-Objekts.
+
+=cut
+
sub new {
my $invocant = shift;
my $class = ref($invocant) || $invocant;
$res = bless $self, $class;
- my $p = verbose() > 2 ? __PACKAGE__ . "::new: " : "";
+ my $p = verbose() > 2 ? __PACKAGE__ . "::new(): " : "";
my $conf = new LogRotate::Conf();
}
+#------------------------------------------------------------------------------------------
+
+=head2 check_state( )
+
+Liest die Status-Datei ein und uebeprueft (wenn kein Testmodus) die Schreibfaehigkeit
+der Statusdatei.
+
+=cut
+
+sub check_state($) {
+
+ my $self = shift;
+ my $p = verbose() ? __PACKAGE__ . "::check_state(): " : "";
+
+ my $state_file = new LogRotate::StateFile(
+ 'test' => $self->{'test'},
+ );
+ $state_file->file($self->{'statusfile'});
+
+ my $states = $state_file->check();
+
+ if ( $states ) {
+ $self->{'state_file'} = $state_file;
+ $self->{'states'} = $states;
+ return 1;
+ }
+
+ warn $p . "Statusdatei '" . $self->{'statusfile'} . " ist nicht verwendungsfaehig.\n";
+ return undef;
+
+}
+
+#------------------------------------------------------------------------------------
+
+=head2 create_olddir ( $logfile )
+
+Checkt das Vorhandensein von $self-E<gt>{'c'}{'logfiles'}{$logfile}{'olddir'}, vorher werden aber die
+POSIX-Datumsersetzungen daran gemacht und in $self-E<gt>{'c'}{'logfiles'}{$logfile}{'olddir'}{'expanded'} zurückgespeichert.
+
+Wenn es kein Testfall ist, wird dieses Verzeichnis auch tatsächlich angelegt.
+
+Gibt den Erfolg als Wahrheitswert zurück.
+
+=cut
+
+sub create_olddir($$) {
+
+ my $self = shift;
+ my $file = shift;
+ my $p = verbose() ? __PACKAGE__ . "::create_olddir(): " : "";
+
+ my ( $dir, $adir, $mode, $owner, $group, $olddir );
+ my ( $pmode, $puid, $pgid );
+ my ( @Dirs, @Stats );
+ my $uid = $>;
+ my ( $gid ) = $) =~ /^(\d+)/;
+
+ unless ( $file ) {
+ carp( $p . "Keine Logdatei uebergeben!\n" );
+ return undef;
+ }
+ print $p . "Ueberpruefe und erstelle 'olddir' fuer Logdatei '$file'.\n" if verbose() > 1;
+ my $f = $self->{'c'}{'logfiles'}{$file};
+
+ print $p . Dumper( $f ) if verbose() > 2;
+
+ unless ( $f ) {
+ carp( $p . "Keine gueltige Logdatei uebergeben!\n" );
+ return undef;
+ }
+
+ my $o = $f->{'olddir'};
+
+ unless ( $o->{'dirname'} ) {
+ print $p . "Keine 'olddir'-Direktive fuer '$file' gegeben.\n" if verbose() > 1;
+ return 1;
+ }
+
+ $mode = $o->{'mode'} || $self->{'logfiles'}{'default'}{'olddir'}{'mode'} || 0755;
+ $owner = $o->{'owner'} || $self->{'logfiles'}{'default'}{'olddir'}{'owner'} || $uid;
+ $group = $o->{'group'} || $self->{'logfiles'}{'default'}{'olddir'}{'group'} || $gid;
+ unless ( $owner =~ /^\d+$/ ) {
+ $owner = getpwuid( $owner );
+ $owner = $uid unless defined $owner;
+ }
+ unless ( $group =~ /^\d+$/ ) {
+ $group = getgrnam( $group );
+ $group = $gid unless defined $group;
+ }
+
+ if ( $o->{'dirname'} =~ /%/ ) {
+ $o->{'dateformat'} = 1;
+ $olddir = POSIX::strftime( $o->{'dirname'}, localtime() );
+ } else {
+ $olddir = $o->{'dirname'};
+ }
+
+ unless ( $olddir =~ m#^/# ) {
+ ( $dir ) = $file =~ m#(.*)/[^/]*$#;
+ $olddir = $dir . "/" . $olddir;
+ }
+ $o->{'expanded'} = $olddir;
+ print $p . "Olddir ist jetzt: '$olddir'.\n" if verbose() > 1;
+
+ unless ( -d $olddir ) {
+
+ @Dirs = split m#/#, $olddir;
+ $adir = "";
+ @Stats = stat "/";
+ ( $pmode, $puid, $pgid ) = @Stats[2, 4, 5];
+ foreach $dir ( @Dirs ) {
+ next unless $dir;
+ next if $dir eq "/";
+ $adir .= "/" . $dir;
+ print " - ueberpruefe $adir\n" if verbose() > 2;
+ if ( -d $adir ) {
+ @Stats = stat $adir;
+ ( $pmode, $puid, $pgid ) = @Stats[2, 4, 5];
+ } else {
+ print $p . "Erstelle Verzeichnis $adir ...\n";
+ # Ermittlung effektive Permissions + Ownership
+ # wenn in config-file gegeben, diese,
+ # ansonsten die vom übergeordneten Verzeichnis.
+ $mode = defined $o->{'mode'} ? $o->{'mode'} : $pmode;
+ $owner = defined $o->{'owner'} ? $o->{'owner'} : $puid;
+ $group = defined $o->{'group'} ? $o->{'group'} : $pgid;
+ print " Permissions: $mode, Owner: $owner, Group: $group\n" if verbose() > 1;
+ unless ( $self->{'test'} ) {
+ print " mkdir $adir $mode\n" if verbose() > 1;
+ unless ( mkdir $adir, $mode ) {
+ warn $p . "$!\n";
+ return undef;
+ }
+ if ( $owner != $uid and $group != $gid ) {
+ print " chown $owner, $group, $adir\n" if verbose() > 1;
+ unless ( chown $owner, $group, $adir ) {
+ warn $p . "$!\n";
+ return undef;
+ }
+ }
+ }
+ }
+ }
+
+ }
+
+ return 1;
+}
+
+#------------------------------------------------------------------------------------
+
+=head2 find_rotatings( $file, $target )
+
+Findet an Hand des uebergebenen Logdatei-Namens und des Dateinamens der rotierten Datei
+alle zu rotierenden und umzubenennenden Dateinamen heraus.
+
+Wenn erfolgreich, wird eine Hash-Ref zurueckgegeben, die folgenden Aufbau hat:
+
+ $res = {
+ 'rotate' => {
+ 'from' => $file,
+ 'to' => $target
+ },
+ 'move' => [
+ ...
+ { 'from' => $file2,
+ 'to' => $file3,
+ },
+ { 'from' => $file1,
+ 'to' => $file2,
+ },
+ { 'from' => $file0,
+ 'to' => $file1,
+ },
+ ]
+ };
+
+Die Reihenfolge in der Attay-Ref $res->{'move'} ist die Reichenfolge, in der die
+Umbenennungen erfolgen muessen.
+
+=cut
+
+
+sub find_rotatings($$$) {
+
+ my $self = shift;
+ my $file = shift;
+ my $target = shift;
+ my $p = verbose() ? __PACKAGE__ . "::find_rotatings(): " : "";
+
+ unless ( $file ) {
+ carp $p . "Keine Logdatei uebergeben!\n";
+ return undef;
+ }
+
+ unless ( $target ) {
+ carp $p . "Kein Dateiname fuer rotierte Logdatei uebergeben!\n";
+ return undef;
+ }
+
+ print $p . "Ermittle alle Umbenennungen und Rotationen fuer Logdatei '$file' -> '$target'...\n" if verbose() > 1;
+ my $f = $self->{'c'}{'logfiles'}{$file};
+
+ my $ext = $f->{'extension'} || "";
+
+ my ( $i, $pair );
+
+ my $res = {
+ 'rotate' => {},
+ 'move' => []
+ };
+
+ unless ( -f $target ) {
+ $res->{'rotate'}{'from'} = $file;
+ $res->{'rotate'}{'to'} = $target;
+ return $res;
+ }
+
+
+
+ return $res;
+
+}
+
#------------------------------------------------------------------------------------
-=head2 read( $file )
+=head2 get_logfile_target ( $file )
+
+Legt das Ziel des Rotierens für das übergebene Logfile fest und gibt dieses zurück.
+
+=cut
+
+sub get_logfile_target($$) {
+
+ my $self = shift;
+ my $file = shift;
+ my $p = verbose() ? __PACKAGE__ . "::get_logfile_target(): " : "";
+
+ unless ( $file ) {
+ carp $p . "Keine Logdatei uebergeben!\n";
+ return undef;
+ }
+
+ print $p . "Ermittle Dateinamen der rotierten Logdatei '$file'...\n" if verbose() > 1;
+ my $f = $self->{'c'}{'logfiles'}{$file};
+
+ print $p . "Struktur von '$file': " . " " . Dumper( $f ) if verbose() > 2;
+
+ my ( $nr, $dir, $basename, $target, $pattern );
+
+ unless ( $f ) {
+ carp $p . "Keine gueltige Logdatei uebergeben!\n";
+ return undef;
+ }
+
+ unless ( ( $dir, $basename ) = $file =~ m#(.*)/([^/]*)$# ) {
+ warn $p . "Ungueltiger Logdateiname: '$file'\n";
+ return undef;
+ }
+
+ $basename = $f->{'olddir'}{'expanded'} ? $f->{'olddir'}{'expanded'} . "/" . $basename : $file;
+ if ( $f->{'dateext'} ) {
+ $pattern = $f->{'datepattern'};
+ print $p . "Verwende Datumsersetzung '$pattern' fuer '$file'.\n" if verbose() > 1;
+ $basename .= "." . $pattern;
+ $basename = POSIX::strftime( $basename, localtime() );
+ }
+
+ print $p . "Dateiname '$target' fuer rotierte Logdatei '$file' gefunden.\n" if verbose() > 1;
+ return $target;
+
+}
+
+#------------------------------------------------------------------------------------
+
+=head2 read_config( $file )
Liest die uebergebene Datei in die Konfiguration ein.
my $self = shift;
my $file = shift;
- my $p = verbose() ? __PACKAGE__ . "::read: " : "";
+ my $p = verbose() ? __PACKAGE__ . "::read_config(): " : "";
return $self->{'c'}->read($file);
}
-#------------------------------------------------------------------------------------------
+#------------------------------------------------------------------------------------
-=head2 check_state()
+=head2 rotate( )
-Liest die Status-Datei ein und uebeprueft (wenn kein Testmodus) die Schreibfaehigkeit
-der Statusdatei.
+Fuehrt das eigentliche Rotieren aus.
=cut
-sub check_state($) {
+sub rotate($) {
my $self = shift;
- my $p = verbose() ? __PACKAGE__ . "::check_state: " : "";
+ my $p = verbose() ? __PACKAGE__ . "::rotate(): " : "";
- my $state_file = new LogRotate::StateFile(
- 'test' => $self->{'test'},
- );
- $state_file->file($self->{'statusfile'});
+ die $p . "Noch keine Konfiguration eingelesen.\n\n" unless $self->{'c'};
- my $states = $state_file->check();
+ unless ( $self->{'c'}{'logfiles'} and scalar( keys %{$self->{'c'}{'logfiles'}} ) ) {
+ warn $p . "Keine Logdateien zum Rotieren gefunden.\n";
+ return undef;
+ }
- if ( $states ) {
- $self->{'state_file'} = $state_file;
- $self->{'states'} = $states;
+ my ( $file, $should_rotate, $firstscript, $prescript, $postscript, $lastscript, $sharedscripts );
+ my ( $cmd, $do_script, $name );
+
+ foreach $file ( sort { lc($a) cmp lc($b) } keys %{$self->{'c'}{'logfiles'}} ) {
+
+ print "\n" . $p . "Bearbeite Logdatei '$file' ...\n" if verbose() > 1;
+
+ $should_rotate = $self->test_for_rotate( $file );
+
+ unless( defined $should_rotate ) {
+ die $p . "Schwerer Fehler, breche hier ab.\n";
+ }
+
+ unless ( $should_rotate ) {
+ print $p . "Logdatei '$file' wird NICHT rotiert.\n" if verbose();
+ next;
+ }
+ print $p . "Logdatei '$file' wird rotiert.\n" if verbose();
+
+ $sharedscripts = $self->{'c'}{'logfiles'}{$file}{'sharedscripts'} || 0;
+ $firstscript = $self->{'c'}{'logfiles'}{$file}{'firstaction'};
+ $prescript = $self->{'c'}{'logfiles'}{$file}{'prerotate'};
+ $postscript = $self->{'c'}{'logfiles'}{$file}{'postrotate'};
+ $lastscript = $self->{'c'}{'logfiles'}{$file}{'lastaction'};
+
+ # Ausfuehren des Firtsaction-Scripts, falls es noch nicht ausgefuehrt wurde
+ if ( $firstscript ) {
+ print $p . "Schau nach, ob das Firstaction-Script ausgefuehrt werden soll ...\n" if verbose() > 2;
+ unless ( $self->{'c'}{'scripts'}{$firstscript}{'first'} ) {
+ $cmd = join( "\n", @{$self->{'c'}{'scripts'}{$firstscript}{'cmd'}} ) . "\n";
+ print $p . "Fuehre Firstaction-Script '$firstscript' aus:\n$cmd" if verbose();
+ system $cmd unless $self->{'test'};
+ }
+ $self->{'c'}{'scripts'}{$firstscript}{'first'} = 1;
+ }
+
+ # Ausfuehren des Prerotate-Scripts, falls es noch nicht ausgefuehrt wurde
+ # oder sharedscripts nicht gesetzt ist
+ if ( $prescript ) {
+ print $p . "Schau nach, ob das Prerotate-Script ausgefuehrt werden soll ...\n" if verbose() > 2;
+ unless ( $self->{'c'}{'scripts'}{$prescript}{'prerun'} and $sharedscripts ) {
+ $cmd = join( "\n", @{$self->{'c'}{'scripts'}{$prescript}{'cmd'}} ) . "\n";
+ print $p . "Fuehre Firstaction-Script '$prescript' aus:\n$cmd" if verbose();
+ system $cmd unless $self->{'test'};
+ }
+ $self->{'c'}{'scripts'}{$prescript}{'prerun'} = 1;
+ }
+
+ #####
+ # Hier jetzt das eigentliche Rotieren ....
+ #####
+
+ unless ( $self->create_olddir( $file ) ) {
+ next;
+ }
+
+ # Ausfuehren des Postrotate-Scripts, falls es die letzte Rotation ist, fuer die
+ # dieses Script gilt, oder sharedscripts nicht gesetzt ist
+ if ( $postscript ) {
+ print $p . "Schau nach, ob das Postrotate-Script ausgefuehrt werden soll ...\n" if verbose() > 2;
+ $do_script = 0;
+ $self->{'c'}{'scripts'}{$postscript}{'post'}--;
+ $self->{'c'}{'scripts'}{$postscript}{'dopost'} = 1;
+ if ( $sharedscripts ) {
+ $do_script = 0;
+ } else {
+ $do_script = 1 if $self->{'c'}{'scripts'}{$postscript}{'post'} == 0;
+ }
+ if ( $do_script ) {
+ $self->{'c'}{'scripts'}{$postscript}{'donepost'} = 1;
+ $cmd = join( "\n", @{$self->{'c'}{'scripts'}{$postscript}{'cmd'}} ) . "\n";
+ print $p . "Fuehre Postrotate-Script '$postscript' aus:\n$cmd" if verbose();
+ system $cmd unless $self->{'test'};
+ }
+ }
+
+ # Ausfuehren des Lastaction-Scripts, falls es die letzte Rotation ist, fuer die
+ # dieses Script gilt, oder sharedscripts nicht gesetzt ist
+ if ( $lastscript ) {
+ print $p . "Schau nach, ob das Lastaction-Script ausgefuehrt werden soll ...\n" if verbose() > 2;
+ $do_script = 0;
+ $self->{'c'}{'scripts'}{$lastscript}{'last'}--;
+ $self->{'c'}{'scripts'}{$lastscript}{'dolast'} = 1;
+ $do_script = 1 if $self->{'c'}{'scripts'}{$lastscript}{'last'} == 0;
+ if ( $do_script ) {
+ $self->{'c'}{'scripts'}{$lastscript}{'donelast'} = 1;
+ $cmd = join( "\n", @{$self->{'c'}{'scripts'}{$lastscript}{'cmd'}} ) . "\n";
+ print $p . "Fuehre Lastaction-Script '$lastscript' aus:\n$cmd" if verbose();
+ system $cmd unless $self->{'test'};
+ }
+ }
+
+ }
+
+ # Checke nach uebriggebliebenen Postrotate-Scripts ...
+ print "\n$p" . "Checke nach uebriggebliebenen Postrotate-Scripts ...\n" if verbose();
+ foreach $name ( keys %{$self->{'c'}{'scripts'}} ) {
+ if ( $self->{'c'}{'scripts'}{$name}{'dopost'} and not $self->{'c'}{'scripts'}{$name}{'donepost'} ) {
+ $cmd = join( "\n", @{$self->{'c'}{'scripts'}{$name}{'cmd'}} ) . "\n";
+ print $p . "Fuehre Postrotate-Script '$name' aus:\n$cmd" if verbose();
+ system $cmd unless $self->{'test'};
+ }
+ }
+
+ # Checke nach uebriggebliebenen Lastaction-Scripts ...
+ print "\n$p" . "Checke nach uebriggebliebenen Lastaction-Scripts ...\n" if verbose();
+ foreach $name ( keys %{$self->{'c'}{'scripts'}} ) {
+ if ( $self->{'c'}{'scripts'}{$name}{'dolast'} and not $self->{'c'}{'scripts'}{$name}{'donelast'} ) {
+ $cmd = join( "\n", @{$self->{'c'}{'scripts'}{$name}{'cmd'}} ) . "\n";
+ print $p . "Fuehre Lastaction-Script '$name' aus:\n$cmd" if verbose();
+ system $cmd unless $self->{'test'};
+ }
+ }
+
+ return 1;
+
+}
+
+#------------------------------------------------------------------------------------
+
+=head2 rotate_file ( $file )
+
+Rotiert (bedingungslos) die in $file übergebene Datei.
+
+Hinterher steht in $self->{'c'}{'logfiles'}{$file}{'rotated'} der Dateiname
+der rotierten Datei und in %{$self->{'c'}{'logfiles'}{$file}{'oldfiles'}} ist
+eine Liste mit allen bisherigen Logdateien (als Keys) mit einem Zeitstempel
+ihres letzten Aenderungsdatums (als Wert).
+
+=cut
+
+sub rotate_file($$) {
+
+ my $self = shift;
+ my $file = shift;
+ my $p = verbose() ? __PACKAGE__ . "::rotate_file(): " : "";
+
+ #my ( $dir, $adir, $mode, $owner, $group, $olddir );
+ #my ( $pmode, $puid, $pgid );
+ #my ( @Dirs, @Stats );
+
+ my ( $target, $rotates );
+
+ my $uid = $>;
+ my ( $gid ) = $) =~ /^(\d+)/;
+
+ unless ( $file ) {
+ carp( $p . "Keine Logdatei uebergeben!\n" );
+ return undef;
+ }
+ print $p . "Rotiere Logdatei '$file'.\n" if verbose();
+ my $f = $self->{'c'}{'logfiles'}{$file};
+
+ print $p . Dumper( $f ) if verbose() > 2;
+
+ unless ( $f ) {
+ carp( $p . "Keine gueltige Logdatei uebergeben!\n" );
+ return undef;
+ }
+
+ unless ( $target = $self->get_logfile_target($file) ) {
+ carp( $p . "Kein gueltigen Dateinamen fuer Rotation gefunden!\n" );
+ return undef;
+ }
+
+ unless ( $rotates = $self->find_rotatings($file, $target) ) {
+ carp( $p . "Keine gueltigen Moves fuer Rotation gefunden!\n" );
+ return undef;
+ }
+
+ print $p . "Ermittelte Moves: " . Dumper($res) if verbose() > 2;
+
+
+
+
+ return 1;
+
+}
+
+#------------------------------------------------------------------------------------
+
+=head2 test_for_rotate( $file )
+
+Ueberprueft, ob die uebergebene Datei ueberhaupt rotiert werden muss.
+
+Rueckgabe:
+
+=over 4
+
+=item 1
+
+Es wird rotiert
+
+=item 0
+
+Es wird nicht rotiert
+
+=item undef
+
+Schwerer Fehler, das Programm sollte besser abgebrochen werden.
+
+=back
+
+=cut
+
+sub test_for_rotate($$) {
+
+ my $self = shift;
+ my $file = shift;
+ my $p = verbose() ? __PACKAGE__ . "::test_for_rotate(): " : "";
+
+ my ( $text, $f_size, $maxsize, $last_updated, $time_next_rotate );
+
+ unless ( $file ) {
+ warn $p . "Keine Datei uebergeben beim Aufruf.\n";
+ return undef;
+ }
+
+ print $p . "Ueberpruefe Logdatei '$file', ob sie rotiert werden soll...\n" if verbose() > 2;
+
+ unless ( -f $file ) {
+ $text = $p . "Logdatei '$file' existiert NICHT, keine Rotation.\n";
+ unless ( $self->{'c'}{'logfiles'}{$file}{'missingok'} ) {
+ warn $text;
+ return 0;
+ }
+ print $text if verbose() > 1;
+ return 0;
+ }
+
+ unless ( -s $file ) {
+ $text = $p . "Logdatei '$file' hat eine Dateigroesse von 0, keine Rotation.\n";
+ unless ( $self->{'c'}{'logfiles'}{$file}{'ifempty'} ) {
+ print $text if verbose() > 1;
+ return 0;
+ }
+ }
+
+ if ( $self->{'force'} ) {
+ print $p . "Logdatei '$file' wird rotiert, da FORCE-Mode eingeschaltet wird.\n" if verbose() > 1;
return 1;
}
- warn $p . "Statusdatei '" . $self->{'statusfile'} . " ist nicht verwendungsfaehig.\n";
- return undef;
+ $f_size = ( -s $file );
+ $maxsize = $self->{'c'}{'logfiles'}{$file}{'size'} || 0;
+ $last_updated = $self->{'states'}{$file} || 0;
+ $time_next_rotate = $last_updated ? ( $last_updated + ($self->{'c'}{'logfiles'}{$file}{'period'} * 24 * 60 * 60) ) : 0;
+
+ if ( $maxsize ) {
+ print $p . "Vergleiche Dateigroesse $f_size mit Maximalgroesse $maxsize ...\n" if verbose > 2;
+ return 0 if $maxsize > $f_size;
+ }
+
+ printf( $p . "Vergleiche Timestamp naechste Rotation %s mit aktuellem Timestamp %s ...\n", $time_next_rotate, time() ) if verbose > 2;
+ if ( verbose() > 3 ) {
+ printf( " Periode %.2f Tage.\n", $self->{'c'}{'logfiles'}{$file}{'period'} );
+ printf( " Letzte Rotation %s\n", $last_updated ? (scalar(localtime($last_updated)) . " GMT") : "<nie>" );
+ printf( " Naechste Rotation %s\n", $time_next_rotate ? (scalar(localtime($time_next_rotate)) . " GMT") : "<sofort>" );
+ printf( " Aktuelle Zeit %s\n", scalar(localtime()) . " GMT" );
+ }
+ return 0 if $time_next_rotate > time();
+
+ return 1;
}