]> rtime.felk.cvut.cz Git - can-eth-gw.git/blobdiff - bench/novaboot
updated kernel module benchmark, added endianness conversion
[can-eth-gw.git] / bench / novaboot
diff --git a/bench/novaboot b/bench/novaboot
new file mode 100755 (executable)
index 0000000..bc52ee3
--- /dev/null
@@ -0,0 +1,1077 @@
+#!/usr/bin/perl -w\r
+\r
+use strict;\r
+use warnings;\r
+use Getopt::Long qw(GetOptionsFromString);\r
+use Pod::Usage;\r
+use File::Basename;\r
+use File::Spec;\r
+use IO::Handle;\r
+use Time::HiRes("usleep");\r
+use Socket;\r
+use FileHandle;\r
+use IPC::Open2;\r
+use POSIX qw(:errno_h);\r
+use Cwd;\r
+\r
+# always flush\r
+$| = 1;\r
+\r
+my $invocation_dir = getcwd();\r
+\r
+chomp(my $gittop = `git rev-parse --show-toplevel 2>/dev/null`);\r
+\r
+# Default configuration\r
+$CFG::hypervisor = "bin/apps/hypervisor";\r
+$CFG::hypervisor_params = "serial";\r
+$CFG::server = "erwin.inf.tu-dresden.de:boot/novaboot/\$NAME";\r
+$CFG::server_grub_prefix = "(nd)/tftpboot/sojka/novaboot/\$NAME";\r
+$CFG::grub_keys = ''; #"/novaboot\n\n/\$NAME\n\n";\r
+$CFG::grub2_prolog = "  set root='(hd0,msdos1)'";\r
+$CFG::genisoimage = "genisoimage";\r
+$CFG::iprelay_addr = '141.76.48.80:2324'; #'141.76.48.252';\r
+$CFG::qemu = 'qemu';\r
+$CFG::script_modifier = ''; # Depricated, use --scriptmod commandline option or custom_options.\r
+@CFG::chainloaders = (); #('bin/boot/bender promisc');\r
+$CFG::pulsar_root = '';\r
+$CFG::pulsar_mac = '52-54-00-12-34-56';\r
+%CFG::custom_options = ('I' => '--server=erwin.inf.tu-dresden.de:~passive/boot --rsync-flags="--chmod=Dg+s,ug+w,o-w,+rX --rsync-path=\"umask 002 && rsync\"" --grub-prefix=(nd)/tftpboot/passive/ --iprelay=141.76.48.80:2324 --scriptmod=s/\\\\bhostserial\\\\b/hostserialpci/g',\r
+                       'J' => '--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');\r
+$CFG::scons = "scons -j2";\r
+\r
+my @qemu_flags = qw(-cpu coreduo -smp 2);\r
+sub read_config($) {\r
+    my ($cfg) = @_;\r
+    {\r
+       package CFG; # Put config data into a separate namespace\r
+       my $rc = do($cfg);\r
+\r
+       # Check for errors\r
+       if ($@) {\r
+           die("ERROR: Failure compiling '$cfg' - $@");\r
+       } elsif (! defined($rc)) {\r
+           die("ERROR: Failure reading '$cfg' - $!");\r
+       } elsif (! $rc) {\r
+           die("ERROR: Failure processing '$cfg'");\r
+       }\r
+    }\r
+}\r
+\r
+my $cfg = $ENV{'NOVABOOT_CONFIG'} || $ENV{'HOME'}."/.novaboot";\r
+Getopt::Long::Configure(qw/no_ignore_case pass_through/);\r
+GetOptions ("config|c=s" => \$cfg);\r
+if (-s $cfg) { read_config($cfg); }\r
+\r
+# Command line\r
+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, $rsync_flags, @scriptmod, $scons, $serial, $server);\r
+\r
+$rsync_flags = '';\r
+\r
+Getopt::Long::Configure(qw/no_ignore_case no_pass_through/);\r
+my %opt_spec = (\r
+    "append|a=s"     => \$append,\r
+    "bender|b"       => \$bender,\r
+    "build-dir=s"    => \$builddir,\r
+    "dhcp-tftp|d"    => \$dhcp_tftp,\r
+    "dump"          => \$dump_opt,\r
+    "dump-config"    => \$dump_config,\r
+    "grub|g:s"              => \$grub_config,\r
+    "grub-prefix=s"  => \$grub_prefix,\r
+    "grub2:s"       => \$grub2_config,\r
+    "iprelay:s"             => \$iprelay,\r
+    "iso|i:s"       => \$iso_image,\r
+    "name=s"        => \$config_name_opt,\r
+    "no-file-gen"    => \$no_file_gen,\r
+    "off"           => \$off_opt,\r
+    "on"            => \$on_opt,\r
+    "pulsar|p:s"     => \$pulsar,\r
+    "qemu|Q=s"              => \$qemu,\r
+    "qemu-append=s"  => \$qemu_append,\r
+    "qemu-flags|q=s" => \$qemu_flags_cmd,\r
+    "rsync-flags=s"  => \$rsync_flags,\r
+    "scons:s"       => \$scons,\r
+    "scriptmod=s"    => \@scriptmod,\r
+    "serial|s:s"     => \$serial,\r
+    "server:s"              => \$server,\r
+    "h"             => \$help,\r
+    "help"          => \$man,\r
+    );\r
+foreach my $opt(keys(%CFG::custom_options)) {\r
+    $opt_spec{$opt} = sub { GetOptionsFromString($CFG::custom_options{$opt}, %opt_spec); };\r
+}\r
+GetOptions %opt_spec or pod2usage(2);\r
+pod2usage(1) if $help;\r
+pod2usage(-exitstatus => 0, -verbose => 2) if $man;\r
+\r
+$CFG::iprelay_addr = $ENV{'NOVABOOT_IPRELAY'} if $ENV{'NOVABOOT_IPRELAY'};\r
+if ($iprelay && $iprelay ne "on" && $iprelay ne "off") { $CFG::iprelay_addr = $iprelay; }\r
+\r
+if (defined $config_name_opt && scalar(@ARGV) > 1) { die "You cannot use --name with multiple scripts"; }\r
+\r
+if ($server) { $CFG::server = $server; }\r
+if ($qemu) { $CFG::qemu = $qemu; }\r
+$qemu_append ||= '';\r
+\r
+if (defined $pulsar) {\r
+    $CFG::pulsar_mac = $pulsar;\r
+}\r
+\r
+if ($scons) { $CFG::scons = $scons; }\r
+if (!@scriptmod && $CFG::script_modifier) { @scriptmod = ( $CFG::script_modifier ); }\r
+if (defined $grub_prefix) { $CFG::server_grub_prefix = $grub_prefix; }\r
+\r
+if ($dump_config) {\r
+    use Data::Dumper;\r
+    $Data::Dumper::Indent=0;\r
+    print "# This file is in perl syntax.\n";\r
+    foreach my $key(sort(keys(%CFG::))) { # See "Symbol Tables" in perlmod(1)\r
+       if (defined ${$CFG::{$key}}) { print Data::Dumper->Dump([${$CFG::{$key}}], ["*$key"]); }\r
+       if (defined @{$CFG::{$key}}) { print Data::Dumper->Dump([\@{$CFG::{$key}}], ["*$key"]); }\r
+       if (        %{$CFG::{$key}}) { print Data::Dumper->Dump([\%{$CFG::{$key}}], ["*$key"]); }\r
+       print "\n";\r
+    }\r
+    print "1;\n";\r
+    exit;\r
+}\r
+\r
+if (defined $serial) {\r
+    $serial ||= "/dev/ttyUSB0";\r
+}\r
+\r
+if (defined $grub_config) {\r
+    $grub_config ||= "menu.lst";\r
+}\r
+\r
+if (defined $grub2_config) {\r
+    $grub2_config ||= "grub.cfg";\r
+}\r
+\r
+if ($on_opt) { $iprelay="on"; }\r
+if ($off_opt) { $iprelay="off"; }\r
+\r
+# Parse the config(s)\r
+my @scripts;\r
+my $file;\r
+my $line;\r
+my $EOF;\r
+my $last_fn = '';\r
+my ($modules, $variables, $generated, $continuation);\r
+while (<>) {\r
+    if ($ARGV ne $last_fn) { # New script\r
+       die "Missing EOF in $last_fn" if $file;\r
+       die "Unfinished line in $last_fn" if $line;\r
+       $last_fn = $ARGV;\r
+       push @scripts, { 'filename' => $ARGV,\r
+                        'modules' => $modules = [],\r
+                        'variables' => $variables = {},\r
+                        'generated' => $generated = []};\r
+\r
+    }\r
+    chomp();\r
+    next if /^#/ || /^\s*$/;   # Skip comments and empty lines\r
+\r
+    foreach my $mod(@scriptmod) { eval $mod; }\r
+\r
+    print "$_\n" if $dump_opt;\r
+\r
+    if (/^([A-Z_]+)=(.*)$/) {  # Internal variable\r
+       $$variables{$1} = $2;\r
+       next;\r
+    }\r
+    if (/^([^ ]*)(.*?)[[:space:]]*<<([^ ]*)$/) { # Heredoc start\r
+       push @$modules, "$1$2";\r
+       $file = [];\r
+       push @$generated, {filename => $1, content => $file};\r
+       $EOF = $3;\r
+       next;\r
+    }\r
+    if ($file && $_ eq $EOF) { # Heredoc end\r
+       undef $file;\r
+       next;\r
+    }\r
+    if ($file) {               # Heredoc content\r
+       push @{$file}, "$_\n";\r
+       next;\r
+    }\r
+    $_ =~ s/^[[:space:]]*// if ($continuation);\r
+    if (/\\$/) {               # Line continuation\r
+       $line .= substr($_, 0, length($_)-1);\r
+       $continuation = 1;\r
+       next;\r
+    }\r
+    $continuation = 0;\r
+    $line .= $_;\r
+    $line .= " $append" if ($append && scalar(@$modules) == 0);\r
+\r
+    if ($line =~ /^([^ ]*)(.*?)[[:space:]]*< ?(.*)$/) { # Command substitution\r
+       push @$modules, "$1$2";\r
+       push @$generated, {filename => $1, command => $3};\r
+       $line = '';\r
+       next;\r
+    }\r
+    push @$modules, $line;\r
+    $line = '';\r
+}\r
+#use Data::Dumper;\r
+#print Dumper(\@scripts);\r
+\r
+exit if $dump_opt;\r
+\r
+sub generate_configs($$$) {\r
+    my ($base, $generated, $filename) = @_;\r
+    if ($base) { $base = "$base/"; };\r
+    foreach my $g(@$generated) {\r
+      if (exists $$g{content}) {\r
+       my $config = $$g{content};\r
+       my $fn = $$g{filename};\r
+       open(my $f, '>', $fn) || die("$fn: $!");\r
+       map { s|\brom://([^ ]*)|rom://$base$1|g; print $f "$_"; } @{$config};\r
+       close($f);\r
+       print "novaboot: Created $fn\n";\r
+      } elsif (exists $$g{command} && ! $no_file_gen) {\r
+       $ENV{SRCDIR} = dirname(File::Spec->rel2abs( $filename, $invocation_dir ));\r
+       system_verbose("( $$g{command} ) > $$g{filename}");\r
+      }\r
+    }\r
+}\r
+\r
+sub generate_grub_config($$$$;$)\r
+{\r
+    my ($filename, $title, $base, $modules_ref, $prepend) = @_;\r
+    if ($base) { $base = "$base/"; };\r
+    open(my $fg, '>', $filename) or die "$filename: $!";\r
+    print $fg "$prepend\n" if $prepend;\r
+    my $endmark = ($serial || defined $iprelay) ? ';' : '';\r
+    print $fg "title $title$endmark\n" if $title;\r
+    #print $fg "root $base\n"; # root doesn't really work for (nd)\r
+    my $first = 1;\r
+    foreach (@$modules_ref) {\r
+       if ($first) {\r
+           $first = 0;\r
+           my ($kbin, $kcmd) = split(' ', $_, 2);\r
+           $kcmd = '' if !defined $kcmd;\r
+           print $fg "kernel ${base}$kbin $kcmd\n";\r
+       } else {\r
+           s|\brom://([^ ]*)|rom://$base$1|g; # Translate rom:// files - needed for vdisk parameter of sigma0\r
+           print $fg "module $base$_\n";\r
+       }\r
+    }\r
+    close($fg);\r
+}\r
+\r
+sub generate_grub2_config($$$$;$)\r
+{\r
+    my ($filename, $title, $base, $modules_ref, $prepend) = @_;\r
+    if ($base && substr($base,-1,1) ne '/') { $base = "$base/"; };\r
+    open(my $fg, '>', $filename) or die "$filename: $!";\r
+    print $fg "$prepend\n" if $prepend;\r
+    my $endmark = ($serial || defined $iprelay) ? ';' : '';\r
+    $title ||= 'novaboot';\r
+    print $fg "menuentry $title$endmark {\n";\r
+    print $fg "$CFG::grub2_prolog\n";\r
+    my $first = 1;\r
+    foreach (@$modules_ref) {\r
+       if ($first) {\r
+           $first = 0;\r
+           my ($kbin, $kcmd) = split(' ', $_, 2);\r
+           $kcmd = '' if !defined $kcmd;\r
+           print $fg "  multiboot ${base}$kbin $kcmd\n";\r
+       } else {\r
+           my @args = split;\r
+           # GRUB2 doesn't pass filename in multiboot info so we have to duplicate it here\r
+           $_ = join(' ', ($args[0], @args));\r
+           #s|\brom://([^ ]*)|rom://$base$1|g; # Translate rom:// files - needed for vdisk parameter of sigma0\r
+           print $fg "  module $base$_\n";\r
+       }\r
+    }\r
+    print $fg "}\n";\r
+    close($fg);\r
+}\r
+\r
+sub generate_pulsar_config($$)\r
+{\r
+    my ($filename, $modules_ref) = @_;\r
+    open(my $fg, '>', $filename) or die "$filename: $!";\r
+    print $fg "root $CFG::pulsar_root\n" if $CFG::pulsar_root;\r
+    my $first = 1;\r
+    my ($kbin, $kcmd);\r
+    foreach (@$modules_ref) {\r
+       if ($first) {\r
+           $first = 0;\r
+           ($kbin, $kcmd) = split(' ', $_, 2);\r
+           $kcmd = '' if !defined $kcmd;\r
+       } else {\r
+           my @args = split;\r
+           print $fg "load $_\n";\r
+       }\r
+    }\r
+    # Put kernel as last - this is needed for booting Linux and has no influence on non-Linux OSes\r
+    print $fg "exec $kbin $kcmd\n";\r
+    close($fg);\r
+}\r
+\r
+sub exec_verbose(@)\r
+{\r
+    print "novaboot: Running: ".join(' ', map("'$_'", @_))."\n";\r
+    exec(@_);\r
+}\r
+\r
+sub system_verbose($)\r
+{\r
+    my $cmd = shift;\r
+    print "novaboot: Running: $cmd\n";\r
+    my $ret = system($cmd);\r
+    if ($ret & 0x007f) { die("Command terminated by a signal"); }\r
+    if ($ret & 0xff00) {die("Command exit with non-zero exit code"); }\r
+    if ($ret) { die("Command failure $ret"); }\r
+}\r
+\r
+if (exists $variables->{WVDESC}) {\r
+    print "Testing \"$variables->{WVDESC}\" in $last_fn:\n";\r
+} elsif ($last_fn =~ /\.wv$/) {\r
+    print "Testing \"all\" in $last_fn:\n";\r
+}\r
+\r
+my $IPRELAY;\r
+if (defined $iprelay) {\r
+    $CFG::iprelay_addr =~ /([.0-9]+)(:([0-9]+))?/;\r
+    my $addr = $1;\r
+    my $port = $3 || 23;\r
+    my $paddr   = sockaddr_in($port, inet_aton($addr));\r
+    my $proto   = getprotobyname('tcp');\r
+    socket($IPRELAY, PF_INET, SOCK_STREAM, $proto)  || die "socket: $!";\r
+    print "novaboot: Connecting to IP relay... ";\r
+    connect($IPRELAY, $paddr)    || die "connect: $!";\r
+    print "done\n";\r
+    $IPRELAY->autoflush(1);\r
+\r
+    while (1) {\r
+       print $IPRELAY "\xFF\xF6";\r
+       alarm(20);\r
+       local $SIG{ALRM} = sub { die "Relay AYT timeout"; };\r
+       my $ayt_reponse = "";\r
+       my $read = sysread($IPRELAY, $ayt_reponse, 100);\r
+       alarm(0);\r
+\r
+       chomp($ayt_reponse);\r
+       print "$ayt_reponse\n";\r
+       if ($ayt_reponse =~ /<iprelayd: not connected/) {\r
+           sleep(10);\r
+           next;\r
+       }\r
+       last;\r
+    }\r
+\r
+    sub relaycmd($$) {\r
+       my ($relay, $onoff) = @_;\r
+       die unless ($relay == 1 || $relay == 2);\r
+\r
+       my $cmd = ($relay == 1 ? 0x5 : 0x6) | ($onoff ? 0x20 : 0x10);\r
+       return "\xFF\xFA\x2C\x32".chr($cmd)."\xFF\xF0";\r
+    }\r
+\r
+    sub relayconf($$) {\r
+       my ($relay, $onoff) = @_;\r
+       die unless ($relay == 1 || $relay == 2);\r
+       my $cmd = ($relay == 1 ? 0xdf : 0xbf) | ($onoff ? 0x00 : 0xff);\r
+       return "\xFF\xFA\x2C\x97".chr($cmd)."\xFF\xF0";\r
+    }\r
+\r
+    sub relay($$;$) {\r
+       my ($relay, $onoff, $can_giveup) = @_;\r
+       my $confirmation = '';\r
+       print $IPRELAY relaycmd($relay, $onoff);\r
+\r
+       # We use non-blocking I/O and polling here because for some\r
+       # reason read() on blocking FD returns only after all\r
+       # requested data is available. If we get during the first\r
+       # read() only a part of confirmation, we may get the rest\r
+       # after the system boots and print someting, which may be too\r
+       # long.\r
+       $IPRELAY->blocking(0);\r
+\r
+       alarm(20); # Timeout in seconds\r
+       my $giveup = 0;\r
+        local $SIG{ALRM} = sub {\r
+           if ($can_giveup) { print("Relay confirmation timeout - ignoring\n"); $giveup = 1;}\r
+           else {die "Relay confirmation timeout";}\r
+       };\r
+       my $index;\r
+       while (($index=index($confirmation, relayconf($relay, $onoff))) < 0 && !$giveup) {\r
+           my $read = read($IPRELAY, $confirmation, 70, length($confirmation));\r
+           if (!defined($read)) {\r
+               die("IP relay: $!") unless $! == EAGAIN;\r
+               usleep(10000);\r
+               next;\r
+           }\r
+           #use MIME::QuotedPrint;\r
+           #print "confirmation = ".encode_qp($confirmation)."\n";\r
+       }\r
+       alarm(0);\r
+       $IPRELAY->blocking(1);\r
+    }\r
+}\r
+\r
+if ($iprelay && ($iprelay eq "on" || $iprelay eq "off")) {\r
+     relay(1, 1); # Press power button\r
+    if ($iprelay eq "on") {\r
+       usleep(100000);         # Short press\r
+    } else {\r
+       usleep(6000000);        # Long press to switch off\r
+    }\r
+    print $IPRELAY relay(1, 0);\r
+    exit;\r
+}\r
+\r
+if ($builddir) {\r
+    $CFG::builddir = $builddir;\r
+} else {\r
+    if (! defined $CFG::builddir) {\r
+       $CFG::builddir = ( $gittop || $ENV{'HOME'}."/nul" ) . "/build";\r
+       if (! -d $CFG::builddir) {\r
+           $CFG::builddir = $ENV{SRCDIR} = dirname(File::Spec->rel2abs( ${$scripts[0]}{filename}, $invocation_dir ));\r
+       }\r
+    }\r
+}\r
+\r
+chdir($CFG::builddir) or die "Can't change directory to $CFG::builddir: $!";\r
+print "novaboot: Entering directory `$CFG::builddir'\n";\r
+\r
+my (%files_iso, $menu_iso, $config_name, $filename);\r
+\r
+foreach my $script (@scripts) {\r
+    $filename = $$script{filename};\r
+    $modules = $$script{modules};\r
+    $generated = $$script{generated};\r
+    $variables = $$script{variables};\r
+    my ($server_grub_prefix);\r
+\r
+    if (defined $config_name_opt) {\r
+       $config_name = $config_name_opt;\r
+    } else {\r
+       ($config_name = $filename) =~ s#.*/##;\r
+    }\r
+\r
+    my $kernel;\r
+    if (exists $variables->{KERNEL}) {\r
+       $kernel = $variables->{KERNEL};\r
+    } else {\r
+       $kernel = $CFG::hypervisor . " ";\r
+       if (exists $variables->{HYPERVISOR_PARAMS}) {\r
+           $kernel .= $variables->{HYPERVISOR_PARAMS};\r
+       } else {\r
+           $kernel .= $CFG::hypervisor_params;\r
+       }\r
+    }\r
+    @$modules = ($kernel, @$modules);\r
+    @$modules = (@CFG::chainloaders, @$modules);\r
+    @$modules = ("bin/boot/bender", @$modules) if ($bender || defined $ENV{'NOVABOOT_BENDER'});\r
+\r
+    if (defined $grub_config) {\r
+       generate_configs("", $generated, $filename);\r
+       generate_grub_config($grub_config, $config_name, "", $modules);\r
+       print("GRUB menu created: $CFG::builddir/$grub_config\n");\r
+       exit;\r
+    }\r
+\r
+    if (defined $grub2_config && !defined $server) {\r
+       generate_configs('', $generated, $filename);\r
+       generate_grub2_config($grub2_config, $config_name, $CFG::builddir, $modules);\r
+       print("GRUB2 configuration created: $CFG::builddir/$grub2_config\n");\r
+       exit;\r
+    }\r
+\r
+    my $pulsar_config;\r
+    if (defined $pulsar) {\r
+       $pulsar_config = "config-$CFG::pulsar_mac";\r
+       generate_configs('', $generated, $filename);\r
+       generate_pulsar_config($pulsar_config, $modules);\r
+       if (!defined $server) {\r
+           print("Pulsar configuration created: $CFG::builddir/$pulsar_config\n");\r
+           exit;\r
+       }\r
+    }\r
+\r
+    if (defined $scons) {\r
+       my @files = map({ ($file) = m/([^ ]*)/; $file; } @$modules);\r
+       # Filter-out generated files\r
+       my @to_build = grep({ my $file = $_; !scalar(grep($file eq $$_{filename}, @$generated)) } @files);\r
+       system_verbose($CFG::scons." ".join(" ", @to_build));\r
+    }\r
+\r
+    if (defined $server) {\r
+       ($server_grub_prefix = $CFG::server_grub_prefix) =~ s/\$NAME/$config_name/;\r
+       ($server = $CFG::server)                         =~ s/\$NAME/$config_name/;\r
+       my $bootloader_config;\r
+       if ($grub2_config) {\r
+           generate_configs('', $generated, $filename);\r
+           $bootloader_config ||= "grub.cfg";\r
+           generate_grub2_config($grub2_config, $config_name, $server_grub_prefix, $modules);\r
+       } elsif (defined $pulsar) {\r
+           $bootloader_config = $pulsar_config;\r
+       } else {\r
+           generate_configs($server_grub_prefix, $generated, $filename);\r
+           $bootloader_config ||= "menu.lst";\r
+           if (!grep { $_ eq $bootloader_config } @$modules) {\r
+               generate_grub_config($bootloader_config, $config_name, $server_grub_prefix, $modules,\r
+                                    $server_grub_prefix eq $CFG::server_grub_prefix ? "timeout 0" : undef);\r
+           }\r
+       }\r
+       my ($hostname, $path) = split(":", $server, 2);\r
+       if (! defined $path) {\r
+           $path = $hostname;\r
+           $hostname = "";\r
+       }\r
+       my $files = "$bootloader_config " . join(" ", map({ ($file) = m/([^ ]*)/; $file; } @$modules));\r
+       my $combined_menu_lst = ($CFG::server =~ m|/\$NAME$|);\r
+       map({ my $file = (split)[0]; die "$file: $!" if ! -f $file; } @$modules);\r
+       my $istty = -t STDOUT && $ENV{'TERM'} ne "dumb";\r
+       my $progress = $istty ? "--progress" : "";\r
+       system_verbose("rsync $progress -RLp $rsync_flags $files $server");\r
+       my $cmd = "cd $path/.. && cat */menu.lst > menu.lst";\r
+       if ($combined_menu_lst) { system_verbose($hostname ? "ssh $hostname '$cmd'" : $cmd); }\r
+    }\r
+\r
+    if (defined $iso_image) {\r
+       generate_configs("(cd)", $generated, $filename);\r
+       my $menu;\r
+       generate_grub_config(\$menu, $config_name, "(cd)", $modules);\r
+       $menu_iso .= "$menu\n";\r
+       map { ($file,undef) = split; $files_iso{$file} = 1; } @$modules;\r
+    }\r
+}\r
+\r
+if (defined $iso_image) {\r
+    open(my $fh, ">menu-iso.lst");\r
+    print $fh "timeout 5\n\n$menu_iso";\r
+    close($fh);\r
+    my $files = "boot/grub/menu.lst=menu-iso.lst " . join(" ", map("$_=$_", keys(%files_iso)));\r
+    $iso_image ||= "$config_name.iso";\r
+    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");\r
+    print("ISO image created: $CFG::builddir/$iso_image\n");\r
+}\r
+\r
+######################################################################\r
+# Boot NOVA using various methods and send serial output to stdout\r
+######################################################################\r
+\r
+if (scalar(@scripts) > 1 && ( defined $dhcp_tftp || defined $serial || defined $iprelay)) {\r
+    die "You cannot do this with multiple scripts simultaneously";\r
+}\r
+\r
+if ($variables->{WVTEST_TIMEOUT}) {\r
+    print "wvtest: timeout ", $variables->{WVTEST_TIMEOUT}, "\n";\r
+}\r
+\r
+if (!(defined $dhcp_tftp || defined $serial || defined $iprelay || defined $server || defined $iso_image)) {\r
+    # Qemu\r
+    @qemu_flags = split(/ /, $variables->{QEMU_FLAGS}) if exists $variables->{QEMU_FLAGS};\r
+    @qemu_flags = split(/ /, $qemu_flags_cmd) if $qemu_flags_cmd;\r
+    push(@qemu_flags, split(/ /, $qemu_append));\r
+\r
+    if (defined $iso_image) {\r
+       # Boot NOVA with grub (and test the iso image)\r
+       push(@qemu_flags, ('-cdrom', "$config_name.iso"));\r
+    } else {\r
+       # Boot NOVA without GRUB\r
+\r
+       # Non-patched qemu doesn't like commas, but NUL can live with pluses instead of commans\r
+       foreach (@$modules) {s/,/+/g;}\r
+       generate_configs("", $generated, $filename);\r
+\r
+       my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);\r
+       $kcmd = '' if !defined $kcmd;\r
+       my $initrd = join ",", @$modules;\r
+\r
+       push(@qemu_flags, ('-kernel', $kbin, '-append', $kcmd));\r
+       push(@qemu_flags, ('-initrd', $initrd)) if $initrd;\r
+    }\r
+    push(@qemu_flags,  qw(-serial stdio)); # Redirect serial output (for collecting test restuls)\r
+    exec_verbose(($CFG::qemu,  '-name', $config_name, @qemu_flags));\r
+}\r
+\r
+my ($dhcpd_pid, $tftpd_pid);\r
+\r
+if (defined $dhcp_tftp)\r
+{\r
+    generate_configs("(nd)", $generated, $filename);\r
+    system_verbose('mkdir -p tftpboot');\r
+    generate_grub_config("tftpboot/os-menu.lst", $config_name, "(nd)", \@$modules, "timeout 0");\r
+    open(my $fh, '>', 'dhcpd.conf');\r
+    my $mac = `cat /sys/class/net/eth0/address`;\r
+    chomp $mac;\r
+    print $fh "subnet 10.23.23.0 netmask 255.255.255.0 {\r
+                     range 10.23.23.10 10.23.23.100;\r
+                     filename \"bin/boot/grub/pxegrub.pxe\";\r
+                     next-server 10.23.23.1;\r
+}\r
+host server {\r
+       hardware ethernet $mac;\r
+       fixed-address 10.23.23.1;\r
+}";\r
+    close($fh);\r
+    system_verbose("sudo ip a add 10.23.23.1/24 dev eth0;\r
+           sudo ip l set dev eth0 up;\r
+           sudo touch dhcpd.leases");\r
+\r
+    $dhcpd_pid = fork();\r
+    if ($dhcpd_pid == 0) {\r
+       # This way, the spawned server are killed when this script is killed.\r
+       exec_verbose("sudo dhcpd -d -cf dhcpd.conf -lf dhcpd.leases -pf dhcpd.pid");\r
+    }\r
+    $tftpd_pid = fork();\r
+    if ($tftpd_pid == 0) {\r
+       exec_verbose("sudo in.tftpd --foreground --secure -v -v -v $CFG::builddir");\r
+    }\r
+    $SIG{TERM} = sub { print "CHILDS KILLED\n"; kill 15, $dhcpd_pid, $tftpd_pid; };\r
+}\r
+\r
+if ($serial || defined $iprelay) {\r
+    my $CONN;\r
+    if (defined $iprelay) {\r
+       print "novaboot: Reseting the test box... ";\r
+       relay(2, 1, 1); # Reset the machine\r
+       usleep(100000);\r
+       relay(2, 0);\r
+       print "done\n";\r
+\r
+       $CONN = $IPRELAY;\r
+    } elsif ($serial) {\r
+       system("stty -F $serial raw -crtscts -onlcr 115200");\r
+       open($CONN, "+<", $serial) || die "open $serial: $!";\r
+       $CONN->autoflush(1);\r
+    }\r
+    if (!defined $dhcp_tftp && $CFG::grub_keys) {\r
+       # Control grub via serial line\r
+       print "Waiting for GRUB's serial output... ";\r
+       while (<$CONN>) {\r
+           if (/Press any key to continue/) { print $CONN "\n"; last; }\r
+       }\r
+       $CFG::grub_keys =~ s/\$NAME/$config_name;/;\r
+       my @characters = split(//, $CFG::grub_keys);\r
+       foreach (@characters) {\r
+           print $CONN $_;\r
+           usleep($_ eq "\n" ? 100000 : 10000);\r
+       }\r
+       print $CONN "\n";\r
+       print "done\n";\r
+    }\r
+    # Pass the NOVA output to stdout.\r
+    while (<$CONN>) {\r
+       print;\r
+    }\r
+    kill 15, $dhcpd_pid, $tftpd_pid if ($dhcp_tftp);\r
+    exit;\r
+}\r
+\r
+if (defined $dhcp_tftp) {\r
+    my $pid = wait();\r
+    if ($pid == $dhcpd_pid) { print "dhcpd exited!\n"; }\r
+    elsif ($pid == $tftpd_pid) { print "tftpd exited!\n"; }\r
+    else { print "wait returned: $pid\n"; }\r
+    kill(15, 0); # Kill current process group i.e. all remaining children\r
+}\r
+\r
+=head1 NAME\r
+\r
+novaboot - NOVA boot script interpreter\r
+\r
+=head1 SYNOPSIS\r
+\r
+B<novaboot> [ options ] [--] script...\r
+\r
+B<./script> [ options ]\r
+\r
+B<novaboot> --help\r
+\r
+=head1 DESCRIPTION\r
+\r
+This program makes it easier to boot NOVA or other operating system\r
+(OS) in different environments. It reads a so called novaboot script\r
+and uses it either to boot the OS in an emulator (e.g. in qemu) or to\r
+generate the configuration for a specific bootloader and optionally to\r
+copy the necessary binaries and other needed files to proper\r
+locations, perhaps on a remote server. In case the system is actually\r
+booted, its serial output is redirected to standard output if that is\r
+possible.\r
+\r
+A typical way of using novaboot is to make the novaboot script\r
+executable and set its first line to I<#!/usr/bin/env novaboot>. Then,\r
+booting a particular OS configuration becomes the same as executing a\r
+local program - the novaboot script.\r
+\r
+With C<novaboot> you can:\r
+\r
+=over 3\r
+\r
+=item 1.\r
+\r
+Run NOVA in Qemu. This is the default action when no other action is\r
+specified by command line switches. Thus running C<novaboot ./script>\r
+(or C<./script> as described above) will run Qemu with configuration\r
+specified in the I<script>.\r
+\r
+=item 2.\r
+\r
+Create a bootloader configuration file (currently supported\r
+bootloaders are GRUB, GRUB2 and Pulsar) and copy it with all files\r
+needed for booting another, perhaps remote, location.\r
+\r
+ ./script --server --iprelay\r
+\r
+This command copies files to a TFTP server specified in the\r
+configuration file and uses TCP/IP-controlled relay to reset the test\r
+box and receive its serial output.\r
+\r
+=item 3.\r
+\r
+Run DHCP and TFTP server on developer's machine to PXE-boot NOVA from\r
+it. E.g.\r
+\r
+ ./script --dhcp-tftp\r
+\r
+When a PXE-bootable machine is connected via Ethernet to developer's\r
+machine, it will boot the configuration described in I<script>.\r
+\r
+=item 4.\r
+\r
+Create bootable ISO images. E.g.\r
+\r
+ novaboot --iso -- script1 script2\r
+\r
+The created ISO image will have GRUB bootloader installed on it and\r
+the boot menu will allow selecting between I<script1> and I<script2>\r
+configurations.\r
+\r
+=back\r
+\r
+=head1 OPTIONS\r
+\r
+=over 8\r
+\r
+=item -a, --append=<parameters>\r
+\r
+Appends a string to the root task's command line.\r
+\r
+=item -b, --bender\r
+\r
+Boot bender tool before the kernel to find PCI serial ports.\r
+\r
+=item --build-dir=<directory>\r
+\r
+Overrides the default build directory location.\r
+\r
+The default build directory location is determined as follows:\r
+\r
+If there is a configuration file, the value specified in I<$builddir>\r
+variable is used. Otherwise, if the current working directory is\r
+inside git work tree and there is F<build> directory at the top of\r
+that tree, it is used. Otherwise, if directory F<~/nul/build> exists,\r
+it is used. Otherwise, it is the directory that contains the first\r
+processed novaboot script.\r
+\r
+=item -c, --config=<filename>\r
+\r
+Use a different configuration file than the default one (i.e.\r
+F<~/.novaboot>).\r
+\r
+=item -d, --dhcp-tftp\r
+\r
+Turns your workstation into a DHCP and TFTP server so that NOVA\r
+can be booted via PXE BIOS on a test machine directly connected by\r
+a plain Ethernet cable to your workstation.\r
+\r
+The DHCP and TFTP servers require root privileges and C<novaboot>\r
+uses C<sudo> command to obtain those. You can put the following to\r
+I</etc/sudoers> to allow running the necessary commands without\r
+asking for password.\r
+\r
+ Cmnd_Alias NOVABOOT = /bin/ip a add 10.23.23.1/24 dev eth0, /bin/ip l set dev eth0 up, /usr/sbin/dhcpd -d -cf dhcpd.conf -lf dhcpd.leases -pf dhcpd.pid, /usr/sbin/in.tftpd --foreground --secure -v -v -v *, /usr/bin/touch dhcpd.leases\r
+ your_login ALL=NOPASSWD: NOVABOOT\r
+\r
+=item --dump\r
+\r
+Prints the content of the novaboot script after removing comments and\r
+evaluating all I<--scriptmod> expressions.\r
+\r
+=item --dump-config\r
+\r
+Dumps current configuration to stdout end exits. Useful as an initial\r
+template for a configuration file.\r
+\r
+=item -g, --grub[=I<filename>]\r
+\r
+Generates grub menu file. If the I<filename> is not specified,\r
+F<menu.lst> is used. The I<filename> is relative to NUL build\r
+directory.\r
+\r
+=item --grub-prefix=I<prefix>\r
+\r
+Specifies I<prefix> that is put before every file in GRUB's menu.lst.\r
+This overrides the value of I<$server_grub_prefix> from the\r
+configuration file.\r
+\r
+=item --grub2[=I<filename>]\r
+\r
+Generate GRUB2 menuentry in I<filename>. If I<filename> is not\r
+specified grub.cfg is used. The content of the menuentry can be\r
+customized by I<$grub2_prolog> and I<$server_grub_prefix>\r
+configuration variables.\r
+\r
+In order to use the the generated menuentry on your development\r
+machine that uses GRUB2, append the following snippet to\r
+F</etc/grub.d/40_custom> file and regenerate your grub configuration,\r
+i.e. run update-grub on Debian/Ubuntu.\r
+\r
+  if [ -f /path/to/nul/build/grub.cfg ]; then\r
+    source /path/to/nul/build/grub.cfg\r
+  fi\r
+\r
+\r
+=item -h, --help\r
+\r
+Print short (B<-h>) or long (B<--help>) help.\r
+\r
+=item --iprelay[=addr or cmd]\r
+\r
+If no I<cmd> is given, use IP relay to reset the machine and to get\r
+the serial output. The IP address of the relay is given by I<addr>\r
+parameter if specified or by $iprelay_addr variable in the\r
+configuration file.\r
+\r
+If I<cmd> is one of "on" or "off", the IP relay is used to press power\r
+button for a short (in case of "on") or long (in case of "off") time.\r
+Then, novaboot exits.\r
+\r
+Note: This option is expected to work with HWG-ER02a IP relays.\r
+\r
+=item -i, --iso[=filename]\r
+\r
+Generates the ISO image that boots NOVA system via GRUB. If no filename\r
+is given, the image is stored under I<NAME>.iso, where I<NAME> is the name\r
+of novaboot script (see also B<--name>).\r
+\r
+=item -I\r
+\r
+This is an alias (see C<%custom_options> below) defined in the default\r
+configuration. When used, it causes novaboot to use Michal's remotely\r
+controllable test bed.\r
+\r
+=item -J\r
+\r
+This is an alias (see C<%custom_options> below) defined in the default\r
+configuration. When used, it causes novaboot to use another remotely\r
+controllable test bed.\r
+\r
+=item --on, --off\r
+\r
+Synonym for --iprelay=on/off.\r
+\r
+=item --name=I<string>\r
+\r
+Use the name I<string> instead of the name of the novaboot script.\r
+This name is used for things like a title of grub menu or for the\r
+server directory where the boot files are copied to.\r
+\r
+=item --no-file-gen\r
+\r
+Do not generate files on the fly (i.e. "<" syntax) except for the\r
+files generated via "<<WORD" syntax.\r
+\r
+=item -p, --pulsar[=mac]\r
+\r
+Generates pulsar bootloader configuration file whose name is based on\r
+the MAC address specified either on the command line or taken from\r
+I<.novaboot> configuration file.\r
+\r
+=item -Q, --qemu=I<qemu-binary>\r
+\r
+Use specific version of qemu binary. The default is 'qemu'.\r
+\r
+=item --qemu-append=I<flags>\r
+\r
+Append I<flags> to the default qemu flags (QEMU_FLAGS variable or\r
+C<-cpu coreduo -smp 2>).\r
+\r
+=item -q, --qemu-flags=I<flags>\r
+\r
+Replace the default qemu flags (QEMU_FLAGS variable or C<-cpu coreduo\r
+-smp 2>) with I<flags> specified here.\r
+\r
+=item --rsync-flags=I<flags>\r
+\r
+Specifies which I<flags> are appended to F<rsync> command line when\r
+copying files as a result of I<--server> option.\r
+\r
+=item --scons[=scons command]\r
+\r
+Runs I<scons> to build files that are not generated by novaboot\r
+itself.\r
+\r
+=item --scriptmod=I<perl expression>\r
+\r
+When novaboot script is read, I<perl expression> is executed for every\r
+line (in $_ variable). For example, C<novaboot\r
+--scriptmod=s/sigma0/omega6/g> replaces every occurrence of I<sigma0>\r
+in the script with I<omega6>.\r
+\r
+When this option is present, it overrides I<$script_modifier> variable\r
+from the configuration file, which has the same effect. If this option\r
+is given multiple times all expressions are evaluated in the command\r
+line order.\r
+\r
+=item --server[=[[user@]server:]path]\r
+\r
+Copy all files needed for booting to a server (implies B<-g> unless\r
+B<--grub2> is given). The files will be copied to the directory\r
+I<path>. If the I<path> contains string $NAME, it will be replaced\r
+with the name of the novaboot script (see also B<--name>).\r
+\r
+Additionally, if $NAME is the last component of the I<path>, a file\r
+named I<path>/menu.lst (with $NAME removed from the I<path>) will be\r
+created on the server by concatenating all I<path>/*/menu.lst (with\r
+$NAME removed from the I<path>) files found on the server.\r
+\r
+=item -s, --serial[=device]\r
+\r
+Use serial line to control GRUB bootloader and to get the serial\r
+output of the machine. The default value is /dev/ttyUSB0.\r
+\r
+=back\r
+\r
+=head1 NOVABOOT SCRIPT SYNTAX\r
+\r
+The syntax tries to mimic POSIX shell syntax. The syntax is defined with the following rules.\r
+\r
+Lines starting with "#" are ignored.\r
+\r
+Lines that end with "\" are concatenated with the following line after\r
+removal of the final "\" and leading whitespace of the following line.\r
+\r
+Lines in the form I<VARIABLE=...> (i.e. matching '^[A-Z_]+=' regular\r
+expression) assign values to internal variables. See VARIABLES\r
+section.\r
+\r
+Otherwise, the first word on the line represents the filename\r
+(relative to the build directory (see I<--build-dir>) of the module to\r
+load and the remaining words are passed as the command line\r
+parameters.\r
+\r
+When the line ends with "<<WORD" then the subsequent lines until the\r
+line containing only WORD are copied literally to the file named on\r
+that line.\r
+\r
+When the line ends with "< CMD" the command CMD is executed with\r
+C</bin/sh> and its standard output is stored in the file named on that\r
+line. The SRCDIR variable in CMD's environment is set to the absolute\r
+path of the directory containing the interpreted novaboot script.\r
+\r
+Example:\r
+  #!/usr/bin/env novaboot\r
+  WVDESC=Example program\r
+  bin/apps/sigma0.nul S0_DEFAULT script_start:1,1 \\r
+    verbose hostkeyb:0,0x60,1,12,2\r
+  bin/apps/hello.nul\r
+  hello.nulconfig <<EOF\r
+  sigma0::mem:16 name::/s0/log name::/s0/timer name::/s0/fs/rom ||\r
+  rom://bin/apps/hello.nul\r
+  EOF\r
+\r
+This example will load three modules: sigma0.nul, hello.nul and\r
+hello.nulconfig. sigma0 gets some command line parameters and\r
+hello.nulconfig file is generated on the fly from the lines between\r
+<<EOF and EOF.\r
+\r
+=head2 VARIABLES\r
+\r
+The following variables are interpreted in the novaboot script:\r
+\r
+=over 8\r
+\r
+=item WVDESC\r
+\r
+Description of the wvtest-compliant program.\r
+\r
+=item WVTEST_TIMEOUT\r
+\r
+The timeout in seconds for WvTest harness. If no complete line appears\r
+in the test output within the time specified here, the test fails. It\r
+is necessary to specify this for long running tests that produce no\r
+intermediate output.\r
+\r
+=item QEMU_FLAGS\r
+\r
+Use specific qemu flags (can be overriden by B<-q>).\r
+\r
+=item HYPERVISOR_PARAMS\r
+\r
+Parameters passed to hypervisor. The default value is "serial", unless\r
+overriden in configuration file.\r
+\r
+=item KERNEL\r
+\r
+The kernel to use instead of NOVA hypervisor specified in the\r
+configuration file. The value should contain the name of the kernel\r
+image as well as its command line parameters. If this variable is\r
+defined and non-empty, the variable HYPERVISOR_PARAMS is not used.\r
+\r
+=back\r
+\r
+=head1 CONFIGURATION FILE\r
+\r
+novaboot can read its configuration from ~/.novaboot file (or another\r
+file specified with B<-c> parameter or NOVABOOT_CONFIG environment\r
+variable). It is a file with perl syntax, which sets values of certain\r
+variables. The current configuration can be dumped with\r
+B<--dump-config> switch. Use\r
+\r
+    novaboot --dump-config > ~/.novaboot\r
+\r
+to create a default configuration file and modify it to your needs.\r
+Some configuration variables can be overriden by environment variables\r
+(see below) or by command line switches.\r
+\r
+Documentation of some configuration variables follows:\r
+\r
+=over 8\r
+\r
+=item @chainloaders\r
+\r
+Custom chainloaders to load before hypervisor and files specified in\r
+novaboot script. E.g. ('bin/boot/bender promisc', 'bin/boot/zapp').\r
+\r
+=item %custom_options\r
+\r
+Defines custom command line options that can serve as aliases for\r
+other options. E.g. 'S' => '--server=boot:/tftproot\r
+--serial=/dev/ttyUSB0'.\r
+\r
+=back\r
+\r
+=head1 ENVIRONMENT VARIABLES\r
+\r
+Some options can be specified not only via config file or command line\r
+but also through environment variables. Environment variables override\r
+the values from configuration file and command line parameters\r
+override the environment variables.\r
+\r
+=over 8\r
+\r
+=item NOVABOOT_CONFIG\r
+\r
+A name of default novaboot configuration file.\r
+\r
+=item NOVABOOT_BENDER\r
+\r
+Defining this variable has the same meaning as B<--bender> option.\r
+\r
+=item NOVABOOT_IPRELAY\r
+\r
+The IP address (and optionally the port) of the IP relay. This\r
+overrides $iprelay_addr variable from the configuration file.\r
+\r
+=back\r
+\r
+=head1 AUTHORS\r
+\r
+Michal Sojka <sojka@os.inf.tu-dresden.de>\r