#!/usr/bin/perl -wT

# Rotates apache log files while apache is running.
# Must be run as root (to send signals to httpd father process),
#   except when run in test mode (-t).
# It makes sense to call it by cron.

# SYNTAX

# apachelogrotate.pl [-s|-v] [-d|-m|-y] [-t] [-c] [-lXX]
#                    [-rPATH] [conffile [conffile] ...] 
#  -s     =  silent (only fatal errors will be reported)
#  -v     =  verbose (excludes -s)
#  -d     =  rotate every day
#  -m     =  rotate every month
#  -y     =  rotate every year
#  -lXX   =  rotate if bigger than XX MBs (default: 10)
#  -rPATH =  set default ServerRoot to PATH
#  -t     =  just test (don't change anything)
#  -c     =  do the whole thing re-writing the config files
#            (adds some safety, but will need more time)

# The files given as arguments will be considered apache 
# configuration files and (recursively) scanned for log file
# definitions.

# If no file is given (which is recommended), the script will 
# try to get this information from the running httpd processes.
# If this fails, /etc/httpd/httpd.conf will be taken as default
# configuration file.

# The switches -d, -m and -y will cause the log files to be
# rotated the first time this script is called in the given
# period (if they are older than the start of the period).
# -d includes -m, and -m includes -y.
# If you use one of these parameters, it is reasonable to call
# the script by a cron job a short time after the beginning of
# the given period.

# If you use a logfile analyser, this script should be called
# rather seldom (e.g. once in a month) and immediately when the
# analyser has finished its job, preferably at a time with low
# traffic on the web server.

# Note: A log file will never be rotated twice a day
# (unless you remove the first gzipped log file of that day)
# and an empty file will not be rotated.

# INSTALL

# To install this script simply copy it into any directory
# (which preferably is only root writable and obviously not
# world writable), 'cd' in that directory and do a
# 'chmod 755 apachelogrotate.pl'.

# Then become a normal user and do the following steps to test
# and run the script, repeating each step with the necessary 
# adjustments if you get errors (try -v to identify the errors
# better):
# 1. run it as not privileged user with parameters -t (and other
#    parameters and/or arguments if needed; cf. syntax section)
# 2. run it as user root with parameter -t
# 3. run it as user root without parameter -t
# 4. make it a cron job ("man 5 crontab") (consider using "-s")

# If the script does not run at all, check your perl interpreter
# and/or adjust the very first line of this script

# Don't be surprised if this script seems to "hang". It has
# some longer "sleep" periods to ensure that all running apache
# processes actually point to the new log files. (Maximum time
# the script needs is about 1+$CNT/6 minutes, with switch -c
# twice this time.) 

# If you notice (e.g. on a server with heavy load) that the
# script complaints about not being able to terminate all old
# httpd (apache) processes you may set $CNT to a higher number
# and use the switch -v to check whether the script actually 
# succeeds to restart at least some processes. If that's the
# case, the problem should be resolved by increasing $CNT and/or
# running the script at a time with less traffic.

# AUTHOR, LICENSE, VERSION

# Author: Hatto von Hatzfeld 2003 (hatto@salesianer.de)

# There is no warranty that this file serves any purpose.
# Nevertheless it may be used under the GNU public license;
# but do it for your own risk. It is a beta version!

# Version: 0.1.5 (beta)
# Date: 2003-04-01

# CHANGES:
# v. 0.1.5
#     o  changed a few things for use under Solaris (see 
#        configuration section) [Thanks to Franky Van Liedekerke]
#     o  fixed a bug in interpretation of ps output which caused
#        script abort with some versions of apache
# v. 0.1.4
#     o  fixed a bug which hindered identification of config
#           files used by running apache processes
#     o  improved identification of apache default config file
# v. 0.1.3
#     o  fixed a bug in use of relatives paths
#     o  added verbose switch (-v), reduced output (when called 
#           without -v and -s)
#     o  deleted bogus "-" in call of command ps
#     o  added option -r to set ServerRoot
#     o  added option -c, changing normal operation to work 
#           without re-write of conf files
#     o  added a warning when encountering symlinks
#     o  code cleanup and enlarged configuration section
#     o  added a check to prevent multiple instances of script
# v. 0.1.2
#     o  enabled relative paths in Apache's Include and log file
#           directives (broken!)
#     o  enabled directories in Include directive (allowed since 
#           apache 1.3.13)
#     o  fixed a bug in parsing of CustomLog directive
#     o  added $FILECHARS in configuration section
# v. 0.1.1
#     first really running version

