From 7ea6aa969779865cc296cc497e19e80fab958a21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Sat, 29 Aug 2026 18:32:12 -0300 Subject: [PATCH 1/4] fix(remoteshell): give the kill option its hyphen The fallback that runs when the restart of the ssh service fails collects the process ids of the daemon and kills them. The option is missing its hyphen, so the shell reads the signal number as one more process id. The daemons then get the default signal, which a process can catch, and the shell also signals the process with id 9. The next line of the same fallback already uses the correct form. --- xCAT/postscripts/remoteshell | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xCAT/postscripts/remoteshell b/xCAT/postscripts/remoteshell index 28f4a3dfa..54390fd93 100755 --- a/xCAT/postscripts/remoteshell +++ b/xCAT/postscripts/remoteshell @@ -648,7 +648,7 @@ fi #try to kill the process and start if [ "$?" != "0" ];then PIDLIST=`ps aux | grep -v grep | grep "/usr/sbin/sshd"|awk -F" " '{print $2}'|xargs` - [ -n "$PIDLIST" ] && kill 9 $PIDLIST + [ -n "$PIDLIST" ] && kill -9 $PIDLIST /usr/sbin/sshd fi kill -9 $CREDPID From 1f26a3b31868b8ddf8fda34c805abc65094422d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Sat, 29 Aug 2026 18:32:47 -0300 Subject: [PATCH 2/4] test(remoteshell): pin the signal that stops the ssh daemon Check that no kill in the postscript gives the signal number without a hyphen, and that the ssh daemon gets the signal that a process cannot catch. --- xCAT-test/unit/remoteshell_kill_signal.t | 26 ++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 xCAT-test/unit/remoteshell_kill_signal.t diff --git a/xCAT-test/unit/remoteshell_kill_signal.t b/xCAT-test/unit/remoteshell_kill_signal.t new file mode 100644 index 000000000..d10d15307 --- /dev/null +++ b/xCAT-test/unit/remoteshell_kill_signal.t @@ -0,0 +1,26 @@ +#!/usr/bin/env perl +use strict; +use warnings; + +use File::Spec; +use FindBin; +use Test::More; + +my $script = File::Spec->catfile( $FindBin::Bin, '..', '..', + 'xCAT', 'postscripts', 'remoteshell' ); +plan skip_all => 'remoteshell not found' unless -r $script; + +open( my $fh, '<', $script ) or die "Unable to read $script: $!"; +my $source = do { local $/; <$fh> }; +close($fh); + +# A kill without the hyphen reads the signal number as one more process id, so +# the target gets the default signal, which a process can catch, and the +# process with that id is signalled as well. +my @bare = ( $source =~ /^[^#\n]*\bkill\s+\d/gm ); +is( scalar(@bare), 0, 'no kill gives the signal number without a hyphen' ); + +like( $source, qr/kill -9 \$PIDLIST/, + 'the ssh daemon gets the signal that a process cannot catch' ); + +done_testing(); From f23745d18b2ab954d4e4def50109e73146c18d37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Sat, 29 Aug 2026 21:01:02 -0300 Subject: [PATCH 3/4] fix(remoteshell): wait for the ssh daemon to end before a new one starts A kill only asks the kernel to end a process, so it returns before the process is gone. The fallback started a new daemon on the next line, which could find the port still held by the old one, fail to start, and leave the node with no daemon at all. Wait for the processes to end, for up to ten seconds, before the new daemon starts. The test that a process is still there sends no signal. --- xCAT/postscripts/remoteshell | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/xCAT/postscripts/remoteshell b/xCAT/postscripts/remoteshell index 54390fd93..3378b9253 100755 --- a/xCAT/postscripts/remoteshell +++ b/xCAT/postscripts/remoteshell @@ -648,7 +648,19 @@ fi #try to kill the process and start if [ "$?" != "0" ];then PIDLIST=`ps aux | grep -v grep | grep "/usr/sbin/sshd"|awk -F" " '{print $2}'|xargs` - [ -n "$PIDLIST" ] && kill -9 $PIDLIST + if [ -n "$PIDLIST" ]; then + kill -9 $PIDLIST + waited=0 + while [ $waited -lt 10 ]; do + alive=0 + for pid in $PIDLIST; do + if kill -0 $pid 2>/dev/null; then alive=1; fi + done + if [ $alive -eq 0 ]; then break; fi + sleep 1 + waited=`expr $waited + 1` + done + fi /usr/sbin/sshd fi kill -9 $CREDPID From 8e2ebe90570d210eeef10b0152bde6cb22ec158d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Sat, 29 Aug 2026 21:01:50 -0300 Subject: [PATCH 4/4] test(remoteshell): pin the wait for the ssh daemon to end Run the wait loop of the postscript against real processes. A process that is still running is reported as still there, and the loop gives up instead of waiting for ever. A process that has ended is reported as gone, and no time is spent on it. The loop runs with a shorter bound so that the test does not spend the whole timeout, and the bound of the postscript is checked on its own. Cover that the check for a running process sends no signal, that the daemon still gets the signal it cannot catch, and that the wait comes before the new daemon starts. --- xCAT-test/unit/remoteshell_kill_wait.t | 62 ++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 xCAT-test/unit/remoteshell_kill_wait.t diff --git a/xCAT-test/unit/remoteshell_kill_wait.t b/xCAT-test/unit/remoteshell_kill_wait.t new file mode 100644 index 000000000..505fd2418 --- /dev/null +++ b/xCAT-test/unit/remoteshell_kill_wait.t @@ -0,0 +1,62 @@ +#!/usr/bin/env perl +use strict; +use warnings; + +use File::Spec; +use FindBin; +use Test::More; + +my $script = File::Spec->catfile( $FindBin::Bin, '..', '..', + 'xCAT', 'postscripts', 'remoteshell' ); +plan skip_all => 'remoteshell not found' unless -r $script; +plan skip_all => 'no bash' unless -x '/bin/bash'; + +open( my $fh, '<', $script ) or die "Unable to read $script: $!"; +my $source = do { local $/; <$fh> }; +close($fh); + +my ($loop) = $source =~ /(waited=0\n.*?\n done)/s; +ok( defined($loop), 'the wait loop was found in the postscript' ); + +# Run the loop of the postscript against real processes, with a shorter bound +# so that the test does not spend the whole timeout of the postscript. +sub waits_for { + my (@pids) = @_; + ( my $body = $loop ) =~ s/\$waited -lt 10/\$waited -lt 3/; + my $list = join( ' ', @pids ); + my $out = `/bin/bash -c 'PIDLIST="$list"\n$body\necho \$alive' 2>/dev/null`; + chomp $out; + return $out; +} + +# A process that is still running must be reported as alive, and the loop must +# give up rather than run for ever. +my $child = fork(); +if ( !defined $child ) { plan skip_all => 'cannot fork' } +if ( $child == 0 ) { exec( 'sleep', '30' ); exit 1 } +my $start = time; +is( waits_for($child), '1', 'a process that is still running is reported alive' ); +cmp_ok( time - $start, '<', 10, 'the loop gives up instead of waiting for ever' ); +kill 'KILL', $child; +waitpid( $child, 0 ); + +# A process that has ended must be reported as gone, without waiting. +my $gone = fork(); +if ( $gone == 0 ) { exit 0 } +waitpid( $gone, 0 ); +$start = time; +is( waits_for($gone), '0', 'a process that has ended is reported gone' ); +cmp_ok( time - $start, '<', 3, 'no time is spent once the process is gone' ); + +# The bound of the postscript itself, and the shape of the test. +like( $source, qr/while \[ \$waited -lt 10 \]/, 'the postscript waits at most ten seconds' ); +like( $source, qr/kill -0 \$pid/, 'the check for a running process sends no signal' ); +like( $source, qr/kill -9 \$PIDLIST/, 'the daemon still gets the signal it cannot catch' ); + +# The wait has to happen before the new daemon starts. +my ($block) = $source =~ /(PIDLIST=.*?\/usr\/sbin\/sshd)/s; +ok( defined($block), 'the fallback block was found' ); +cmp_ok( index( $block, 'waited=0' ), '<', index( $block, '/usr/sbin/sshd' ), + 'the wait comes before the new daemon starts' ); + +done_testing();