GetOptions ("target|t=s" => \$explicit_target);
# Variables for command line options
-my ($amt, @append, $bender, @chainloaders, $concat, $config_name_opt, $dhcp_tftp, $dump_opt, $dump_config, @exiton, $exiton_timeout, @expect_raw, $final_eol, $gen_only, $grub_config, $grub_prefix, $grub_preamble, $grub2_prolog, $grub2_config, $help, $ider, $interaction, $iprelay, $iprelay_cmd, $iso_image, $interactive, $kernel_opt, $make, $man, $netif, $no_file_gen, $off_opt, $on_opt, $pulsar, $pulsar_root, $qemu, $qemu_append, $qemu_flags_cmd, $remote_cmd, $remote_expect, $remote_expect_silent, $remote_expect_timeout, $reset, $reset_cmd, $reset_send, $rom_prefix, $rsync_flags, @scriptmod, $scons, $serial, $server, $stty, $tftp, $tftp_port, $uboot, %uboot_addr, $uboot_cmd, @uboot_init);
+my ($amt, @append, $bender, @chainloaders, $concat, $config_name_opt, $dhcp_tftp, $dump_opt, $dump_config, @exiton, $exiton_timeout, @expect_raw, $final_eol, $gen_only, $grub_config, $grub_prefix, $grub_preamble, $grub2_prolog, $grub2_config, $help, $ider, $interaction, $iprelay, $iprelay_cmd, $iso_image, $interactive, $kernel_opt, $make, $man, $netif, $no_file_gen, $off_opt, $on_opt, $pulsar, $pulsar_root, $qemu, $qemu_append, $qemu_flags_cmd, @remote_cmd, $remote_expect, $remote_expect_silent, $remote_expect_timeout, $reset, @reset_cmd, $reset_send, $rom_prefix, $rsync_flags, @scriptmod, $scons, $serial, $server, $stty, $tftp, $tftp_port, $uboot, %uboot_addr, $uboot_cmd, @uboot_init);
my ($target_reset, $target_power_on, $target_power_off);
"qemu|Q:s" => \$qemu,
"qemu-append=s" => \$qemu_append,
"qemu-flags|q=s" => \$qemu_flags_cmd,
- "remote-cmd=s" => \$remote_cmd,
+ "remote-cmd=s" => sub { @remote_cmd = ($_[1]); },
"remote-expect=s"=> \$remote_expect,
"remote-expect-silent=s"=> sub { $remote_expect=$_[1]; $remote_expect_silent=1; },
"remote-expect-timeout=i"=> \$remote_expect_timeout,
"reset!" => \$reset,
- "reset-cmd=s" => \$reset_cmd,
+ "reset-cmd=s" => sub { @reset_cmd = ($_[1]); },
"reset-send=s" => \$reset_send,
"rsync-flags=s" => \$rsync_flags,
"scons:s" => \$scons,
{
my ($n, $val) = @_;
my $xdg_runtime_dir = $ENV{XDG_RUNTIME_DIR} || '/var/run';
- my $ssh_ctl_path = "${xdg_runtime_dir}/novaboot$$-%r@%h%p";
+ my $ssh_ctl_path = "${xdg_runtime_dir}/novaboot$$";
- $remote_cmd = "ssh -tt -M -S '${ssh_ctl_path}' '${val}' console";
+ @remote_cmd = ('ssh', '-tt', '-M', '-S', $ssh_ctl_path, $val, 'console');
$remote_expect = "novaboot-shell: Connected";
$server = "$val:";
$rsync_flags = "--rsh='ssh -S \'${ssh_ctl_path}\''";
($grub_prefix = $val) =~ s|(.*)@.*|\/$1\/| if index($val, '@') != -1;
- $reset_cmd = "ssh -tt -S '${ssh_ctl_path}' '${val}' reset";
+ @reset_cmd = ('ssh', '-tt', '-S', $ssh_ctl_path, $val, 'reset');
- $target_power_off = sub { system_verbose("ssh -tt -S '${ssh_ctl_path}' '${val}' off"); };
- $target_power_on = sub { system_verbose("ssh -tt -S '${ssh_ctl_path}' '${val}' on"); };
+ $target_power_off = sub { system_verbose('ssh', '-tt', '-S', $ssh_ctl_path, $val, 'off'); };
+ $target_power_on = sub { system_verbose('ssh', '-tt', '-S', $ssh_ctl_path, $val, 'on'); };
my $cmd = "ssh '${val}' get-config";
print STDERR "novaboot: Running: $cmd\n";
my %input_opts = ('--iprelay' => \$iprelay,
'--iprelay-cmd'=> \$iprelay_cmd,
'--serial' => \$serial,
- '--remote-cmd' => \$remote_cmd,
+ '--remote-cmd' => (@remote_cmd ? \$remote_cmd[0] : undef),
'--amt' => \$amt);
my @opts = grep(defined(${$input_opts{$_}}) , keys %input_opts);
sub shell_cmd_string(@)
{
- return join(' ', map((/^[-_=a-zA-Z0-9\/\.\+]+$/ ? "$_" : "'$_'"), @_));
+ if (scalar(@_) == 1) {
+ return $_[0];
+ } else {
+ return join(' ', map((/^[-_=a-zA-Z0-9\/\.\+]+$/ ? "$_" : "'$_'"), @_));
+ }
}
sub exec_verbose(@)
my $exp; # Expect object to communicate with the target over serial line
+sub kill_exp_on_signal() {
+ # Sometimes, under unclear circumstances (e.g. when running under
+ # both Jenkins and Robotframework), novaboot does not terminate
+ # console command when killed. The console is then blocked by the
+ # stale process forever. Theoretically, this should not happen,
+ # because when novaboot is killed, console command's controlling
+ # terminal sends SIGHUP to the console command and the command
+ # should terminate. It seems that at least SSH sometimes ignores
+ # HUP and does not terminate. The code below seems to work around
+ # that problem by killing the process immediately with SIGTERM,
+ # which is not ignored.
+
+ sub kill_console { kill TERM => $exp->pid if $exp->pid; die "Terminated by SIG$_[0]"; };
+
+ # For our Jenkins/Robotframework use case, it was sufficient to
+ # handle the TERM signal, but to be on the safe side, we also
+ # catch other signals.
+ $SIG{TERM} = \&kill_console;
+ $SIG{HUP} = \&kill_console;
+ $SIG{INT} = \&kill_console;
+ $SIG{QUIT} = \&kill_console;
+}
+
+
my ($amt_user, $amt_password, $amt_host, $amt_port);
if (defined $iprelay || defined $iprelay_cmd) {
$exp = new Expect;
$exp->raw_pty(1);
$exp->spawn($iprelay_cmd);
+ kill_exp_on_signal();
}
while (1) {
open($CONN, "+<", $serial) || die "open $serial: $!";
$exp = Expect->init(\*$CONN);
}
-elsif ($remote_cmd) {
- print STDERR "novaboot: Running: $remote_cmd\n";
- $exp = Expect->spawn($remote_cmd);
+elsif (@remote_cmd) {
+ print STDERR "novaboot: Running: ".shell_cmd_string(@remote_cmd)."\n";
+ $exp = Expect->spawn(@remote_cmd);
+ kill_exp_on_signal();
}
elsif (defined $amt) {
require LWP::UserAgent;
}
}
-if (defined $reset_cmd) {
+if (@reset_cmd) {
$target_reset = sub {
- system_verbose($reset_cmd);
+ system_verbose(@reset_cmd);
};
}
}
}
+# When exp-spawned command ignores SIGHUP, Expect waits 5 seconds
+# before killing it. We kill it by SIGTERM immediately.
+kill TERM => $exp->pid if defined $exp && $exp->pid;
+
## Kill dhcpc or tftpd
if (defined $dhcp_tftp || defined $tftp) {
die("novaboot: This should kill servers on background\n");