#!/usr/bin/perl
#
# By Phil2k@gmail.com
#  ver. 2.7
#
# Changes:
#  - ver. 2.1-2.2: corrected bug regarding searching files from queue/mess/ that must be in one of directories: todo, local, remote, bounce
#  - ver. 2.3: added some qmail-processes check and running lock
#  - ver. 2.4: - corrected bugs in $QMAIL_* global variables
#              - added stage status printout
#  - ver. 2.5: checks corect file names from queue/mess/ ( = inode number )
#  - ver. 2.6: - corrected queue/bounce/ verification by queue/mess/ ( it has no split-dir )
#              - corrected lock-file location bug
#              - cached stat() of files for large queues
#  - ver. 2.7: added split-dir checking for custom qmail-queue split-dirs ( check: fix_queue.pl --help , qmail-showctl )
#

#use File::stat;
use Fcntl ':mode';
use constant FORCE_STAT => 1;
use constant CACHED_STAT => 0;

$QMAIL_DIR="/var/qmail";

@QMAIL_PROCESSES=("$QMAIL_DIR/bin/qmail-send", "$QMAIL_DIR/bin/qmail-queue", "$QMAIL_DIR/bin/qmail-read", "$QMAIL_DIR/bin/qmail-rspawn", "$QMAIL_DIR/bin/qmail-lspawn", "$QMAIL_DIR/bin/qmail-remote", "$QMAIL_DIR/bin/qmail-local");
$QMAIL_SHOWCTL="$QMAIL_DIR/bin/qmail-showctl";
$QMAIL_QUEUE="$QMAIL_DIR/queue";

$PROC_DIR="/proc";

$script_name="$0";
$script_pid="$$";
$THIS_PID=$script_pid;



$USER_QMAILS="qmails";
$USER_QMAILQ="qmailq";
$GROUP_QMAIL="qmail";
$GROUP_VPOPMAIL="vpopmail,vchkpw";

$uid_qmails=getpwnam($USER_QMAILS);
if ($uid_qmails eq "") {
  &exit_prog("Unknown user \"$USER_QMAILS\": $!\n");
  }
$uid_qmailq=getpwnam($USER_QMAILQ);
if ($uid_qmailq eq "") {
  &exit_prog("Unknown user \"$USER_QMAILQ\": $!\n");
  }
$gid_qmail=getgrnam($GROUP_QMAIL);
if ($gid_qmail eq "") {
  &exit_prog("Unknown group \"$GROUP_QMAIL\": $!\n");
  }
foreach $group (split(/[^0-9a-zA-Z]+/,$GROUP_VPOPMAIL)) {
  $gid_vpopmail=getgrnam($group);
  if ($gid_vpopmail ne "") { last; }
  }
if ($gid_vpopmail eq "") {
  &exit_prog("Unknown group(s) \"$GROUP_VPOPMAIL\": $!\n");
  }

$ret=qx!$QMAIL_SHOWCTL!;
($splitdirs)=$ret=~/subdirectory split:\s+(\d+)/;

$syntax="$0 [-w] [-s <split_dir_num> ] [<queue_path>]\nWhere:\n  -w = fix the problems, doing a real write !\n  -s <split_dir_num> = split directory number used in qmail-queue (usualy it's returned by qmail-showctl ), default = $splitdirs\n  <queue_path> = queue path ( default = $QMAIL_QUEUE )\n\n";

$modify=0;
$queue_path="";
$optenable=1;
$optval=0;
foreach $arg (@ARGV) {
  if ($optenable) {
    if ($arg=~/^-(.*)$/) {
      &exit_prog("Options -$opt needs a value !\n".$syntax) if ($optval);
      $opt=$1;
      $isopt=1;
      if ($opt eq "w") {
        $modify=1;
        }
      elsif ($opt eq "s") {
        $optval=1;
        } else {
        &exit_prog("Invalid option !\n".$syntax);
        }
      }
    elsif ($optval && length($opt)) {
      $val=$arg;
      if ($opt eq "s") {
        &exit_prog("Split dir. option must be a prime number equal with the one showed by qmail-showctl !\n".$syntax) if (int($val) ne $val);
        $splitdirs=$val;
        }
      $optval=0;
      }
    else {
      $isopt=0;
      }
    } else {
    $isopt=0;
    }
  if (!$isopt) {
    $optenable=0;
    if ($queue_path eq "") {
      $queue_path=$arg;
      } else {
      &exit_prog("Too many parameters !\n".$syntax);
      }
    }
  }
if ($queue_path eq "") {
  $queue_path="$QMAIL_QUEUE";
  }