# BUGS:
#     o  default apache log files (logs/error_log and logs/access_log)
#           are ignored (unless configured in a conf file)
#     o  symlinks are not supported
#     o  others which I don't know yet (please let me know, if you
#           find some, but please include output created by use of -v)

# CONFIGURATION SECTION
# make changes only if necessary; command line argument should be sufficient
# please give complete paths (i.e. with leading slash) without final slash

$MAXMB=10; # longer files will be rotated without regarding their age
$SLEEP=30; # safety time between apache restart and manipulation of log files
$PS="/bin/ps;/usr/bin/ps"; # possible paths of command ps (separated by ";")
$GZIP="/bin/gzip;/usr/bin/gzip"; # possible paths of command gzip (separated by ";")
$SERVERROOT="/usr/local/apache"; # default server root (apache compiled-in)
$DEFAULTCONF="/etc/httpd.conf;/etc/httpd/httpd.conf;$SERVERROOT/conf/httpd.conf"; # default config file paths
$CNT=30; # how many loops (each needs 10 seconds) to wait for apache children to exit

# change the following parameters only if you really know what you are doing!

$PSPAR="aux"; # Parameters when calling command ps (should normally be "aux")
$PSusr=0; $PSpid=1; $PScmd=10; # columns in output of "ps -aux"
# $PSPAR="-e -o user,pid,comm"; # to use with solaris
# $PSusr=0; $PSpid=1; $PScmd=2; # to use with solaris 
$INCLUDESTR="Include";
$LOGFILESTR="ErrorLog|CustomLog|TransferLog";
$SERVERROOTSTR="ServerRoot";
$FILECHARS='[a-zA-Z0-9_\/\.\-]'; # allowed chars in file names and paths (for use in RegEx)

# END OF CONFIGURATION SECTION


$ENV{'PATH'} = '/bin:/usr/bin:/usr/local/bin';

