#*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | # / __| | | | |_) | | # | (__| |_| | _ <| |___ # \___|\___/|_| \_\_____| # # Copyright (C) 1998 - 2006, Daniel Stenberg, , et al. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://curl.haxx.se/docs/copyright.html. # # You may opt to use, copy, modify, merge, publish, distribute and/or sell # copies of the Software, and permit persons to whom the Software is # furnished to do so, under the terms of the COPYING file. # # This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY # KIND, either express or implied. # # $Id$ ########################################################################### use strict; use warnings; my $DEFAULT_TIMEOUT_START = 90; # default allowed time for a process to startup my $DEFAULT_TIMEOUT_STOP = 90; # default allowed time for a process to stop my $ONE_HALF_STOP_TIMEOUT = int($DEFAULT_TIMEOUT_STOP / 2); my $ONE_THIRD_STOP_TIMEOUT = int($DEFAULT_TIMEOUT_STOP / 3); my $ONE_SIXTH_STOP_TIMEOUT = int($DEFAULT_TIMEOUT_STOP / 6); my $pidpattern = qr/^\-?(\d+)$/; # pre-compiled pid pattern regexp ###################################################################### # pidfromfile returns the pid stored in the given pidfile. The value # of the returned pid will never be a negative value. It will be zero # on any file related error or if a pid can not be extracted from the # file. Otherwise it will be a positive value, even If the pid number # stored in the file is a negative one. # sub pidfromfile { my ($pidfile)=@_; my $pid = 0; # on failure or empty file return 0 my $pidline; if(not defined $pidfile) { return 0; } if(-f $pidfile) { if(open(PIDF, "<$pidfile")) { my $pidline = ; close(PIDF); if($pidline) { chomp $pidline; $pidline =~ s/^\s+//; $pidline =~ s/\s+$//; $pidline =~ s/^[+-]?0+//; if($pidline =~ $pidpattern) { $pid = $1; } } } } return $pid; } ###################################################################### # unlinkpidfiles unlinks/deletes the given pidfiles. The first argument # 'pidfiles' is a string of whitespace separated pidfiles. If successful # returns 0, on error it returns the number of files not deleted. # sub unlinkpidfiles { my ($pidfiles)=@_; if(not defined $pidfiles) { return 0; } my $pidfile; my $errcount = 0; for $pidfile (split(" ", $pidfiles)) { if($pidfile) { if(unlink($pidfile) == 0) { $errcount++; } } } return $errcount; } ###################################################################### # checkalivepid checks if the process of the given pid is alive. The # argument must represent a single pid and be a valid number, if not # it will return 0. It will also return 0 if the pid argument is zero # or negative. If the pid argument is positive and it is alive returns # the same positive pid, otherwise, if it is not alive it will return # the negative value of the pid argument. # sub checkalivepid { my ($pid)=@_; if(not defined $pid) { return 0; } if ($pid !~ $pidpattern) { return 0; # invalid argument } if($pid > 0) { if(kill(0, $pid)) { return $pid; # positive means it is alive } else { return -$pid; # negative means dead process } } return 0; # not a positive pid argument } ###################################################################### # checkalivepidfile checks if the process of the pid stored in the # given pidfile is alive. It will return 0 on any file related error # or if a pid can not be extracted from the file. If the process of # the pid present in the file is alive it returns that positive pid, # if it is not alive it will return the negative value of the pid. # sub checkalivepidfile { my ($pidfile)=@_; my $pid = pidfromfile($pidfile); my $ret = checkalivepid($pid); return $ret; } ###################################################################### # signalpids signals processes in the second argument with the signal # given in the first argument. The second argument 'pids' is a string # of whitespace separated pids. Of the given pids only those that are # positive and are actually alive will be signalled, and no matter # how many times a pid is repeated it will only be signalled once. # sub signalpids { my ($signal, $pids)=@_; if((not defined $signal) || (not defined $pids)) { return; } if($pids !~ /\s+/) { # avoid sorting if only one pid if(checkalivepid($pids) > 0) { kill($signal, $pids); } return; } my $prev = 0; for(sort({$a <=> $b} split(" ", $pids))) { if($_ =~ $pidpattern) { my $pid = $1; if($prev != $pid) { $prev = $pid; if(checkalivepid($pid) > 0) { kill($signal, $pid); } } } } } ###################################################################### # signalpidfile signals the process of the pid stored in the given # pidfile with the signal given in the first argument if the process # with that pid is actually alive. # sub signalpidfile { my ($signal, $pidfile)=@_; my $pid = pidfromfile($pidfile); if($pid > 0) { signalpids($signal, $pid); } } ###################################################################### # waitdeadpid waits until all processes given in the first argument # are not alive, waiting at most timeout seconds. The first argument # 'pids' is a string of whitespace separated pids. Returns 1 when all # pids are not alive. Returns 0 when the specified timeout has expired # and at least one of the specified pids is still alive. # sub waitdeadpid { my ($pids, $timeout)=@_; if(not defined $pids) { return 1; } if((not defined $timeout) || ($timeout < 1)) { $timeout = $DEFAULT_TIMEOUT_STOP; } while($timeout--) { my $alive = 0; for(split(" ", $pids)) { if($_ =~ $pidpattern) { my $pid = $1; if(checkalivepid($pid) > 0) { $alive++; } } } if($alive == 0) { return 1; # not a single pid is alive } sleep(1); } return 0; # at least one pid is still alive after timeout seconds } ###################################################################### # waitalivepidfile waits until the given pidfile has a pid that is # alive, waiting at most timeout seconds. It returns the positive pid # When it is alive, otherwise it returns 0 when timeout seconds have # elapsed and the pidfile does not have a pid that is alive. # sub waitalivepidfile { my ($pidfile, $timeout)=@_; if(not defined $pidfile) { return 0; } if((not defined $timeout) || ($timeout < 1)) { $timeout = $DEFAULT_TIMEOUT_START; } while($timeout--) { my $pid = checkalivepidfile($pidfile); if($pid > 0) { return $pid; # positive means it is alive } sleep(1); } return 0; # no pid in pidfile or not alive } ###################################################################### # stopprocess ends the given pid(s), waiting for them to die. The 'pids' # argument is a string of whitespace separated pids. Returns 1 if all # of the processes have been successfully stopped. If unable to stop # any of them in DEFAULT_TIMEOUT_STOP seconds then it returns 0. # sub stopprocess { my ($pids)=@_; if(not defined $pids) { return 1; } signalpids("TERM", $pids); if(waitdeadpid($pids, $ONE_HALF_STOP_TIMEOUT) == 0) { signalpids("INT", $pids); if(waitdeadpid($pids, $ONE_THIRD_STOP_TIMEOUT) == 0) { signalpids("KILL", $pids); if(waitdeadpid($pids, $ONE_SIXTH_STOP_TIMEOUT) == 0) { return 0; # at least one pid is still alive !!! } } } return 1; # not a single pid is alive } ###################################################################### # stopprocesspidfile ends the test server process of the given pidfile, # waiting for it to die, and unlinking/deleting the given pidfile. If # the given process was not running or has been successfully stopped it # returns 1. If unable to stop it in DEFAULT_TIMEOUT_STOP seconds then # returns 0. # sub stopprocesspidfile { my ($pidfile)=@_; if(not defined $pidfile) { return 1; } my $ret = 1; # assume success stopping it my $pid = checkalivepidfile($pidfile); if($pid > 0) { $ret = stopprocess($pid); } unlinkpidfiles($pidfile); return $ret; } ###################################################################### # ftpkillslave ends a specific slave, waiting for it to die, and # unlinking/deleting its pidfiles. If the given ftpslave was not # running or has been successfully stopped it returns 1. If unable # to stop it in DEFAULT_TIMEOUT_STOP seconds then it returns 0. # sub ftpkillslave { my ($id, $ext)=@_; my $ret = 1; # assume success stopping them my $pids = ""; my $pidfiles = ""; for my $base (('filt', 'data')) { my $pidfile = ".sock$base$id$ext.pid"; my $pid = checkalivepidfile($pidfile); $pidfiles .= " $pidfile"; if($pid > 0) { $pids .= " $pid"; } } if($pids) { $ret = stopprocess($pids); } if($pidfiles) { unlinkpidfiles($pidfiles); } return $ret; } ###################################################################### # ftpkillslaves ends all the ftpslave processes, waiting for them to # die, unlinking/deleting its pidfiles. If they were not running or # have been successfully stopped it returns 1. If unable to stop any # of them in DEFAULT_TIMEOUT_STOP seconds then returns 0. # sub ftpkillslaves { my $ret = 1; # assume success stopping them my $pids = ""; my $pidfiles = ""; for my $ext (("", "ipv6")) { for my $id (("", "2")) { for my $base (('filt', 'data')) { my $pidfile = ".sock$base$id$ext.pid"; my $pid = checkalivepidfile($pidfile); $pidfiles .= " $pidfile"; if($pid > 0) { $pids .= " $pid"; } } } } if($pids) { $ret = stopprocess($pids); } if($pidfiles) { unlinkpidfiles($pidfiles); } return $ret; } ###################################################################### # library files end with 1; to make 'require' and 'use' succeed. 1;