if ($queue_path!~/^\/$/) {
  $queue_path.='/';
  }

if ($splitdirs<1) {
  &warn("Invalid split directory number ($splitdirs) ! Force it to qmail's default setting (23) !\n");
  $splitdirs=23;
  }
for($i=2;$i<$splitdirs;$i++) {
  if (($splitdirs%$i)==0) {
    &exit_prog("Split directory number ($splitdirs) it's not a prime number !\n".$syntax);
    }
  }

$QMAIL_INFO="info";
#$QMAIL_RMT="remote";
$QMAIL_MESS="mess";
$QMAIL_REMOTE="remote";
$QMAIL_LOCAL="local";
$QMAIL_INTD="intd";
$QMAIL_TODO="todo";
$QMAIL_BOUNCE="bounce";
$QMAIL_PID="pid";
$QMAIL_LOCK="lock";

$LOCK_FILE="$QMAIL_QUEUE/$QMAIL_LOCK/fix_queue.lock";
$MAX_LOCK_FILE_AGE=43200; # seconds
$_locked=0; # Leave it "0" !!!

if (defined($mtime=&mtime($LOCK_FILE))) {
  open FILE, $LOCK_FILE;
  $lock_pid=<FILE>;
  chomp($lock_pid);
  close FILE;
  &warn("Conforming with lock-file $LOCK_FILE, $script_name is already running with pid $lock_pid ");
  if ($mtime>=(time()-$MAX_LOCK_FILE_AGE)) {
    &warn("! Try again later ...\n");
    } else {
    $age=int((time()-$mtime)/60);
    &warn(" from $age minutes ! If you are sure that it's dead, remove the file $LOCK_FILE, then try again ...\n");
    }
  exit 1;
  }

if ($modify) {
  ($process,$pid)=&find_processes_in_proc(@QMAIL_PROCESSES);
  if ($pid) {
    &exit_prog("$process is already running with PID $pid !\
Please stop it, and also other programs that might modify the queue !\n");
    }
  }  

open FILE, ">$LOCK_FILE";
print FILE $THIS_PID;
close FILE;
$_locked=1;



my $dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks;

#$|=1;

%files_stat=();

