#!/usr/bin/perl -w
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
use strict;
use warnings;
use Getopt::Long qw(GetOptionsFromString);
use Pod::Usage;
use File::Basename;
use File::Spec;
use IO::Handle;
use Time::HiRes("usleep");
use Socket;
use FileHandle;
use IPC::Open2;
use POSIX qw(:errno_h);
use Cwd;
# always flush
$| = 1;
my $invocation_dir = getcwd();
chomp(my $gittop = `git rev-parse --show-toplevel 2>/dev/null`);
## Configuration file handling
# Default configuration
$CFG::hypervisor = "";
$CFG::hypervisor_params = "serial";
$CFG::server = "erwin.inf.tu-dresden.de:boot/novaboot/\$NAME";
$CFG::server_grub_prefix = "(nd)/tftpboot/sojka/novaboot/\$NAME";
$CFG::grub_keys = ''; #"/novaboot\n\n/\$NAME\n\n";
$CFG::grub2_prolog = " set root='(hd0,msdos1)'";
$CFG::genisoimage = "genisoimage";
$CFG::iprelay_addr = '141.76.48.80:2324'; #'141.76.48.252';
$CFG::qemu = 'qemu';
$CFG::script_modifier = ''; # Depricated, use --scriptmod commandline option or targets.
@CFG::chainloaders = (); #('bin/boot/bender promisc');
$CFG::pulsar_root = '';
$CFG::pulsar_mac = '52-54-00-12-34-56';
%CFG::targets = (
"tud" => '--server=erwin.inf.tu-dresden.de:~sojka/boot/novaboot --rsync-flags="--chmod=Dg+s,ug+w,o-w,+rX --rsync-path=\"umask 002 && rsync\"" --grub --grub-prefix=(nd)/tftpboot/sojka/novaboot --iprelay=141.76.48.80:2324 --scriptmod=s/\\\\bhostserial\\\\b/hostserialpci/g',
"novabox" => '--server=rtime.felk.cvut.cz:/srv/tftp/novaboot --rsync-flags="--chmod=Dg+s,ug+w,o-w,+rX --rsync-path=\"umask 002 && rsync\"" --pulsar=novaboot --iprelay=147.32.86.92:2324',
"localhost" => '--scriptmod=s/console=tty[A-Z0-0,]// --grub2 --server=/boot/novaboot/$NAME',
);
$CFG::scons = "scons -j2";
my @qemu_flags = qw(-cpu coreduo -smp 2);
sub read_config($) {
my ($cfg) = @_;
{
package CFG; # Put config data into a separate namespace
my $rc = do($cfg);
# Check for errors
if ($@) {
die("ERROR: Failure compiling '$cfg' - $@");
} elsif (! defined($rc)) {
die("ERROR: Failure reading '$cfg' - $!");
} elsif (! $rc) {
die("ERROR: Failure processing '$cfg'");
}
}
}
my $cfg = $ENV{'NOVABOOT_CONFIG'} || $ENV{'HOME'}."/.novaboot";
Getopt::Long::Configure(qw/no_ignore_case pass_through/);
GetOptions ("config|c=s" => \$cfg);
if (-s $cfg) { read_config($cfg); }
## Command line handling
my ($append, $bender, $builddir, $config_name_opt, $dhcp_tftp, $dump_opt, $dump_config, $grub_config, $grub_prefix, $grub2_config, $help, $iprelay, $iso_image, $man, $no_file_gen, $off_opt, $on_opt, $pulsar, $qemu, $qemu_append, $qemu_flags_cmd, $rom_prefix, $rsync_flags, @scriptmod, $scons, $serial, $server);
$rsync_flags = '';
$rom_prefix = 'rom://';
Getopt::Long::Configure(qw/no_ignore_case no_pass_through/);
my %opt_spec;
%opt_spec = (
"append|a=s" => \$append,
"bender|b" => \$bender,
"build-dir=s" => \$builddir,
"dhcp-tftp|d" => \$dhcp_tftp,
"dump" => \$dump_opt,
"dump-config" => \$dump_config,
"grub|g:s" => \$grub_config,
"grub-prefix=s" => \$grub_prefix,
"grub2:s" => \$grub2_config,
"iprelay:s" => \$iprelay,
"iso|i:s" => \$iso_image,
"name=s" => \$config_name_opt,
"no-file-gen" => \$no_file_gen,
"off" => \$off_opt,
"on" => \$on_opt,
"pulsar|p:s" => \$pulsar,
"qemu|Q=s" => \$qemu,
"qemu-append:s" => \$qemu_append,
"qemu-flags|q=s" => \$qemu_flags_cmd,
"rsync-flags=s" => \$rsync_flags,
"scons:s" => \$scons,
"scriptmod=s" => \@scriptmod,
"serial|s:s" => \$serial,
"server:s" => \$server,
"strip-rom" => sub { $rom_prefix = ''; },
"target|t=s" => sub { my ($opt_name, $opt_value) = @_;
exists $CFG::targets{$opt_value} or die("Unknown target '$opt_value' (valid targets are: ".join(", ", sort keys(%CFG::targets)).")");
GetOptionsFromString($CFG::targets{$opt_value}, %opt_spec); },
"h" => \$help,
"help" => \$man,
);
GetOptions %opt_spec or pod2usage(2);
pod2usage(1) if $help;
pod2usage(-exitstatus => 0, -verbose => 2) if $man;
### Sanitize configuration
$CFG::iprelay_addr = $ENV{'NOVABOOT_IPRELAY'} if $ENV{'NOVABOOT_IPRELAY'};
if ($iprelay && $iprelay ne "on" && $iprelay ne "off") { $CFG::iprelay_addr = $iprelay; }
if (defined $config_name_opt && scalar(@ARGV) > 1) { die "You cannot use --name with multiple scripts"; }
if ($server) { $CFG::server = $server; }
if ($qemu) { $CFG::qemu = $qemu; }
$qemu_append ||= '';
if ($pulsar) {
$CFG::pulsar_mac = $pulsar;
}
if ($scons) { $CFG::scons = $scons; }
if (!@scriptmod && $CFG::script_modifier) { @scriptmod = ( $CFG::script_modifier ); }
if (defined $grub_prefix) { $CFG::server_grub_prefix = $grub_prefix; }
### Dump sanitized configuration (if requested)
if ($dump_config) {
use Data::Dumper;
$Data::Dumper::Indent=0;
print "# This file is in perl syntax.\n";
foreach my $key(sort(keys(%CFG::))) { # See "Symbol Tables" in perlmod(1)
if (defined ${$CFG::{$key}}) { print Data::Dumper->Dump([${$CFG::{$key}}], ["*$key"]); }
if ( @{$CFG::{$key}}) { print Data::Dumper->Dump([\@{$CFG::{$key}}], ["*$key"]); }
if ( %{$CFG::{$key}}) { print Data::Dumper->Dump([\%{$CFG::{$key}}], ["*$key"]); }
print "\n";
}
print "1;\n";
exit;
}
if (defined $serial) {
$serial ||= "/dev/ttyUSB0";
}
if (defined $grub_config) {
$grub_config ||= "menu.lst";
}
if (defined $grub2_config) {
$grub2_config ||= "grub.cfg";
}
if ($on_opt) { $iprelay="on"; }
if ($off_opt) { $iprelay="off"; }
## Parse the novaboot script(s)
my @scripts;
my $file;
my $line;
my $EOF;
my $last_fn = '';
my ($modules, $variables, $generated, $continuation);
while (<>) {
if ($ARGV ne $last_fn) { # New script
die "Missing EOF in $last_fn" if $file;
die "Unfinished line in $last_fn" if $line;
$last_fn = $ARGV;
push @scripts, { 'filename' => $ARGV,
'modules' => $modules = [],
'variables' => $variables = {},
'generated' => $generated = []};
}
chomp();
next if /^#/ || /^\s*$/; # Skip comments and empty lines
foreach my $mod(@scriptmod) { eval $mod; }
print "$_\n" if $dump_opt;
if (/^([A-Z_]+)=(.*)$/) { # Internal variable
$$variables{$1} = $2;
next;
}
if (/^([^ ]*)(.*?)[[:space:]]*<<([^ ]*)$/) { # Heredoc start
push @$modules, "$1$2";
$file = [];
push @$generated, {filename => $1, content => $file};
$EOF = $3;
next;
}
if ($file && $_ eq $EOF) { # Heredoc end
undef $file;
next;
}
if ($file) { # Heredoc content
push @{$file}, "$_\n";
next;
}
$_ =~ s/^[[:space:]]*// if ($continuation);
if (/\\$/) { # Line continuation
$line .= substr($_, 0, length($_)-1);
$continuation = 1;
next;
}
$continuation = 0;
$line .= $_;
$line .= " $append" if ($append && scalar(@$modules) == 0);
if ($line =~ /^([^ ]*)(.*?)[[:space:]]*< ?(.*)$/) { # Command substitution
push @$modules, "$1$2";
push @$generated, {filename => $1, command => $3};
$line = '';
next;
}
push @$modules, $line;
$line = '';
}
#use Data::Dumper;
#print Dumper(\@scripts);
exit if $dump_opt;
## Helper functions
sub generate_configs($$$) {
my ($base, $generated, $filename) = @_;
if ($base) { $base = "$base/"; };
foreach my $g(@$generated) {
if (exists $$g{content}) {
my $config = $$g{content};
my $fn = $$g{filename};
open(my $f, '>', $fn) || die("$fn: $!");
map { s|\brom://([^ ]*)|$rom_prefix$base$1|g; print $f "$_"; } @{$config};
close($f);
print "novaboot: Created $fn\n";
} elsif (exists $$g{command} && ! $no_file_gen) {
$ENV{SRCDIR} = dirname(File::Spec->rel2abs( $filename, $invocation_dir ));
system_verbose("( $$g{command} ) > $$g{filename}");
}
}
}
sub generate_grub_config($$$$;$)
{
my ($filename, $title, $base, $modules_ref, $prepend) = @_;
if ($base) { $base = "$base/"; };
open(my $fg, '>', $filename) or die "$filename: $!";
print $fg "$prepend\n" if $prepend;
my $endmark = ($serial || defined $iprelay) ? ';' : '';
print $fg "title $title$endmark\n" if $title;
#print $fg "root $base\n"; # root doesn't really work for (nd)
my $first = 1;
foreach (@$modules_ref) {
if ($first) {
$first = 0;
my ($kbin, $kcmd) = split(' ', $_, 2);
$kcmd = '' if !defined $kcmd;
print $fg "kernel ${base}$kbin $kcmd\n";
} else {
s|\brom://([^ ]*)|$rom_prefix$base$1|g; # Translate rom:// files - needed for vdisk parameter of sigma0
print $fg "module $base$_\n";
}
}
close($fg);
}
sub generate_grub2_config($$$$;$)
{
my ($filename, $title, $base, $modules_ref, $prepend) = @_;
if ($base && substr($base,-1,1) ne '/') { $base = "$base/"; };
open(my $fg, '>', $filename) or die "$filename: $!";
print $fg "$prepend\n" if $prepend;
my $endmark = ($serial || defined $iprelay) ? ';' : '';
$title ||= 'novaboot';
print $fg "menuentry $title$endmark {\n";
print $fg "$CFG::grub2_prolog\n";
my $first = 1;
foreach (@$modules_ref) {
if ($first) {
$first = 0;
my ($kbin, $kcmd) = split(' ', $_, 2);
$kcmd = '' if !defined $kcmd;
print $fg " multiboot ${base}$kbin $kcmd\n";
} else {
my @args = split;
# GRUB2 doesn't pass filename in multiboot info so we have to duplicate it here
$_ = join(' ', ($args[0], @args));
s|\brom://|$rom_prefix|g; # We do not need to translate path for GRUB2
print $fg " module $base$_\n";
}
}
print $fg "}\n";
close($fg);
}
sub generate_pulsar_config($$)
{
my ($filename, $modules_ref) = @_;
open(my $fg, '>', $filename) or die "$filename: $!";
print $fg "root $CFG::pulsar_root\n" if $CFG::pulsar_root;
my $first = 1;
my ($kbin, $kcmd);
foreach (@$modules_ref) {
if ($first) {
$first = 0;
($kbin, $kcmd) = split(' ', $_, 2);
$kcmd = '' if !defined $kcmd;
} else {
my @args = split;
s|\brom://|$rom_prefix|g;
print $fg "load $_\n";
}
}
# Put kernel as last - this is needed for booting Linux and has no influence on non-Linux OSes
print $fg "exec $kbin $kcmd\n";
close($fg);
}
sub exec_verbose(@)
{
print "novaboot: Running: ".join(' ', map("'$_'", @_))."\n";
exec(@_);
}
sub system_verbose($)
{
my $cmd = shift;
print "novaboot: Running: $cmd\n";
my $ret = system($cmd);
if ($ret & 0x007f) { die("Command terminated by a signal"); }
if ($ret & 0xff00) {die("Command exit with non-zero exit code"); }
if ($ret) { die("Command failure $ret"); }
}
## WvTest handline
if (exists $variables->{WVDESC}) {
print "Testing \"$variables->{WVDESC}\" in $last_fn:\n";
} elsif ($last_fn =~ /\.wv$/) {
print "Testing \"all\" in $last_fn:\n";
}
## Handle reset and power on/off
my $IPRELAY;
if (defined $iprelay) {
$CFG::iprelay_addr =~ /([.0-9]+)(:([0-9]+))?/;
my $addr = $1;
my $port = $3 || 23;
my $paddr = sockaddr_in($port, inet_aton($addr));
my $proto = getprotobyname('tcp');
socket($IPRELAY, PF_INET, SOCK_STREAM, $proto) || die "socket: $!";
print "novaboot: Connecting to IP relay... ";
connect($IPRELAY, $paddr) || die "connect: $!";
print "done\n";
$IPRELAY->autoflush(1);
while (1) {
print $IPRELAY "\xFF\xF6";
alarm(20);
local $SIG{ALRM} = sub { die "Relay AYT timeout"; };
my $ayt_reponse = "";
my $read = sysread($IPRELAY, $ayt_reponse, 100);
alarm(0);
chomp($ayt_reponse);
print "$ayt_reponse\n";
if ($ayt_reponse =~ /blocking(0);
alarm(20); # Timeout in seconds
my $giveup = 0;
local $SIG{ALRM} = sub {
if ($can_giveup) { print("Relay confirmation timeout - ignoring\n"); $giveup = 1;}
else {die "Relay confirmation timeout";}
};
my $index;
while (($index=index($confirmation, relayconf($relay, $onoff))) < 0 && !$giveup) {
my $read = read($IPRELAY, $confirmation, 70, length($confirmation));
if (!defined($read)) {
die("IP relay: $!") unless $! == EAGAIN;
usleep(10000);
next;
}
#use MIME::QuotedPrint;
#print "confirmation = ".encode_qp($confirmation)."\n";
}
alarm(0);
$IPRELAY->blocking(1);
}
}
if ($iprelay && ($iprelay eq "on" || $iprelay eq "off")) {
relay(1, 1); # Press power button
if ($iprelay eq "on") {
usleep(100000); # Short press
} else {
usleep(6000000); # Long press to switch off
}
print $IPRELAY relay(1, 0);
exit;
}
## Figure out the location of builddir and chdir() there
if ($builddir) {
$CFG::builddir = $builddir;
} else {
if (! defined $CFG::builddir) {
$CFG::builddir = ( $gittop || $ENV{'HOME'}."/nul" ) . "/build";
if (! -d $CFG::builddir) {
$CFG::builddir = $ENV{SRCDIR} = dirname(File::Spec->rel2abs( ${$scripts[0]}{filename}, $invocation_dir ));
}
}
}
chdir($CFG::builddir) or die "Can't change directory to $CFG::builddir: $!";
print "novaboot: Entering directory `$CFG::builddir'\n";
## File generation phase
my (%files_iso, $menu_iso, $config_name, $filename);
foreach my $script (@scripts) {
$filename = $$script{filename};
$modules = $$script{modules};
$generated = $$script{generated};
$variables = $$script{variables};
my ($server_grub_prefix);
($config_name = $filename) =~ s#.*/##;
$config_name = $config_name_opt if (defined $config_name_opt);
my $kernel;
if (exists $variables->{KERNEL}) {
$kernel = $variables->{KERNEL};
} else {
if ($CFG::hypervisor) {
$kernel = $CFG::hypervisor . " ";
if (exists $variables->{HYPERVISOR_PARAMS}) {
$kernel .= $variables->{HYPERVISOR_PARAMS};
} else {
$kernel .= $CFG::hypervisor_params;
}
}
}
@$modules = ($kernel, @$modules) if $kernel;
@$modules = (@CFG::chainloaders, @$modules);
@$modules = ("bin/boot/bender", @$modules) if ($bender || defined $ENV{'NOVABOOT_BENDER'});
### Generate bootloader configuration files
if (defined $grub_config) {
generate_configs("", $generated, $filename);
generate_grub_config($grub_config, $config_name, "", $modules);
print("GRUB menu created: $CFG::builddir/$grub_config\n");
exit;
}
if (defined $grub2_config && !defined $server) {
generate_configs('', $generated, $filename);
generate_grub2_config($grub2_config, $config_name, $CFG::builddir, $modules);
print("GRUB2 configuration created: $CFG::builddir/$grub2_config\n");
exit;
}
my $pulsar_config;
if (defined $pulsar) {
$pulsar_config = "config-$CFG::pulsar_mac";
generate_configs('', $generated, $filename);
generate_pulsar_config($pulsar_config, $modules);
if (!defined $server) {
print("Pulsar configuration created: $CFG::builddir/$pulsar_config\n");
exit;
}
}
### Run scons
if (defined $scons) {
my @files = map({ ($file) = m/([^ ]*)/; $file; } @$modules);
# Filter-out generated files
my @to_build = grep({ my $file = $_; !scalar(grep($file eq $$_{filename}, @$generated)) } @files);
system_verbose($CFG::scons." ".join(" ", @to_build));
}
### Copy files (using rsync)
if (defined $server) {
($server_grub_prefix = $CFG::server_grub_prefix) =~ s/\$NAME/$config_name/;
($server = $CFG::server) =~ s/\$NAME/$config_name/;
my $bootloader_config;
if ($grub2_config) {
generate_configs('', $generated, $filename);
$bootloader_config ||= "grub.cfg";
generate_grub2_config($grub2_config, $config_name, $server_grub_prefix, $modules);
} elsif (defined $pulsar) {
$bootloader_config = $pulsar_config;
} else {
generate_configs($server_grub_prefix, $generated, $filename);
$bootloader_config ||= "menu.lst";
if (!grep { $_ eq $bootloader_config } @$modules) {
generate_grub_config($bootloader_config, $config_name, $server_grub_prefix, $modules,
$server_grub_prefix eq $CFG::server_grub_prefix ? "timeout 0" : undef);
}
}
my ($hostname, $path) = split(":", $server, 2);
if (! defined $path) {
$path = $hostname;
$hostname = "";
}
my $files = "$bootloader_config " . join(" ", map({ ($file) = m/([^ ]*)/; $file; } @$modules));
my $combined_menu_lst = ($CFG::server =~ m|/\$NAME$|);
map({ my $file = (split)[0]; die "$file: $!" if ! -f $file; } @$modules);
my $istty = -t STDOUT && ($ENV{'TERM'} || 'dumb') ne 'dumb';
my $progress = $istty ? "--progress" : "";
system_verbose("rsync $progress -RLp $rsync_flags $files $server");
my $cmd = "cd $path/.. && cat */menu.lst > menu.lst";
if ($combined_menu_lst) { system_verbose($hostname ? "ssh $hostname '$cmd'" : $cmd); }
}
### Prepare ISO image generation
if (defined $iso_image) {
generate_configs("(cd)", $generated, $filename);
my $menu;
generate_grub_config(\$menu, $config_name, "(cd)", $modules);
$menu_iso .= "$menu\n";
map { ($file,undef) = split; $files_iso{$file} = 1; } @$modules;
}
}
## Generate ISO image
if (defined $iso_image) {
open(my $fh, ">menu-iso.lst");
print $fh "timeout 5\n\n$menu_iso";
close($fh);
my $files = "boot/grub/menu.lst=menu-iso.lst " . join(" ", map("$_=$_", keys(%files_iso)));
$iso_image ||= "$config_name.iso";
system_verbose("$CFG::genisoimage -R -b stage2_eltorito -no-emul-boot -boot-load-size 4 -boot-info-table -hide-rr-moved -J -joliet-long -o $iso_image -graft-points bin/boot/grub/ $files");
print("ISO image created: $CFG::builddir/$iso_image\n");
}
## Boot the system using various methods and send serial output to stdout
if (scalar(@scripts) > 1 && ( defined $dhcp_tftp || defined $serial || defined $iprelay)) {
die "You cannot do this with multiple scripts simultaneously";
}
if ($variables->{WVTEST_TIMEOUT}) {
print "wvtest: timeout ", $variables->{WVTEST_TIMEOUT}, "\n";
}
sub trim($) {
my ($str) = @_;
$str =~ s/^\s+|\s+$//g;
return $str
}
### Qemu
if (!(defined $dhcp_tftp || defined $serial || defined $iprelay || defined $server || defined $iso_image)) {
# Qemu
if (!$qemu && $variables->{QEMU}) {
@qemu_flags = split(" ", $variables->{QEMU});
$CFG::qemu = shift(@qemu_flags);
}
@qemu_flags = split(/ +/, trim($variables->{QEMU_FLAGS})) if exists $variables->{QEMU_FLAGS};
@qemu_flags = split(/ +/, trim($qemu_flags_cmd)) if $qemu_flags_cmd;
push(@qemu_flags, split(/ +/, trim($qemu_append)));
if (defined $iso_image) {
# Boot NOVA with grub (and test the iso image)
push(@qemu_flags, ('-cdrom', "$config_name.iso"));
} else {
# Boot NOVA without GRUB
# Non-patched qemu doesn't like commas, but NUL can live with pluses instead of commans
foreach (@$modules) {s/,/+/g;}
generate_configs("", $generated, $filename);
my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);
$kcmd = '' if !defined $kcmd;
my $dtb;
@$modules = map { if (/\.dtb$/) { $dtb=$_; (); } else { $_ } } @$modules;
my $initrd = join ",", @$modules;
push(@qemu_flags, ('-kernel', $kbin, '-append', $kcmd));
push(@qemu_flags, ('-initrd', $initrd)) if $initrd;
push(@qemu_flags, ('-dtb', $dtb)) if $dtb;
}
push(@qemu_flags, qw(-serial stdio)); # Redirect serial output (for collecting test restuls)
exec_verbose(($CFG::qemu, '-name', $config_name, @qemu_flags));
}
### Local DHCPD and TFTPD
my ($dhcpd_pid, $tftpd_pid);
if (defined $dhcp_tftp)
{
generate_configs("(nd)", $generated, $filename);
system_verbose('mkdir -p tftpboot');
generate_grub_config("tftpboot/os-menu.lst", $config_name, "(nd)", \@$modules, "timeout 0");
open(my $fh, '>', 'dhcpd.conf');
my $mac = `cat /sys/class/net/eth0/address`;
chomp $mac;
print $fh "subnet 10.23.23.0 netmask 255.255.255.0 {
range 10.23.23.10 10.23.23.100;
filename \"bin/boot/grub/pxegrub.pxe\";
next-server 10.23.23.1;
}
host server {
hardware ethernet $mac;
fixed-address 10.23.23.1;
}";
close($fh);
system_verbose("sudo ip a add 10.23.23.1/24 dev eth0;
sudo ip l set dev eth0 up;
sudo touch dhcpd.leases");
$dhcpd_pid = fork();
if ($dhcpd_pid == 0) {
# This way, the spawned server are killed when this script is killed.
exec_verbose("sudo dhcpd -d -cf dhcpd.conf -lf dhcpd.leases -pf dhcpd.pid");
}
$tftpd_pid = fork();
if ($tftpd_pid == 0) {
exec_verbose("sudo in.tftpd --foreground --secure -v -v -v $CFG::builddir");
}
$SIG{TERM} = sub { print "CHILDS KILLED\n"; kill 15, $dhcpd_pid, $tftpd_pid; };
}
### Serial line or IP relay
if ($serial || defined $iprelay) {
my $CONN;
if (defined $iprelay) {
print "novaboot: Reseting the test box... ";
relay(2, 1, 1); # Reset the machine
usleep(100000);
relay(2, 0);
print "done\n";
$CONN = $IPRELAY;
} elsif ($serial) {
system("stty -F $serial raw -crtscts -onlcr 115200");
open($CONN, "+<", $serial) || die "open $serial: $!";
$CONN->autoflush(1);
}
if (!defined $dhcp_tftp && $CFG::grub_keys) {
# Control grub via serial line
print "Waiting for GRUB's serial output... ";
while (<$CONN>) {
if (/Press any key to continue/) { print $CONN "\n"; last; }
}
$CFG::grub_keys =~ s/\$NAME/$config_name;/;
my @characters = split(//, $CFG::grub_keys);
foreach (@characters) {
print $CONN $_;
usleep($_ eq "\n" ? 100000 : 10000);
}
print $CONN "\n";
print "done\n";
}
# Pass the NOVA output to stdout.
while (<$CONN>) {
print;
}
kill 15, $dhcpd_pid, $tftpd_pid if ($dhcp_tftp);
exit;
}
### Wait for dhcpc or tftpd
if (defined $dhcp_tftp) {
my $pid = wait();
if ($pid == $dhcpd_pid) { print "dhcpd exited!\n"; }
elsif ($pid == $tftpd_pid) { print "tftpd exited!\n"; }
else { print "wait returned: $pid\n"; }
kill(15, 0); # Kill current process group i.e. all remaining children
}
## Documentation
=head1 NAME
novaboot - A tool for booting various operating systems on various hardware or in qemu
=head1 SYNOPSIS
B [ options ] [--] script...
B<./script> [ options ]
B --help
=head1 DESCRIPTION
This program makes it easier to boot NOVA or other operating system
(OS) in different environments. It reads a so called novaboot script
and uses it either to boot the OS in an emulator (e.g. in qemu) or to
generate the configuration for a specific bootloader and optionally to
copy the necessary binaries and other needed files to proper
locations, perhaps on a remote server. In case the system is actually
booted, its serial output is redirected to standard output if that is
possible.
A typical way of using novaboot is to make the novaboot script
executable and set its first line to I<#!/usr/bin/env novaboot>. Then,
booting a particular OS configuration becomes the same as executing a
local program - the novaboot script.
With C you can:
=over 3
=item 1.
Run an OS in Qemu. This is the default action when no other action is
specified by command line switches. Thus running C
(or C<./script> as described above) will run Qemu and make it boot the
configuration specified in the I