1#!/usr/bin/perl -w 2# 3# Copyright (C) Internet Systems Consortium, Inc. ("ISC") 4# 5# This Source Code Form is subject to the terms of the Mozilla Public 6# License, v. 2.0. If a copy of the MPL was not distributed with this 7# file, You can obtain one at http://mozilla.org/MPL/2.0/. 8# 9# See the COPYRIGHT file distributed with this work for additional 10# information regarding copyright ownership. 11 12# Framework for stopping test servers 13# Based on the type of server specified, signal the server to stop, wait 14# briefly for it to die, and then kill it if it is still alive. 15# If a server is specified, stop it. Otherwise, stop all servers for test. 16 17use strict; 18use warnings; 19 20use Cwd ':DEFAULT', 'abs_path'; 21use English '-no_match_vars'; 22use Getopt::Long; 23 24# Usage: 25# perl stop.pl [--use-rndc [--port port]] test [server] 26# 27# --use-rndc Attempt to stop the server via the "rndc stop" command. 28# 29# --port port Only relevant if --use-rndc is specified, this sets the 30# command port over which the attempt should be made. If 31# not specified, port 9953 is used. 32# 33# test Name of the test directory. 34# 35# server Name of the server directory. 36 37my $usage = "usage: $0 [--use-rndc [--halt] [--port port]] test-directory [server-directory]"; 38 39my $use_rndc = 0; 40my $halt = 0; 41my $rndc_port = 9953; 42my $errors = 0; 43 44GetOptions( 45 'use-rndc!' => \$use_rndc, 46 'halt!' => \$halt, 47 'port=i' => \$rndc_port 48 ) or die "$usage\n"; 49 50my ( $test, $server_arg ) = @ARGV; 51 52if (!$test) { 53 die "$usage\n"; 54} 55 56# Global variables 57my $topdir = abs_path($ENV{'SYSTEMTESTTOP'}); 58my $testdir = abs_path($topdir . "/" . $test); 59 60if (! -d $testdir) { 61 die "No test directory: \"$testdir\"\n"; 62} 63 64if ($server_arg && ! -d "$testdir/$server_arg") { 65 die "No server directory: \"$testdir/$server_arg\"\n"; 66} 67 68my $RNDC = $ENV{RNDC}; 69 70my @ns; 71my @ans; 72 73if ($server_arg) { 74 if ($server_arg =~ /^ns/) { 75 push(@ns, $server_arg); 76 } elsif ($server_arg =~ /^ans/) { 77 push(@ans, $server_arg); 78 } else { 79 print "$0: ns or ans directory expected"; 80 print "I:$test:failed"; 81 } 82} else { 83 # Determine which servers need to be stopped for this test. 84 opendir DIR, $testdir or die "unable to read test directory: \"$test\" ($OS_ERROR)\n"; 85 my @files = sort readdir DIR; 86 closedir DIR; 87 88 @ns = grep /^ns[0-9]*$/, @files; 89 @ans = grep /^ans[0-9]*$/, @files; 90} 91 92# Stop the server(s), pass 1: rndc. 93if ($use_rndc) { 94 foreach my $name(@ns) { 95 stop_rndc($name, $rndc_port); 96 } 97 98 @ns = wait_for_servers(30, @ns); 99} 100 101# Pass 2: SIGTERM 102foreach my $name (@ns) { 103 stop_signal($name, "TERM"); 104} 105 106@ns = wait_for_servers(60, @ns); 107 108foreach my $name(@ans) { 109 stop_signal($name, "TERM", 1); 110} 111 112@ans = wait_for_servers(1200, @ans); 113 114# Pass 3: SIGABRT 115foreach my $name (@ns) { 116 print "I:$test:$name didn't die when sent a SIGTERM\n"; 117 stop_signal($name, "ABRT"); 118 $errors = 1; 119} 120foreach my $name (@ans) { 121 print "I:$test:$name didn't die when sent a SIGTERM\n"; 122 stop_signal($name, "ABRT", 1); 123 $errors = 1; 124} 125 126exit($errors); 127 128# Subroutines 129 130# Return the full path to a given server's lock file. 131sub server_lock_file { 132 my ( $server ) = @_; 133 134 return if (defined($ENV{'CYGWIN'})); 135 136 return $testdir . "/" . $server . "/named.lock" if ($server =~ /^ns/); 137 return if ($server =~ /^ans/); 138 139 die "Unknown server type $server\n"; 140} 141 142# Return the full path to a given server's PID file. 143sub server_pid_file { 144 my ( $server ) = @_; 145 146 return $testdir . "/" . $server . "/named.pid" if ($server =~ /^ns/); 147 return $testdir . "/" . $server . "/ans.pid" if ($server =~ /^ans/); 148 149 die "Unknown server type $server\n"; 150} 151 152# Read a PID. 153sub read_pid { 154 my ( $pid_file ) = @_; 155 156 return unless -f $pid_file; 157 # we don't really care about the race condition here 158 my $result = open(my $fh, "<", $pid_file); 159 if (!defined($result)) { 160 print "I:$test:$pid_file: $!\n"; 161 unlink $pid_file; 162 return; 163 } 164 165 my $pid = <$fh>; 166 return unless defined($pid); 167 168 chomp($pid); 169 return $pid; 170} 171 172# Stop a named process with rndc. 173sub stop_rndc { 174 my ( $server, $port ) = @_; 175 my $n; 176 177 if ($server =~ /^ns(\d+)/) { 178 $n = $1; 179 } else { 180 die "unable to parse server number from name \"$server\"\n"; 181 } 182 183 my $ip = "10.53.0.$n"; 184 my $how = $halt ? "halt" : "stop"; 185 186 # Ugly, but should work. 187 system("$RNDC -c ../common/rndc.conf -s $ip -p $port $how | sed 's/^/I:$test:$server /'"); 188 return; 189} 190 191sub server_died { 192 my ( $server, $signal ) = @_; 193 194 print "I:$test:$server died before a SIG$signal was sent\n"; 195 $errors = 1; 196 197 my $pid_file = server_pid_file($server); 198 unlink($pid_file); 199 200 return; 201} 202 203sub send_signal { 204 my ( $signal, $pid, $ans ) = @_; 205 206 if (! defined $ans) { 207 $ans = 0; 208 } 209 210 my $result = 0; 211 212 if (!$ans && ($^O eq 'cygwin' || $^O eq 'msys')) { 213 my $killout = `/bin/kill -f -$signal $pid 2>&1`; 214 chomp($killout); 215 $result = 1 if ($killout eq ''); 216 } else { 217 $result = kill $signal, $pid; 218 } 219 return $result; 220} 221 222# Stop a server by sending a signal to it. 223sub stop_signal { 224 my ( $server, $signal, $ans ) = @_; 225 if (! defined $ans) { 226 $ans = 0; 227 } 228 229 my $pid_file = server_pid_file($server); 230 my $pid = read_pid($pid_file); 231 232 return unless defined($pid); 233 234 # Send signal to the server, and bail out if signal can't be sent 235 if (send_signal($signal, $pid, $ans) != 1) { 236 server_died($server, $signal); 237 return; 238 } 239 240 return; 241} 242 243sub pid_file_exists { 244 my ( $server ) = @_; 245 246 my $pid_file = server_pid_file($server); 247 my $pid = read_pid($pid_file); 248 249 return unless defined($pid); 250 251 # If we're here, the PID file hasn't been cleaned up yet 252 if (send_signal(0, $pid) == 0) { 253 # XXX: on windows this is likely to result in a 254 # false positive, so don't bother reporting the error. 255 if (!defined($ENV{'CYGWIN'})) { 256 print "I:$test:$server crashed on shutdown\n"; 257 $errors = 1; 258 } 259 return; 260 } 261 262 return $server; 263} 264 265sub lock_file_exists { 266 my ( $server ) = @_; 267 my $lock_file = server_lock_file($server); 268 269 return unless defined($lock_file) && -f $lock_file; 270 271 return $server; 272} 273 274sub wait_for_servers { 275 my ( $timeout, @servers ) = @_; 276 277 while ($timeout > 0 && @servers > 0) { 278 sleep 1 if (@servers > 0); 279 @servers = 280 grep { defined($_) } 281 map { pid_file_exists($_) || lock_file_exists($_) } @servers; 282 $timeout--; 283 } 284 285 return @servers; 286} 287