local *DIR;
opendir(DIR, $queue_path) || &exit_prog($!);
local @files=readdir(DIR);
#while($file=readdir(DIR)) {
closedir(DIR);
foreach $file (@files) {
  if ($file=~/^\.{1,2}$/) { next; }
  $file_path=$queue_path.$file;
  #print "file_path=$file_path\n";
  ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks) = @{&my_stat($file_path, CACHED_STAT)};
  if (($uid eq "") || ($gid eq "") || ($mode eq "")) {
    &exit_prog("Cannot stat() file $file_path !");
    }
  $mode=$mode & 07777;
  $user=getpwuid($uid);
  if ($user eq "") { $user=$uid; }
  $group=getgrgid($gid);
  if ($group eq "") { $group=$group; }
  $isdir=&my_is_dir($file_path);
  if ($isdir) {
    #&warn("debug: stage=$file\n");
    &warn("Stage: $file\n");
    if ($file eq $QMAIL_PID) {
      #&warn("debug: pid here\n");
      $correct=0700;
      if ($mode!=$correct) {
        &warn("mode=$mode\n");
        &warn(sprintf("File $file_path have wrong mode: %04o ! Should have: %04o !", $mode, $correct));
        if ($modify) {
          chmod $correct, $file_path;
          &my_stat($file_path, FORCE_STAT);
          if ($?) { &warn(" Cannot chmod $file_path: ".$!." !\n"); }
            else { &warn(" Changed !\n"); }
          } else {
          &warn("\n");
          }
        }
      if (($uid!=$uid_qmailq) || ($gid!=$gid_qmail)) {
        &warn("File $file_path have wrong owner: $user.$group ! Should have: $USER_QMAILQ.$GROUP_QMAIL !");
        if ($modify) {
          chown $uid_qmailq, $gid_qmail, $file_path;
          &my_stat($file_path, FORCE_STAT);
          if ($?) { &warn(" Cannot chown $file_path: ".$!." !\n"); }
            else { &warn(" Changed !\n"); }
          } else {
          &warn("\n");
          }
        }
      }
    elsif ($file eq $QMAIL_LOCK) {
      #&warn("debug: lock here\n");
      $correct=0750;
      if ($mode!=$correct) {
        &warn(sprintf("File $file_path have wrong mode: %04o ! Should have: %04o !", $mode, $correct));
        if ($modify) {
          chmod $correct, $file_path;
          &my_stat($file_path, FORCE_STAT);
          if ($?) { &warn(" Cannot chmod $file_path: ".$!." !\n"); }
            else { &warn(" Changed !\n"); }
          } else {
          &warn("\n");
          }
        }
      if (($uid!=$uid_qmailq) || ($gid!=$gid_qmail)) {
        &warn("File $file_path have wrong owner: $user.$group ! Should have: $USER_QMAILQ.$GROUP_QMAIL !");
        if ($modify) {
          chown $uid_qmailq, $gid_qmail, $file_path;
          &my_stat($file_path, FORCE_STAT);
          if ($?) { &warn(" Cannot chown $file_path: ".$!." !\n"); }
            else { &warn(" Changed !\n"); }
          } else {
          &warn("\n");
          }
        }
      }
    elsif ($file eq $QMAIL_BOUNCE) {
      #&warn("debug: bounce here\n");
      $correct=0700;
      if ($mode!=$correct) {
        &warn(sprintf("File $file_path have wrong mode: %04o ! Should have: %04o !", $mode, $correct));
        if ($modify) {
          chmod $correct, $file_path;
          &my_stat($file_path, FORCE_STAT);
          if ($?) { &warn(" Cannot chmod $file_path: ".$!." !\n"); }
            else { &warn(" Changed !\n"); }
          } else {
          &warn("\n");
          }
        }
      if (($uid!=$uid_qmails) || ($gid!=$gid_qmail)) {
        &warn("File $file_path have wrong owner: $user.$group ! Should have: $USER_QMAILS.$GROUP_QMAIL !");
        if ($modify) {
          chown $uid_qmails, $gid_qmail, $file_path;
          &my_stat($file_path, FORCE_STAT);
          if ($?) { &warn(" Cannot chown $file_path: ".$!." !\n"); }
            else { &warn(" Changed !\n"); }
          } else {
          &warn("\n");
          }
        }
      
      &do_path($file_path.'/', $queue_path.$QMAIL_BOUNCE.'/' , 0600, $uid_qmails, $gid_qmail);
      }
    elsif ($file eq $QMAIL_INFO) {
      #&warn("debug: info here\n");
      $correct=0700;
      if ($mode!=$correct) {
        &warn(sprintf("File $file_path have wrong mode: %04o ! Should have: %04o !", $mode, $correct));
        if ($modify) {
          chmod $correct, $file_path;
          &my_stat($file_path, FORCE_STAT);
          if ($?) { &warn(" Cannot chmod $file_path: ".$!." !\n"); }
            else { &warn(" Changed !\n"); }
          } else {
          &warn("\n");
          }
        }
      if (($uid!=$uid_qmails) || ($gid!=$gid_qmail)) {
        &warn("File $file_path have wrong owner: $user.$group ! Should have: $USER_QMAILS.$GROUP_QMAIL !");
        if ($modify) {
          chown $uid_qmails, $gid_qmail, $file_path;
          &my_stat($file_path, FORCE_STAT);
          if ($?) { &warn(" Cannot chown $file_path: ".$!." !\n"); }
            else { &warn(" Changed !\n"); }
          } else {
          &warn("\n");
          }
        }
      &do_path($file_path.'/', $queue_path.$QMAIL_INFO.'/' , 0600, $uid_qmails, $gid_qmail, 0700, $uid_qmails, $gid_qmail);
      }
    elsif ($file eq $QMAIL_INTD) {
      #&warn("debug: intd here\n");
      $correct=0700;
      if ($mode!=$correct) {
        &warn(sprintf("File $file_path have wrong mode: %04o ! Should have: %04o !", $mode, $correct));
        if ($modify) {
          chmod $correct, $file_path;
          &my_stat($file_path, FORCE_STAT);
          if ($?) { &warn(" Cannot chmod $file_path: ".$!." !\n"); }
            else { &warn(" Changed !\n"); }
          } else {
          &warn("\n");
          }
        }
      if (($uid!=$uid_qmailq) || ($gid!=$gid_qmail)) {
        &warn("File $file_path have wrong owner: $user.$group ! Should have: $USER_QMAILQ.$GROUP_QMAIL !");
        if ($modify) {
          chown $uid_qmailq, $gid_qmail, $file_path;
          &my_stat($file_path, FORCE_STAT);
          if ($?) { &warn(" Cannot chown $file_path: ".$!." !\n"); }
            else { &warn(" Changed !\n"); }
          } else {
          &warn("\n");
          }
        }
      
      &do_path($file_path.'/', $queue_path.$QMAIL_INTD.'/' , 0644, $uid_qmailq, $gid_qmail, 0700, $uid_qmailq, $gid_qmail);
      }
    elsif ($file eq $QMAIL_LOCAL) {
      #&warn("debug: local here\n");
      $correct=0700;
      if ($mode!=$correct) {
        &warn(sprintf("File $file_path have wrong mode: %04o ! Should have: %04o !", $mode, $correct));
        if ($modify) {
          chmod $correct, $file_path;
          &my_stat($file_path, FORCE_STAT);
          if ($?) { &warn(" Cannot chmod $file_path: ".$!." !\n"); }
            else { &warn(" Changed !\n"); }
          } else {
          &warn("\n");
          }
        }
      if (($uid!=$uid_qmails) || ($gid!=$gid_qmail)) {
        &warn("File $file_path have wrong owner: $user.$group ! Should have: $USER_QMAILS.$GROUP_QMAIL !");
        if ($modify) {
          chown $uid_qmails, $gid_qmail, $file_path;
          &my_stat($file_path, FORCE_STAT);
          if ($?) { &warn(" Cannot chown $file_path: ".$!." !\n"); }
            else { &warn(" Changed !\n"); }
          } else {
          &warn("\n");
          }
        }
      
      &do_path($file_path.'/', $queue_path.$QMAIL_LOCAL.'/' , 0600, $uid_qmails, $gid_qmail, 0700, $uid_qmails, $gid_qmail);
      }
    elsif ($file eq $QMAIL_MESS) {
      #&warn("debug: mess here\n");
      $correct=0750;
      if ($mode!=$correct) {
        &warn(sprintf("File $file_path have wrong mode: %04o ! Should have: %04o !", $mode, $correct));
        if ($modify) {
          chmod $correct, $file_path;
          &my_stat($file_path, FORCE_STAT);
          if ($?) { &warn(" Cannot chmod $file_path: ".$!." !\n"); }
            else { &warn(" Changed !\n"); }
          } else {
          &warn("\n");
          }
        }
      if (($uid!=$uid_qmailq) || ($gid!=$gid_qmail)) {
        &warn("File $file_path have wrong owner: $user.$group ! Should have: $USER_QMAILQ.$GROUP_QMAIL !");
        if ($modify) {
          chown $uid_qmailq, $gid_qmail, $file_path;
          &my_stat($file_path, FORCE_STAT);
          if ($?) { &warn(" Cannot chown $file_path: ".$!." !\n"); }
            else { &warn(" Changed !\n"); }
          } else {
          &warn("\n");
          }
        }
      
      &do_path($file_path.'/', $queue_path.$QMAIL_MESS.'/' , 0644, $uid_qmailq, $gid_qmail, 0750, $uid_qmailq, $gid_qmail);
      }
    elsif ($file eq $QMAIL_REMOTE) {
      #&warn("debug: remote here\n");
      $correct=0700;
      if ($mode!=$correct) {
        &warn(sprintf("File $file_path have wrong mode: %04o ! Should have: %04o !", $mode, $correct));
        if ($modify) {
          chmod $correct, $file_path;
          &my_stat($file_path, FORCE_STAT);
          if ($?) { &warn(" Cannot chmod $file_path: ".$!." !\n"); }
            else { &warn(" Changed !\n"); }
          } else {
          &warn("\n");
          }
        }
      if (($uid!=$uid_qmails) || ($gid!=$gid_qmail)) {
        &warn("File $file_path have wrong owner: $user.$group ! Should have: $USER_QMAILS.$GROUP_QMAIL !");
        if ($modify) {
          chown $uid_qmails, $gid_qmail, $file_path;
          &my_stat($file_path, FORCE_STAT);
          if ($?) { &warn(" Cannot chown $file_path: ".$!." !\n"); }
            else { &warn(" Changed !\n"); }
          } else {
          &warn("\n");
          }
        }
      
      &do_path($file_path.'/', $queue_path.$QMAIL_REMOTE.'/' , 0600, $uid_qmails, $gid_qmail, 0700, $uid_qmails, $gid_qmail);
      }
    elsif ($file eq $QMAIL_TODO) {
      #&warn("debug: todo here\n");
      $correct=0750;
      if ($mode!=$correct) {
        &warn(sprintf("File $file_path have wrong mode: %04o ! Should have: %04o !", $mode, $correct));
        if ($modify) {
          chmod $correct, $file_path;
          &my_stat($file_path, FORCE_STAT);
          if ($?) { &warn(" Cannot chmod $file_path: ".$!." !\n"); }
            else { &warn(" Changed !\n"); }
          } else {
          &warn("\n");
          }
        }
      if (($uid!=$uid_qmailq) || ($gid!=$gid_qmail)) {
        &warn("File $file_path have wrong owner: $user.$group ! Should have: $USER_QMAILQ.$GROUP_QMAIL !");
        if ($modify) {
          chown $uid_qmailq, $gid_qmail, $file_path;
          &my_stat($file_path, FORCE_STAT);
          if ($?) { &warn(" Cannot chown $file_path: ".$!." !\n"); }
            else { &warn(" Changed !\n"); }
          } else {
          &warn("\n");
          }
        }
      
      &do_path($file_path.'/', $queue_path.$QMAIL_TODO.'/' , 0644, $uid_qmailq, $gid_qmail, 0750, $uid_qmailq, $gid_qmail);
      }

    #print "$file_path\n";
    } else {
    &warn("File $file_path shouldn't be in path $queue_path ! Ignore it ...\n");
    }
  }
