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