# read parameters
%SWITCHES=(); 
@CFILES=(); # conf files
%CF=(); # conf file names found
$FilesGiven=0;
while($#ARGV>=0) {
  if($ARGV[0]=~/^\-r[\"\']?($FILECHARS+)[\"\']?$/oi) { 
    $SERVERROOT=$1;
    if($SERVERROOT=~/^($FILECHARS+)\/$/o) { $SERVERROOT=$1; }
  }
  elsif($ARGV[0]=~/^\-l([\d\.]+)$/i) { $MAXMB=$1; }
  elsif($ARGV[0]=~/^\-([cdmstvy])$/i) { $SWITCHES{lc $1}=1; }
  elsif($ARGV[0]=~/^\-/i) { die("Unknown switch/argument ".$ARGV[0]."\n"); }
  elsif($ARGV[0]=~/^[\"\']?($FILECHARS+)[\"\']?$/o) { 
    $zwi=$1;
    if($zwi!~/^\//) { 
      $zwi=$SERVERROOT.'/'.$zwi;
      $zwi=~s/\/\.\//\//g; $zwi=~s/\/[^\/]+\/\.\.\//\//g;
    }
    $CFILES[$#CFILES+1]=$zwi; $FilesGiven++;
  }
  else { die("unknown argument ".$ARGV[0]."\n"); }
  shift @ARGV;
}
$tdef='n'; # time period
if(defined($SWITCHES{'d'})) { $tdef='d'; }
elsif(defined($SWITCHES{'m'})) { $tdef='m'; }
elsif(defined($SWITCHES{'y'})) { $tdef='y'; }

$VL=1; # verbosity level (0=silent)
if(defined($SWITCHES{'s'})) { $VL=0; }
if(defined($SWITCHES{'v'})) { 
  if(defined($SWITCHES{'s'})) { print STDERR "Switch -s superseded by -v\n"; }
  $VL=2;
}

# some system and configuration checks
if($PS=~/;/) {
  @ZWI=split(/\s*;\s*/,$PS);
  foreach $zwi (@ZWI) { if(-e $zwi) { $PS=$zwi; last; } }
  if($PS=~/;/ || !-e $PS) { die("Cannot find command PS in '$PS'! - Program aborted.\n"); }
  if($VL>1) { print "Found command ps ($PS)\n"; }
}
if($GZIP=~/;/) {
  @ZWI=split(/\s*;\s*/,$GZIP);
  foreach $zwi (@ZWI) { if(-e $zwi) { $GZIP=$zwi; last; } }
  if($GZIP=~/;/ || !-e $GZIP) { die("Cannot find command GZIP in '$GZIP'! - Program aborted.\n"); }
  if($VL>1) { print "Found command gzip ($GZIP)\n"; }
}
if(!(-e $PS && -x $PS)) { die("Cannot start $PS! - Program aborted.\n"); }
if(!(-e $GZIP && -x $GZIP)) { die("Cannot start $GZIP! - Program aborted.\n"); }

# check user
if(!(defined($SWITCHES{'t'}) || $< == 0)) {
  die("This script must be run as root or with parameter -t (or both)!\n");
}

# make suffix for logfiles
(undef,undef,undef,$mday,$mon,$year,undef,$yday,undef) = localtime(time);
$daystamp=sprintf('%04u%02u%02u',($year>2000?$year:$year+1900),$mon+1,$mday);

# first check of running apache processes
$httpdnotroot=0;
if($VL>1) { print "Looking for httpd processes...\n"; }
%HTTPD2PID=(); %HTTPDROOT=();
open(PSIN,$PS." ".$PSPAR." |") || die("Error when running $PS! - Script aborted\n");
while(defined($IL=<PSIN>)) {
  chomp $IL;
  $IL =~ s/^\s+//g; # delete leading spaces
  @PSIL=split(/\s+/,$IL);
  if($PSIL[$PScmd]=~/[\/\[]httpd\]?$/) {
    if($PSIL[$PSpid]!~/^\d+$/) { 
      die("FATAL ERROR: Could not find PID in output from ps -aux!\n".
          "Check output of '$PS $PSPAR | grep httpd' and\n".
          "adjust \$PSusr, \$PSpid and \$PScmd in configuration section!\n");
    }
    $HTTPD2PID{$PSIL[$PSpid]}=$PSIL[$PSusr];
    if($VL>1) { 
      print $PSIL[$PSpid];
    }
    if($PSIL[$PSusr] eq "root") { 
      $HTTPDROOT{$PSIL[$PSpid]}=0;
      if($VL>1) {
        print " running as root";
      }
      $i=$PScmd+1;
      while($i<$#PSIL) {
        if($PSIL[$i] eq '-f') {
          $zwi=$PSIL[$i+1];
          if($VL>1) {
            print " with conf file ".$zwi;
          }
          if($FilesGiven==0 && !defined($CF{$zwi})) {
            $CF{$zwi}=$zwi;
            $CFILES[$#CFILES+1]=$zwi;
            if($VL>1) {
              print ", added to list of conf files";
            }
          }
        }
        $i++;
      }
    }
    else { $httpdnotroot++; }
    if($VL>1) { print "\n"; }
  }
  elsif($PSIL[$PScmd]=~/\/perl/ && $IL=~/apachelogrotate/) {
    if($PSIL[$PSpid] != $$) {
      die("Found other running apachelogrotate with PID ".$PSIL[$PSpid]." - Script aborted!\n");
    }
  }
}
if($VL>0) {
  if(defined((keys %HTTPDROOT)[1])) {
    print STDERR "WARNING: Multiple httpd root processes running!\n".
        "This may cause some trouble later!\n";
  }
}
close(PSIN);
if(!defined((keys %HTTPDROOT)[0])) { 
  die("FATAL ERROR: Could not find any running httpd root process!\n".
      "Check by 'ps -aux | grep httpd' for apache processes and/or\n".
      "adjust \$PSusr, \$PSpid and \$PScmd in configuration section!\n");
}
if($httpdnotroot==0) { 
  die("FATAL ERROR: httpd running only as user root (not wwwrun) - Script aborted\n");
}

# define cleanup on abort/die etc.
sub cleanup {
  print STDERR "Program will be aborted!\n";
  if(defined($_[0])) { print STDERR "Reason: ".$_[0]."\n"; }
  if(defined((keys %RENAMED)[0])) {
    if(defined($SWITCHES{'c'})) {  # If re-writing of conf files has been chosen
      if(defined((keys %RENAMED)[0])) { 
        foreach $ccfile (keys %RENAMED) {
          if(-e $ccfile && -e $RENAMED{$ccfile}) { 
            print STDERR "ABORT WARNING! Both logfiles $ccfile and ".
                 $RENAMED{$ccfile}." exist!\n";
          }
          else {
            print STDERR "ABORT WARNING! Please check logfiles $ccfile and ".
                 $RENAMED{$ccfile}."\n";
          }
        }
        print STDERR "After this abort you should check and restart apache manually!\n";
      }
    }
    else {
      print STDERR "Cleaning up before abort...\n";
      foreach $ccfile (keys %RENAMED) {
        if(-e $RENAMED{$ccfile}) {
          if(-e $ccfile) { unlink $ccfile; }
          rename $RENAMED{$ccfile},$ccfile; 
        }
        if(!-e $ccfile) {
          print STDERR "WARNING! File $ccfile disappeared! Apache will probably crash on restart!\n";
        }
      }
    }
  }
  exit 1;
}
$SIG{__DIE__} = 'cleanup';
$SIG{'TERM'} = 'cleanup';
$SIG{'QUIT'} = 'cleanup';
$SIG{'INT'} = 'cleanup';
$SIG{'HUP'} = 'cleanup';
$SIG{'USR1'} = sub { print STDERR "Got signal USR1\n"; };
$SIG{'USR2'} = sub { print STDERR "Got signal USR2\n"; };

# read conf files and get log file names
%CFM=(); # conf files to modify
%LF=(); # log file names found
%LFILES=(); # log files to rotate
if($#CFILES<0) { # assure that there is a conf file entry
  if($DEFAULTCONF=~/;/) {
    @ZWI=split(/\s*;\s*/,$DEFAULTCONF);
    foreach $zwi (@ZWI) { if(-e $zwi) { $DEFAULTCONF=$zwi; last; } }
    if($DEFAULTCONF=~/;/ || !-e $DEFAULTCONF) {
      die("Could not find conf file(s). Please give them as arguments to this script!\n");
    }
    if($VL>0) { print "Found conf file $DEFAULTCONF (just a guess - please check that!)\n"; }
  }
  if(!-e $DEFAULTCONF) {
    die("Could not find conf file(s). Please give them as arguments to this script!\n");
  }
  $CFILES[0]=$DEFAULTCONF;
}

$SRAct=0; # whether chdir tried and succeeded
if(chdir($SERVERROOT)) { $SRAct=1; }

# main loop reading conf files
foreach $cfile (@CFILES) {
  if(-d $cfile) { # whole directory as conf file
    opendir(DIRHANDLE,"$cfile") || die("Could not open directory $cfile!\n");
    @dircontents=readdir(DIRHANDLE);
    close(DIRHANDLE);
    if($VL>1) { print "Checking dir $cfile for conf files\n"; }
    foreach $dir(@dircontents) {
      if($dir!~/^\.\.?$/ && !defined($CF{$cfile.'/'.$dir})) { 
        $CF{$cfile.'/'.$dir}=1; $CFILES[$#CFILES+1]=$cfile.'/'.$dir;
      }
    }
    next;
  }
  if($VL>0) { print "Reading conf file $cfile\n"; }
  open(CFILE,$cfile) || die("Could not open $cfile\n");
  flock(CFILE,1);
  while(defined($il=<CFILE>)) {
    # Check for ServerRoot definition
    if($il=~/^\s*$SERVERROOTSTR\s+[\"\']?([^\s\"\']+)[\"\']?\s*$/o) {
      $zwi=$1;
      if($VL>1) { print "Found ServerRoot definition: $zwi\n"; }
      if($zwi=~/^($FILECHARS+)\/$/) { $zwi=$1; }
      if($zwi!~/^\//) {
        print "WARNING: Relative path as server root (does not make much sense)\n";
        $zwi=$SERVERROOT.'/'.$zwi;
        $zwi=~s/\/\.\//\//g; $zwi=~s/\/[^\/]+\/\.\.\//\//g; $zwi=~s/\/\//\//g;
      }
      $SERVERROOT=$zwi;
      if(chdir($SERVERROOT)) {
        if($VL>1) { print "Successfully made a chdir to $SERVERROOT\n"; }
        $SRAct=1;
      }
      else {
        print "WARNING: chdir to $SERVERROOT failed - this MAY cause troubles!\n";
        $SRAct=0;
      }
    }
    # check for included conf files
    elsif($il=~/^\s*($INCLUDESTR)\s+[\"\']?([^\s]+)[\"\']?\s*$/o) {
      $zwi=$2;
      if ($zwi=~/^$FILECHARS+$/) {
        if($zwi=~/^($FILECHARS+)\/$/) { $zwi=$1; }
        if($zwi!~/^\//) {
          $zwi=$SERVERROOT.'/'.$zwi;
          $zwi=~s/\/\.\//\//g; $zwi=~s/\/[^\/]+\/\.\.\//\//g; $zwi=~s/\/\//\//g;
        }
        if(-e $zwi) {
          if(-l $zwi) {
            print "Conf file $zwi is a symlink\n*** symlinks are not supported ".
             "and may cause undesired results - YOU HAVE BEEN WARNED ***\n";
          }
          if(!defined($CF{$zwi})) { $CF{$zwi}=1; $CFILES[$#CFILES+1]=$zwi; }
        }
        else { print STDERR "Conf File $zwi does not exist and has been skipped!\n"; }
      }
      else { print STDERR "Filename ".$zwi." contained illegal chars and has been skipped!\n"; }
    }
    # check for log file names
    elsif($il=~/^\s*($LOGFILESTR)\s+([^\s]+)[\"\']?(\s+\S.*|)[\"\']?\s*$/o) {
      $zwi=$2;
      if ($zwi=~/^$FILECHARS+$/) {
        if($zwi!~/^\//) { 
          $zwi=$SERVERROOT.'/'.$zwi;
          $zwi=~s/\/\.\//\//g; $zwi=~s/\/[^\/]+\/\.\.\//\//g; $zwi=~s/\/\//\//g;
        }
        if($VL>1) { print "Found logfile entry $zwi\n"; }
        if(defined($LFILES{$zwi})) {
          $CFM{$cfile}=1; # this conf file has to be modified
          if($VL>1) {
            print "Logfile $zwi already marked as to be rotated\n";
          }
        }
        elsif(defined($LF{$zwi})) {
          if($VL>1) {
            print "Logfile $zwi already checked, doesn't need to be rotated\n";
          }
        }
        elsif(-e $zwi && -l $zwi) {
          if($VL>0) {
            print "WARNING: Logfile $zwi is a symlink and will be skipped!\n";
          }
          $LF{$zwi}=1; 
        }
        elsif(-e $zwi) {
          if(!(-e $zwi."-".$daystamp || -e $zwi."-".$daystamp.".gz")) {
            $LF{$zwi}=1; 
            # check log file
            @fstat=stat($zwi); $fsize=$fstat[7]; $fctime=$fstat[10]; 
            if($fsize>$MAXMB*1024*1024) {
              $LFILES{$zwi}=$zwi;
              if($VL>0) {
                print "Logfile ".$zwi." has ".$fsize." bytes and must be rotated\n";
              }
              $CFM{$cfile}=1; # this conf file has to be modified
            }
            elsif($tdef ne 'n' && $fsize>0) { # time check
              if($VL>1) {
                print "Logfile ".$zwi." has ".$fsize." bytes (<$MAXMB MB); ".
                      "checking time...\n";
              }
              (undef,undef,undef,$fmday,$fmon,$fyear,undef,$fyday,undef) = localtime($fctime);
              if(($tdef eq 'd' && ($yday > $fyday || $year > $fyear)) ||
              ($tdef eq 'm' && ($mon > $fmon || $year > $fyear)) ||
              ($tdef eq 'y' && $year > $fyear)) {
                $LFILES{$zwi}=$zwi;
                if($VL>1) {
                  print "Logfile $zwi has reached time limit (inode change ".
                    sprintf('%04u-%02u-%02u',($fyear>2000?$fyear:$fyear+1900),$fmon+1,$fmday).
                    ") and must be rotated\n";
                }
                elsif($VL>0) {
                  print "Logfile ".$zwi." is too old and must be rotated\n";
                }
                $CFM{$cfile}=1; # this conf file has to be modified
              }
              elsif($VL>1) {
                print "Logfile $zwi has not reached time limit (inode change ".
                  sprintf('%04u-%02u-%02u',($fyear>2000?$fyear:$fyear+1900),$fmon+1,$fmday).
                  ")\n";
              }
            }
            elsif($VL>1) {
              print "Logfile ".$zwi." has ".$fsize." bytes (less than $MAXMB MB; ".
                "no rotation needed)\n";
            }
          }
          elsif($VL>0) { 
            print "Logfile $zwi cannot be rotated. File $zwi-$daystamp(.gz) exists.\n";
          }
        }
        elsif($VL>0) {
          print "Logfile $zwi not found (entry ignored)\n";
        }
      }
      else { 
        print STDERR "WARNING! Logfile ".$zwi." skipped because of illegal chars in name!\n";
      }
    }
  }
  flock(CFILE,8);
  close(CFILE);
}
if(!defined((keys %LFILES)[0])) { 
  if($VL>0) {
    print "Did not find any log files to rotate.\n";
  }
  if(defined($SWITCHES{'t'})) {
    print "Program finished regularly in test mode.\n";
  }
  elsif($VL>0) {
    print "Program finished regularly.\n";
  }
  exit 0;
}

if(defined($SWITCHES{'c'})) {  # If re-writing of conf files has been chosen

  # modify conf files
  foreach $cfile (keys %CFM) {
    $cfilen=$cfile."-rotatebak";
    if(!defined($SWITCHES{'t'})) {
      if(rename($cfile,$cfilen)) { 
        if($VL>1) { print "File $cfile renamed to $cfilen\n"; }
        $RENAMED{$cfile}=$cfilen;
      }
      else { die "Could not rename $cfile to $cfilen! - Script aborted\n"; }
      open(CFILEN,$cfilen) || die("Could not read $cfilen - Script aborted\n");
      open(CFILE,">".$cfile) || die("Could not create new $cfile - Script aborted\n");
      flock(CFILEN,1);
      flock(CFILE,2);
      while(defined($il=<CFILEN>)) {
        if($il=~/^(\s*$LOGFILESTR\s+)([^\s]+)(\s+\w+|)\s*$/o && defined($LFILES{$2})) {
          print CFILE $1.$2."-rot.tmp".$3."\n";
          if($VL>1) {
            print "Output to logfile $2 temporarily redirected to $2-rot.tmp\n";
          }
        }
        else { print CFILE $il; }
      }
      close(CFILE);
      close(CFILEN);
      if($VL>1) { print "Created temporary version of $cfile\n"; }
    }
    else {
      print "Test mode: skipped backup and modification of conf file $cfile\n";
    }
  }

  # check running apache processes
  $httpdnotroot=0;
  if($VL>1) { print "Looking for httpd processes: "; }
  %HTTPD2PID=(); %HTTPDROOT=();
  open(PSIN,$PS." ".$PSPAR." |") || die("Error when running $PS! - Script aborted\n");
  while(defined($IL=<PSIN>)) {
    chomp $IL;
    $IL =~ s/^\s+//g; # delete leading spaces
    @PSIL=split(/\s+/,$IL);
    if($PSIL[$PScmd]=~/[\/\[]httpd\]?$/) {
      $HTTPD2PID{$PSIL[$PSpid]}=$PSIL[$PSusr];
      if($PSIL[$PSusr] eq "root") { $HTTPDROOT{$PSIL[$PSpid]}=0; }
      else { $httpdnotroot++; }
      if($VL>1) { 
        print $PSIL[$PSpid].($PSIL[$PSusr] eq 'root' ? '! ' : ' ');
      }
    }
  }
  if($VL>1) {
    print "\n";
    if(defined((keys %HTTPDROOT)[1])) {
      print STDERR "WARNING: Multiple httpd root processes running!\n".
          "This may cause some trouble later!\n";
    }
  }
  close(PSIN);
  if(!defined((keys %HTTPDROOT)[0])) { 
    die("FATAL ERROR: Could not find any running httpd root process! - Script aborted\n");
  }
  if($httpdnotroot==0) { 
    die("FATAL ERROR: httpd running only as user root (not wwwrun) - Script aborted\n");
  }
  if($VL>0) { print "Found $httpdnotroot not-root httpd process(es). Attempting restart now.\n"; }

  # graceful restart of apache
  if($< == 0) { # if user root
    $cnt=0; $stillalive=$httpdnotroot;
    foreach $pid (keys %HTTPDROOT) { 
      kill 10, $pid;
      if($VL>1) { print "Signal USR1 has been sent to process $pid\n"; }
    }
    while($cnt<$CNT && $stillalive != 0) {
      $stillalive=0; $httpdprocs=0;
      sleep 10;
      # control success
      open(PSIN,$PS." ".$PSPAR." |") || die("Error when running $PS! - Script aborted\n");
      while(defined($IL=<PSIN>)) {
        chomp $IL;
        $IL =~ s/^\s+//g; # delete leading spaces
        @PSIL=split(/\s+/,$IL);
        if($PSIL[$PScmd]=~/[\/\[]httpd\]?$/) {
          $httpdprocs++;
          if(defined($HTTPD2PID{$PSIL[$PSpid]}) && 
                !defined($HTTPDROOT{$PSIL[$PSpid]})) { $stillalive+=1; }
        }
      }
      close(PSIN);
      if($VL>1 && $stillalive>0) { print "$stillalive 'old' processes are still alive\n"; }
      $cnt++; 
    }
    if($cnt>=$CNT) {
      die("Unable to terminate all intermediate httpd processes in the given time!\n");
    }
    if($httpdprocs==0) { 
      if(defined($SWITCHES{'t'})) {
        die("!!!ALERT!!! httpd processes died when re-reading old conf file(s)!");
      }
      else {
        die("!!!ALERT!!! httpd processes died when reading modified conf file(s)!");
      }
    }
    if($cnt<$CNT && $httpdprocs>0 && !defined($SWITCHES{'s'})) {
      print "httpd processes successfully restarted\n";
    }
  }
  else { print STDERR "Restart of httpd skipped - only root can do that!\n"; }

  # sleep to add some probability for integer log files in case of wrong configuration
  if(!defined($SWITCHES{'t'})) {
    if($VL>0) {
      print "Waiting $SLEEP seconds...\n";
    }
    sleep $SLEEP;
  } 
  else { 
    print "Waiting only 5 seconds because of test mode...\n";
    sleep 5;
  }

} # end of first part with -c 

# rename log files
foreach $lfile (keys %LFILES) {
  if(!defined($SWITCHES{'t'})) {
    if(!rename($lfile,$lfile."-".$daystamp)) { 
      print STDERR "WARNING: Could not rename $lfile; it will remain!\n";
    }
    if(-e $lfile."-".$daystamp) {
      if(defined($SWITCHES{'c'})) { $RENAMED{$lfile}=$lfile."-".$daystamp; }
      if($VL>1) {
        print "Created log file $lfile-$daystamp\n";
      }
    }
    else {
      print STDERR "WARNING! Could not create $lfile-$daystamp!\n";
    }
  }
  else {
    print "Test mode: skipped rotation of log file $lfile\n";
  }
}

$flagsaverestart=1;
if(defined($SWITCHES{'c'})) {  # If re-writing of conf files has been chosen
  # re-install old conf
  foreach $cfile (keys %RENAMED) {
    if(-e $RENAMED{$cfile}) {
      if(-e $cfile) { 
        if(!unlink($cfile)) {
          print STDERR "Warning: Could not unlink modified (?) conf file $cfile!\n";
        }
      }
      if(!rename($RENAMED{$cfile},$cfile)) {
        print STDERR "WARNING: Could not recover original conf file $cfile!\n";
      }
      else {
        delete $RENAMED{$cfile};
        if($VL>1) {
          print "Original conf file $cfile recovered.\n";
        }
      }
      if(!-e $cfile) {
        print STDERR "ALERT! File $cfile disappeared! Apache will probably crash on restart!\n";
        $flagsaverestart=0;
      }
    }
  }
}

if($flagsaverestart) {
  # check running apache processes
  $httpdnotroot=0;
  if($VL>1) { print "Looking for httpd processes: "; }
  %HTTPD2PID=(); %HTTPDROOT=();
  open(PSIN,$PS." ".$PSPAR." |") || die("Error when running $PS! - Script aborted\n");
  while(defined($IL=<PSIN>)) {
    chomp $IL;
    $IL =~ s/^\s+//g; # delete leading spaces
    @PSIL=split(/\s+/,$IL);
    if($PSIL[$PScmd]=~/[\/\[]httpd\]?$/) {
      $HTTPD2PID{$PSIL[$PSpid]}=$PSIL[$PSusr];
      if($PSIL[$PSusr] eq "root") { $HTTPDROOT{$PSIL[$PSpid]}=0; }
      else { $httpdnotroot++; }
      if($VL>1) { 
        print $PSIL[$PSpid].($PSIL[$PSusr] eq 'root' ? '! ' : ' ');
      }
    }
  }
  if($VL>1) { print "\n"; }
  close(PSIN);
  if(!defined((keys %HTTPDROOT)[0])) { 
    die("FATAL ERROR: Could not find any running httpd root process! - Script aborted\n");
  }
  if($httpdnotroot==0) { 
    die("FATAL ERROR: httpd running only as user root (not wwwrun) - Script aborted\n");
  }
  if($VL>0) { print "Found $httpdnotroot not-root httpd process(es). Attempting restart now.\n"; }

  # graceful restart of apache
  if($< == 0) { # if user root
    $cnt=0; $stillalive=$httpdnotroot;
    foreach $pid (keys %HTTPDROOT) { 
      kill 10, $pid;
      if($VL>1) { print "Signal USR1 has been sent to process $pid\n"; }
    }
    while($cnt<$CNT && $stillalive != 0) {
      $stillalive=0; $httpdprocs=0;
      sleep 10;
      # control success
      open(PSIN,$PS." ".$PSPAR." |") || die("Error when running $PS! - Script aborted\n");
      while(defined($IL=<PSIN>)) {
        chomp $IL;
        $IL =~ s/^\s+//g; # delete leading spaces
        @PSIL=split(/\s+/,$IL);
        if($PSIL[$PScmd]=~/[\/\[]httpd\]?$/) {
          $httpdprocs++;
          if(defined($HTTPD2PID{$PSIL[$PSpid]}) && 
                !defined($HTTPDROOT{$PSIL[$PSpid]})) { $stillalive+=1; }
        }
      }
      close(PSIN);
      if($VL>1 && $stillalive>0) { print "$stillalive 'old' processes are still alive\n"; }
      $cnt++; 
    }
    if($cnt>=$CNT) {
      die("Unable to terminate intermediate httpd processes!\n");
    }
    if($httpdprocs==0) { 
      if(defined($SWITCHES{'t'}) || defined($SWITCHES{'c'})) {
        die("ALERT!!! httpd processes died when re-reading conf file(s)!");
      }
      else {
        die("ALERT!!! httpd processes died when reading recovered conf file(s)!");
      }
    }
    if($cnt<$CNT && $httpdprocs>0 && !defined($SWITCHES{'s'})) {
      print "httpd processes successfully restarted\n";
    }
  }
  else { print STDERR "Restart of httpd skipped - only root can do that!\n"; }
}
else { print STDERR "Restart of httpd skipped because of previous error!\n"; }

# sleep to add some probability for integer log files in case of wrong configuration
if(!defined($SWITCHES{'t'})) {
  if($VL>0) {
    print "Waiting $SLEEP seconds...\n";
  }
  sleep $SLEEP;
} 
else { 
  print "Waiting only 5 seconds because of test mode...\n";
  sleep 5;
} 

# pack log files
foreach $lfile (keys %LF) {
  if(-e $lfile."-".$daystamp) {
    if(-e $lfile."-rot.tmp" && defined($SWITCHES{'c'})) { # add entries from temporary log files
      if($VL>1) { print "Found temporary log file $lfile-rot.tmp\n"; }
      system("cat $lfile-rot.tmp >> $lfile-$daystamp");
      unlink $lfile."-rot.tmp";
    }
    system($GZIP,$lfile."-".$daystamp);
    if(-e $lfile."-".$daystamp.".gz") {
      if($VL>0) {
        print "Created packed log file $lfile-$daystamp.gz\n";
      }
    }
  }
}

if(defined($SWITCHES{'t'})) {
  print "Program finished regularly in test mode.\n";
}
elsif($VL>0) {
  print "Program finished regularly.\n";
}