#closedir(DIR);

exit_prog();


sub do_path() {
my ($path, $src_path, $file_mode, $file_uid, $file_gid, $dir_mode, $dir_uid, $dir_gid)=@_;
my $dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks;
my $file;
my $user, $group;
my $erased;
my $mess_path;
my $split_dir;
my $mess_id;
my $prefix;
my $file_path;
my $valid_file_path;
my $file_path_id;
my $valid_file_path_id;
my $old, $new;
my @renames;
my ($todo_path, $local_path, $remote_path, $bounce_path, $info_path);

local *DIR;
opendir(DIR, $path);
#print "\rProcessing files ...";
while($file=readdir(DIR)) {
  if ($file=~/^\.{1,2}$/) { next; }
  $file_path=$path.$file;
  #print "\rProcessing ($src_path) $file_path ...                                                ";
  ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks) = @{&my_stat($file_path, CACHED_STAT)};
  if (($uid eq "") || ($gid eq "") || ($mode eq "")) {
    &exit_prog("Cannot stat() file $file_path !");
    }
  $mode=$mode & 07777;
  $user=getpwuid($uid);
  if ($user eq "") { $user=$uid; }
  $group=getgrgid($gid);
  if ($group eq "") { $group=$group; }
  $erased=0;
  if (&my_is_dir($file_path)) {
    if (($dir_mode ne "") && ($dir_uid) && ($dir_gid)) {
      if ($mode!=$dir_mode) {
        &warn(sprintf("Directory $file_path have wrong mode: %04o ! Should have: %04o !", $mode, $dir_mode));
        if ($modify) {
          chmod $dir_mode, $file_path;
          &my_stat($file_path, FORCE_STAT);
          if ($?) { &warn(" Cannot chmod $file_path: ".$!." !\n"); }
            else { warn(" Changed !\n"); }
          } else {
          &warn("\n");
          }
        }
      if (($uid!=$dir_uid) || ($gid!=$dir_gid)) {
        &warn("Directory $file_path have wrong owner: $user.$group ! Should have: ".(getpwuid($dir_uid)?getpwuid($dir_uid):$dir_uid).".".(getgrgid($dir_gid)?getgrgid($dir_gid):$dir_gid)." !");
        if ($modify) {
          chown $dir_uid, $dir_gid, $file_path;
          &my_stat($file_path, FORCE_STAT);
          if ($?) { &warn(" Cannot chown $file_path: ".$!." !\n"); }
            else { &warn(" Changed !\n"); }
          } else {
          &warn("\n");
          }
        }
      &do_path($file_path.'/', $src_path, $file_mode, $file_uid, $file_gid, $dir_mode, $dir_uid, $dir_gid);
      } else {
      &warn("Directory $file_path shouldn't be in path: $path ! Ignore it ...\n");
      }
    } else {
    
    if ($mode!=$file_mode) {
      &warn(sprintf("File $file_path have wrong mode: %04o ! Should have: %04o !", $mode, $file_mode));
      if ($modify) {
        chmod $file_mode, $file_path;
        &my_stat($file_path, FORCE_STAT);
        if ($?) { &warn(" Cannot chmod $file_path: ".$!." !\n"); }
          else { &warn(" Changed !\n"); }
        } else {
        &warn("\n");
        }
      }
    if (($uid!=$file_uid) || (($gid!=$file_gid) && ((($src_path ne $queue_path.$QMAIL_MESS.'/') && ($src_path ne $queue_path.$QMAIL_TODO.'/')) || ($gid!=$gid_vpopmail)))) {
      &warn("File $file_path have wrong owner: $user.$group ! Should have: ".(getpwuid($file_uid)?getpwuid($file_uid):$file_uid).".".(getgrgid($file_gid)?getgrgid($file_gid):$file_gid)." !");
      if ($modify) {
        chown $file_uid, $file_gid, $file_path;
        &my_stat($file_path, FORCE_STAT);
        if ($?) { &warn(" Cannot chown $file_path: ".$!." !\n"); }
          else { &warn(" Changed !\n"); }
        } else {
        &warn("\n");
        }
      }
    if ($src_path eq $queue_path.$QMAIL_MESS.'/') {
      ($file_path_id)=$file_path=~/^$src_path(.+)$/;
      if ($file_path_id=~/\//) {
        ($split_dir, $mess_id)=split('/', $file_path_id);
        $valid_split_dir = ($mess_id % $splitdirs);
        $found=0;
        $found_in="";
        #$info_path=$queue_path.$QMAIL_INFO.'/'.$file_path_id;
        #if (&my_is_file($info_path))  {
        #  $found++;
        #  $found_in=($found_in?", ":"").$QMAIL_INFO;
        #  }
        $todo_path=$queue_path.$QMAIL_TODO.'/'.$file_path_id;
        if (&my_is_file($todo_path))  {
          $found++;
          $found_in=($found_in?", ":"").$QMAIL_TODO;
          }
        $local_path=$queue_path.$QMAIL_LOCAL.'/'.$file_path_id;
        if (&my_is_file($local_path)) {
          $found++;
          $found_in=($found_in?", ":"").$QMAIL_LOCAL;
          }
        $remote_path=$queue_path.$QMAIL_REMOTE.'/'.$file_path_id;
        if (&my_is_file($remote_path)) {
          $found++;
          $found_in=($found_in?", ":"").$QMAIL_REMOTE;
          }
        $bounce_path=$queue_path.$QMAIL_BOUNCE.'/'.$file_path_id;
        if (&my_is_file($bounce_path)) {
          $found++;
          $found_in=($found_in?", ":"").$QMAIL_BOUNCE;
          }
        if (!$found) {
          &warn("$file_path_id not found in $QMAIL_INFO, $QMAIL_TODO, $QMAIL_LOCAL, $QMAIL_REMOTE or $QMAIL_BOUNCE !");
          if ($modify) {
            unlink $file_path;
            &uncache_stat($file_path);
            if ($?) { &warn(" Couldn't fix it by erasing file $file_path: ".$!." !\n"); } 
              else { &warn(" Fix it by erasing file $file_path !\n"); }
            $erased=1;
            } else {
            &warn("\n");
            }
          }
        elsif ($found>1) {
          &warn("$file_path_id exists in $found_in !");
          if ($modify) {
            unlink $file_path;
            &uncache_stat($file_path);
            if ($?) { &warn(" Couldn't fix it by erasing file $file_path: ".$!." !\n"); } 
              else { &warn(" Fix it by erasing file $file_path !\n"); }
            $erased=1;
            } else {
            &warn("\n");
            }
          }
        elsif (($mess_id ne $ino) || ($split_dir ne $valid_split_dir)) {
          &warn("$file_path_id have different name by inode number: $ino !") if ($mess_id ne $ino);
          &warn("$file_path_id have different split-dir by: $valid_split_dir/$ino !") if ($split_dir ne $valid_split_dir);
          $valid_file_path_id=$valid_split_dir.'/'.$ino;
          $valid_mess_id=$ino;
          if ($modify) {
            $info_path=$queue_path.$QMAIL_INFO.'/'.$valid_file_path_id;
            $todo_path=$queue_path.$QMAIL_TODO.'/'.$valid_file_path_id;
            $local_path=$queue_path.$QMAIL_LOCAL.'/'.$valid_file_path_id;
            $remote_path=$queue_path.$QMAIL_REMOTE.'/'.$valid_file_path_id;
            #$bounce_path=$queue_path.$QMAIL_BOUNCE.'/'.$valid_file_path_id;
            $bounce_path=$queue_path.$QMAIL_BOUNCE.'/'.$ino;
            if ((!&my_file_exists($todo_path)) && (!&my_file_exists($local_path)) && (!&my_file_exists($remote_path)) && (!&my_file_exists($bounce_path))) {
              $valid_file_path=$queue_path.$QMAIL_MESS.'/'.$valid_file_path_id;
              # rename file(s)
              $old=$file_path;
              $new=$valid_file_path;
              rename $old, $new;
              &uncache_stat($old);
              &my_stat($new, FORCE_STAT);
              push @renames, "$old to $new";
              
              if (&my_is_file($queue_path.$QMAIL_INFO.'/'.$file_path_id)) {
                $old=$queue_path.$QMAIL_INFO.'/'.$file_path_id;
                $new=$queue_path.$QMAIL_INFO.'/'.$valid_file_path_id;
                rename $old, $new;
                &uncache_stat($old);
                &my_stat($new, FORCE_STAT);
                push @renames, "$old to $new";
                }
              if (&my_is_file($queue_path.$QMAIL_TODO.'/'.$file_path_id)) {
                $old=$queue_path.$QMAIL_TODO.'/'.$file_path_id;
                $new=$queue_path.$QMAIL_TODO.'/'.$valid_file_path_id;
                rename $old, $new;
                &uncache_stat($old);
                &my_stat($new, FORCE_STAT);
                push @renames, "$old to $new";
                }
              if (&my_is_file($queue_path.$QMAIL_LOCAL.'/'.$file_path_id)) {
                $old=$queue_path.$QMAIL_LOCAL.'/'.$file_path_id;
                $new=$queue_path.$QMAIL_LOCAL.'/'.$valid_file_path_id;
                rename $old, $new;
                &uncache_stat($old);
                &my_stat($new, FORCE_STAT);
                push @renames, "$old to $new";
                }
              if (&my_is_file($queue_path.$QMAIL_REMOTE.'/'.$file_path_id)) {
                $old=$queue_path.$QMAIL_REMOTE.'/'.$file_path_id;
                $new=$queue_path.$QMAIL_REMOTE.'/'.$valid_file_path_id;
                rename $old, $new;
                &uncache_stat($old);
                &my_stat($new, FORCE_STAT);
                push @renames, "$old to $new";
                }
              if (&my_is_file($queue_path.$QMAIL_BOUNCE.'/'.$mess_id)) {
                $old=$queue_path.$QMAIL_BOUNCE.'/'.$mess_id;
                $new=$queue_path.$QMAIL_BOUNCE.'/'.$valid_mess_id;
                rename $old, $new;
                &uncache_stat($old);
                &my_stat($new, FORCE_STAT);
                push @renames, "$old to $new";
                }
              &warn(" Fix it by renaming file".((($#renames)>0)?"s":"").join(', ', @renames)." !\n");
              # rebuild variables in case of later use
              $file_path=$valid_file_path;
              $mess_id=$ino;
              $file_path_id=$valid_file_path_id;
              } else {
              unlink $file_path;
              &uncache_stat($file_path);
              if ($?) { &warn(" Couldn't fix it by erasing file $file_path: ".$!." !\n"); } 
                else { &warn(" Fix it by erasing file $file_path !\n"); }
              $erased=1;
              }
            } else {
            &warn("\n");
            }
          }
        }
      } else {
      ($file_path_id)=$file_path=~/^$src_path(.+)$/;
      if ($file_path_id=~/\//) {
        $mess_path=$queue_path.$QMAIL_MESS.'/'.$file_path_id;
        if (&my_file_exists($mess_path)) {
          if (!(&my_is_file($mess_path))) {
            &warn("$mess_path is not a file ! Ignore it ...\n");
            }
          } else {
          &warn("$mess_path doesn't exists !");
          if ($modify) {
            unlink $file_path;
            &uncache_stat($file_path);
            if ($?) { &warn(" Couldn't fix it by erasing file $file_path: ".$!." !\n"); } 
              else { &warn(" Fix it by erasing file $file_path !\n"); }
            $erased=1;
            } else {
            &warn("\n");
            }
          }
        } else {
        $file_path_id="";
        }
      ($file_id)=&basename($file_path);
      $mess_path=&search_in_mess($file_id, $queue_path.$QMAIL_MESS.'/');
      #print "files stated: ".scalar(keys %files_stat)." \n";
      ($prefix)=$mess_path=~/^$queue_path$QMAIL_MESS\/(.+)$/;
      if ($prefix ne "") {
        if (($file_path_id ne "") && ($prefix.$file_id ne $file_path_id)) {
          &warn("Message with id $file_id was found in different queue-message path: ".$prefix.$file_id.", instead of ".$file_path_id." !");
          if ($modify) {
            unlink $file_path;
            &uncache_stat($file_path);
            if ($?) { &warn(" Couldn't fix it by erasing file $file_path: ".$!." !\n"); }
              else { &warn(" Fix it by erasing file $file_path !\n"); }
            $erased=1;
            } else {
            &warn("\n");
            }
          }
        }
      elsif ($erased==0) {
        &warn("Message with id $file_id wasn't found in queue-message !");
        if ($modify) {
          unlink $file_path;
          &uncache_stat($file_path);
          if ($?) { &warn(" Couldn't fix it by erasing file $file_path: ".$!." !\n"); }
            else { &warn(" Fix it by erasing file $file_path !\n"); }
          } else {
          &warn("\n");
          }
        }
      }
    }
  }
closedir(DIR);
}

sub search_in_mess() {
my ($file_id, $path)=@_;
my $file;
my $ret_path;
my $file_path;
local *DIR;


#print "look for $file_id in $path (".scalar(keys %files_stat).")\n";
foreach $file_path (keys %files_stat) {
  return &dirname($file_path).'/' if (&my_is_file($file_path) && (substr($file_path, 0, length($path)) eq $path) && (&basename($file_path) eq $file_id));
  }

opendir(DIR, $path);
while($file=readdir(DIR)) {
  if ($file=~/^\.{1,2}$/) { next; }
  $file_path=$path.$file;
  next if (!&my_file_exists($file_path));
  if (&my_is_dir($file_path)) {
    $ret_path=&search_in_mess($file_id, $file_path.'/');
    if ($ret_path ne "") {
      closedir(DIR);
      return $ret_path;
      }
    }
  if ($file eq $file_id) {
    closedir(DIR);
    #print "found $file in $path !\n";
    $tmp=<STDIN>;
    return $path;
    }
  }
return "";
closedir(DIR);
}

sub warn {
 print STDERR $_[0];
}

sub exit_prog {
 my $err=join('\n',@_);
 unlink $LOCK_FILE if ($_locked);
 &uncache_stat($file_path);
 if ($err) {
  &warn("Error: $err\n");
  exit 1;
  } else {
  exit 0;
  }
}

sub find_processes_in_proc {
 my @processes=@_;
 my @result=();
 my ($file, $exe_link, $process);
 my $any_pids_cnt=0;
 local * DIR;
 
 #print "debug: find processes ".join(',',@processes)." in $PROC_DIR ...\n";
 opendir DIR, "$PROC_DIR";
 &exit_prog("No proc-fs found ($PROC_DIR) !\nUse instead with \"-p\" or with \"-f\" ( with \"-f\", be sure that qmail-send it's stopped ) !\n") if (!&my_is_dir($PROC_DIR));
 while($file=readdir(DIR)) {
   if ($file eq int($file)) { # search only PIDs
     next if ($file eq $THIS_PID);
     $any_pids_cnt++;
     $exe_link=$PROC_DIR."/".$file."/exe";
     if (&my_is_symlink($exe_link)) {
       foreach $process (@processes) {
         if (readlink($exe_link) eq $process) {
           #push @result, $file;
           @result=($process, $file);
           $found=1;
           last;
           }
         }
       last if $found;
       }
     }
   }
 close DIR;
 &exit_prog("No proc-fs found ($PROC_DIR) !\nUse instead with \"-p\" or with \"-f\" ( with \"-f\", be sure that qmail-send it's stopped ) !\n") if (!$any_pids_cnt);
 return @result;
}

sub my_stat() {
 my ($file_path, $force_stat)=@_;
 my @st;
 if ((!$force_stat) && exists($files_stat{$file_path})) {
   return $files_stat{$file_path};
   } else {
   @st=stat($file_path);
   if ($#st>8) {
     $files_stat{$file_path}=[ @st ];
     #print "\nstat for $file_path\n";
     } else {
     $files_stat{$file_path}=undef;
     }
   }
 return $files_stat{$file_path};
}

sub my_file_exists() {
 return (defined(&my_stat($_[0], CACHED_STAT)));
}

sub my_is_file() {
 my $stref=&my_stat($_[0], CACHED_STAT);
 return (defined($stref) && S_ISREG($stref->[2]));
}

sub my_is_dir() {
 my $stref=&my_stat($_[0], CACHED_STAT);
 return (defined($stref) && S_ISDIR($stref->[2]));
}

sub my_is_symlink() {
 my $stref=&my_stat($_[0], CACHED_STAT);
 return (defined($stref) && S_ISLNK($stref->[2]));
}

sub mtime() {
 my $stref=&my_stat($_[0], CACHED_STAT);
 return undef if (not(defined($stref)));
 return $stref->[9];
}

sub basename() {
 my ($file)=$_[0]=~/([^\/]+)$/;
 return $file;
}

sub dirname() {
 my ($dir)=$_[0]=~/^(.*?)\/[^\/]*$/;
 return $dir;
}

sub uncache_stat() {
 my $file_path=$_[0];
 delete($files_stat{$file_path}) if (exists($files_stat{$file_path}));
}