]> rtime.felk.cvut.cz Git - novaboot.git/blobdiff - novaboot
server: Drop novaboot-delayed-power-off.target
[novaboot.git] / novaboot
index 1f0366fbad43119d5a38776c85e7e7f834211a67..cd59748d35e6cb9633f345f6ce2d6d504ec4c149 100755 (executable)
--- a/novaboot
+++ b/novaboot
@@ -1,4 +1,4 @@
-#!/usr/bin/perl -w
+#!/usr/bin/env perl
 
 # 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
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+## Initialization
+
 use strict;
 use warnings;
-use warnings (exists $ENV{NOVABOOT_TEST} ? (FATAL => 'all') : ());
-use Getopt::Long qw(GetOptionsFromString);
+use warnings (exists $ENV{NOVABOOT_TEST} ?
+              (FATAL => 'all') :
+              (FATAL => qw(inplace))); # Open warnings in <<>> are fatal
+use Getopt::Long qw(GetOptionsFromString GetOptionsFromArray);
 use Pod::Usage;
 use File::Basename;
 use File::Spec;
+use File::Path qw(make_path);
 use IO::Handle;
 use Time::HiRes("usleep");
 use Socket;
 use FileHandle;
 use IPC::Open2;
-use POSIX qw(:errno_h);
+use POSIX qw(:errno_h sysconf);
 use Cwd qw(getcwd abs_path);
 use Expect;
 
 # always flush
 $| = 1;
 
-my $invocation_dir = getcwd();
+my $invocation_dir = $ENV{PWD} || getcwd();
+
+# We prefer using PWD, to use nicer paths names with symbolic links.
+# However, when executed from 'make -C dir', PWD may contain the make
+# invocation path, not the real invocation path with dir at the end.
+# We fix that here.
+if (abs_path($ENV{PWD}) ne abs_path(getcwd())) {
+    $invocation_dir = getcwd();
+}
 
 ## Configuration file handling
 
@@ -40,20 +53,27 @@ my $invocation_dir = getcwd();
 $CFG::hypervisor = "";
 $CFG::hypervisor_params = "serial";
 $CFG::genisoimage = "genisoimage";
-$CFG::qemu = 'qemu -cpu coreduo -smp 2';
-$CFG::default_target = 'qemu';
+$CFG::qemu = 'qemu-system-i386 -cpu coreduo -smp 2';
+$CFG::default_target = '';
+$CFG::netif = 'eth0';
 %CFG::targets = (
     'qemu' => '--qemu',
-    "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 --grub-preamble="timeout 0" --concat --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 --iprelay=147.32.86.92:2324',
-    "localhost" => '--scriptmod=s/console=tty[A-Z0-9,]+// --server=/boot/novaboot/$NAME --grub2 --grub-prefix=/boot/novaboot/$NAME --grub2-prolog="  set root=\'(hd0,msdos1)\'"',
-    "ryuglab" => '--server=pc-sojkam.felk.cvut.cz:/srv/tftp --uboot --uboot-init="mw f0000b00 \${psc_cfg}" --remote-cmd="ssh -t pc-sojkam.felk.cvut.cz \"cu -l /dev/ttyUSB0 -s 115200\"" --remote-expect="Connected." --reset-cmd="ssh -t pc-sojkam.felk.cvut.cz \"dtrrts /dev/ttyUSB0 1 1\""',
-    "ryulocal" => '--dhcp-tftp --serial --uboot --uboot-init="dhcp; mw f0000b00 \${psc_cfg}" --reset-cmd="if which dtrrts; then dtrrts $NB_SERIAL 0 1; sleep 0.1; dtrrts $NB_SERIAL 1 1; fi"',
-
+    "tud" => '--copy=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 --grub-preamble="timeout 0" --concat --iprelay=141.76.48.80:2324 --scriptmod=s/\\\\bhostserial\\\\b/hostserialpci/g',
+    "novabox" => '--ssh=novabox@rtime.felk.cvut.cz',
+    "localhost" => '--scriptmod=s/console=tty[A-Z0-9,]+// --copy=/boot/novaboot/$NAME --grub2 --grub-prefix=/boot/novaboot/$NAME --grub2-prolog="  set root=\'(hd0,msdos1)\'"',
+    "ryu" =>  '--uboot --uboot-init="mw f0000b00 \${psc_cfg}; sleep 1" --uboot-addr kernel=800000 --uboot-addr ramdisk=b00000 --uboot-addr fdt=7f0000',
+    "ryuglab" => '--target ryu --ssh=ryu@pc-sojkam.felk.cvut.cz',
+    "ryulocal" => '--target ryu --dhcp-tftp --serial --reset-cmd="if which dtrrts; then dtrrts $NB_SERIAL 0 1; sleep 0.1; dtrrts $NB_SERIAL 1 1; fi"',
     );
-chomp(my $nproc = `nproc`);
-$CFG::scons = "scons -j$nproc";
-$CFG::make = "make -j$nproc";
+
+{
+    my %const;
+    $const{linux}->{_SC_NPROCESSORS_CONF} = 83;
+    my $nproc = sysconf($const{$^O}->{_SC_NPROCESSORS_CONF});
+
+    $CFG::scons = "scons -j$nproc";
+    $CFG::make = "make -j$nproc";
+}
 
 my $builddir;
 
@@ -61,6 +81,7 @@ sub read_config($) {
     my ($cfg) = @_;
     {
        package CFG; # Put config data into a separate namespace
+
        my $rc = do($cfg);
 
        # Check for errors
@@ -83,31 +104,96 @@ my @cfgs;
     # when a script is run as "novaboot <options> <script>" then $ARGV[0]
     # contains the first option. Hence the -f check.
     my $dir = File::Spec->rel2abs($ARGV[0] && -f $ARGV[0] ? dirname($ARGV[0]) : '', $invocation_dir);
-    while (-d $dir && $dir ne "/") {
+    while ((-d $dir || -l $dir ) && $dir ne "/") {
        push @cfgs, "$dir/.novaboot" if -r "$dir/.novaboot";
-       $dir = abs_path($dir."/..");
+       my @dirs = File::Spec->splitdir($dir);
+       $dir = File::Spec->catdir(@dirs[0..$#dirs-1]);
+    }
+    @cfgs = reverse @cfgs;
+
+    my $xdg_config_home = $ENV{'XDG_CONFIG_HOME'} || $ENV{'HOME'}.'/.config';
+    unshift(@cfgs, "$xdg_config_home/novaboot") if -r "$xdg_config_home/novaboot";
+
+    $dir = $ENV{'NOVABOOT_CONFIG_DIR'} || '/etc/novaboot.d';
+    if (opendir(my $dh, $dir)) {
+       my @etccfg = map { "$dir/$_" } grep { /^[-_a-zA-Z0-9]+$/ && -f "$dir/$_" } readdir($dh);
+       closedir $dh;
+       @etccfg = sort(@etccfg);
+       @cfgs = ( @etccfg, @cfgs );
     }
 }
 my $cfg = $ENV{'NOVABOOT_CONFIG'};
 Getopt::Long::Configure(qw/no_ignore_case pass_through/);
 GetOptions ("config|c=s" => \$cfg);
-read_config($_) foreach $cfg or reverse @cfgs;
+read_config($_) foreach $cfg or @cfgs;
 
 ## Command line handling
 
-my $explicit_target;
+my $explicit_target = $ENV{'NOVABOOT_TARGET'};
 GetOptions ("target|t=s" => \$explicit_target);
 
-my ($append, $bender, @chainloaders, $concat, $config_name_opt, $dhcp_tftp, $dump_opt, $dump_config, $gen_only, $grub_config, $grub_prefix, $grub_preamble, $grub2_prolog, $grub2_config, $help, $iprelay, $iso_image, $make, $man, $no_file_gen, $off_opt, $on_opt, $pulsar, $pulsar_root, $qemu, $qemu_append, $qemu_flags_cmd, $remote_cmd, $remote_expect, $reset_cmd, $rom_prefix, $rsync_flags, @scriptmod, $scons, $serial, $server, $stty, $uboot, $uboot_init);
+# 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, $uboot_stop_key);
+
+my ($target_reset, $target_power_on, $target_power_off);
 
+# Default values of certain command line options
+%uboot_addr = (
+    'kernel'  => '${kernel_addr_r}',
+    'ramdisk' => '${ramdisk_addr_r}',
+    'fdt'     => '${fdt_addr_r}',
+    );
 $rsync_flags = '';
 $rom_prefix = 'rom://';
-$stty = 'raw -crtscts -onlcr 115200';
+$stty = 'raw -crtscts -onlcr -echo 115200';
+$reset = 1;                    # Reset target by default
+$interaction = 1;              # Perform target interaction by default
+$final_eol = 1;
+$netif = $CFG::netif;
+$remote_expect_timeout = -1;
+
+my @expect_seen = ();
+sub handle_expect
+{
+    my ($n, $v) = @_;
+    push(@expect_seen, '-re') if $n eq "expect-re";
+    push(@expect_seen, $v);
+}
+
+sub handle_send
+{
+    my ($n, $v) = @_;
+    unless (@expect_seen) { die("No --expect before --send"); }
+    my $ret = ($n eq "sendcont") ? exp_continue : 0;
+    unshift(@expect_raw, sub { shift->send(eval("\"$v\"")); $ret; });
+    unshift(@expect_raw, @expect_seen);
+    @expect_seen = ();
+}
+
+# Options which can be safely specified on the server (via --ssh),
+# i.e. which cannot cause unwanted local code execution etc.
+my %opt_spec_safe = (
+    "grub|g:s"              => \$grub_config,
+    "grub-preamble=s"=> \$grub_preamble,
+    "grub2-prolog=s" => \$grub2_prolog,
+    "grub2:s"       => \$grub2_config,
+    "prefix|grub-prefix=s" => \$grub_prefix,
+    "pulsar-root=s"  => \$pulsar_root,
+    "pulsar|p:s"     => \$pulsar,
+    "remote-expect=s"=> \$remote_expect,
+    "remote-expect-silent=s"=> sub { $remote_expect=$_[1]; $remote_expect_silent=1; },
+    "remote-expect-timeout=i"=> \$remote_expect_timeout,
+    "uboot-addr=s"   => \%uboot_addr,
+    "uboot-cmd=s"    => \$uboot_cmd,
+    "uboot-stop-key=s" => \$uboot_stop_key,
+    "uboot-init=s"   => sub { push @uboot_init, { command => $_[1] }; },
+    "uboot:s"       => \$uboot,
+    );
 
-Getopt::Long::Configure(qw/no_ignore_case no_pass_through/);
-my %opt_spec;
-%opt_spec = (
-    "append|a=s"     => \$append,
+my %opt_spec = (
+    %opt_spec_safe,
+    "amt=s"         => \$amt,
+    "append|a=s"     => \@append,
     "bender|b"       => \$bender,
     "build-dir=s"    => sub { my ($n, $v) = @_; $builddir = File::Spec->rel2abs($v); },
     "concat"        => \$concat,
@@ -115,46 +201,107 @@ my %opt_spec;
     "dhcp-tftp|d"    => \$dhcp_tftp,
     "dump"          => \$dump_opt,
     "dump-config"    => \$dump_config,
+    "exiton=s"       => \@exiton,
+    "exiton-timeout=i"=> \$exiton_timeout,
+    "exiton-re=s"    => sub { my ($n, $v) = @_; push(@exiton, '-re', $v); },
+    "expect=s"      => \&handle_expect,
+    "expect-re=s"    => \&handle_expect,
+    "expect-raw=s"   => sub { my ($n, $v) = @_; unshift(@expect_raw, eval($v)); },
+    "final-eol!"     => \$final_eol,
     "gen-only"      => \$gen_only,
-    "grub|g:s"              => \$grub_config,
-    "grub-preamble=s"=> \$grub_preamble,
-    "grub-prefix=s"  => \$grub_prefix,
-    "grub2:s"       => \$grub2_config,
-    "grub2-prolog=s" => \$grub2_prolog,
+    "ider"           => \$ider,
+    "interaction!"   => \$interaction,
     "iprelay=s"             => \$iprelay,
-    "iso|i:s"       => \$iso_image,
+    "iprelay-cmd=s"  => \$iprelay_cmd,
+    "iso:s"         => \$iso_image,
+    "kernel|k=s"     => \$kernel_opt,
+    "interactive|i"  => \$interactive,
     "name=s"        => \$config_name_opt,
     "make|m:s"      => \$make,
+    "netif=s"       => \$netif,
     "no-file-gen"    => \$no_file_gen,
     "off"           => \$off_opt,
     "on"            => \$on_opt,
-    "pulsar|p:s"     => \$pulsar,
-    "pulsar-root=s"  => \$pulsar_root,
     "qemu|Q:s"              => \$qemu,
     "qemu-append=s"  => \$qemu_append,
     "qemu-flags|q=s" => \$qemu_flags_cmd,
-    "remote-cmd=s"   => \$remote_cmd,
-    "remote-expect=s"=> \$remote_expect,
-    "reset-cmd=s"    => \$reset_cmd,
+    "remote-cmd=s"   => sub { @remote_cmd = ($_[1]); },
+    "reset!"         => \$reset,
+    "reset-cmd=s"    => sub { @reset_cmd = ($_[1]); },
+    "reset-send=s"   => \$reset_send,
     "rsync-flags=s"  => \$rsync_flags,
     "scons:s"       => \$scons,
     "scriptmod=s"    => \@scriptmod,
+    "send=s"        => \&handle_send,
+    "sendcont=s"     => \&handle_send,
     "serial|s:s"     => \$serial,
     "server:s"              => \$server,
+    "copy:s"        => \$server,
+    "ssh:s"         => \&handle_novaboot_server,
     "strip-rom"             => sub { $rom_prefix = ''; },
     "stty=s"        => \$stty,
-    "uboot"         => \$uboot,
-    "uboot-init=s"   => \$uboot_init,
+    "tftp"          => \$tftp,
+    "tftp-port=i"    => \$tftp_port,
+    "no-uboot"      => sub { undef $uboot; },
     "h"             => \$help,
     "help"          => \$man,
     );
 
+sub handle_novaboot_server
+{
+    my ($n, $val) = @_;
+    my $xdg_runtime_dir = $ENV{XDG_RUNTIME_DIR} || '/var/run';
+    my $ssh_ctl_path = "${xdg_runtime_dir}/novaboot$$";
+
+    @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');
+
+    $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 @target_config = qx($cmd  < /dev/null);
+    if ($?) { die("Cannot get target configuration from the server"); }
+    printf "novaboot: Received configuration from the server:%s\n", (!@target_config) ? " empty" : "";
+    foreach (@target_config) { chomp; print "  $_\n"; }
+
+    my $p = Getopt::Long::Parser->new;
+    $p->configure(qw/no_ignore_case no_pass_through/);
+    $p->getoptionsfromarray(\@target_config, %opt_spec_safe) or die("Error processing configuration from the server");
+
+    if (scalar @target_config) { die "Unsuported configuration received from the server: ".join(", ", @target_config); }
+}
+
 # First process target options
-GetOptionsFromString($CFG::targets{$explicit_target || $CFG::default_target}, %opt_spec);
+{
+    my $t = defined($explicit_target) ? $explicit_target : $CFG::default_target;
+    my @target_expanded;
+    Getopt::Long::Configure(qw/no_ignore_case pass_through/);
+    while ($t) {
+       exists $CFG::targets{$t} or die("Unknown target '$t' (valid targets are: ".join(", ", sort keys(%CFG::targets)).")");
+
+       undef $explicit_target;
+       my ($ret, $remaining_args) = GetOptionsFromString ($CFG::targets{$t}, ("target|t=s" => \$explicit_target));
+       if (!$ret) { die "Error parsing target $t option"; }
+       push(@target_expanded, @$remaining_args);
+       $t = $explicit_target;
+    }
+
+    my @args = (@target_expanded, @ARGV);
+    print STDERR "novaboot: Effective options: @args\n";
+
+    Getopt::Long::Configure(qw/no_ignore_case no_pass_through/);
+    GetOptionsFromArray(\@target_expanded, %opt_spec) or die ("Error in target definition");
+}
 
 # Then process other command line options - some of them may override
 # what was specified by the target
-GetOptions %opt_spec;
+GetOptions %opt_spec or die("Error in command line arguments");
 pod2usage(1) if $help;
 pod2usage(-exitstatus => 0, -verbose => 2) if $man;
 
@@ -175,52 +322,74 @@ if ($dump_config) {
 
 ### Sanitize configuration
 
+if ($interactive && !-t STDIN) {
+    die("novaboot: Interactive mode not supported when not on terminal");
+}
+
 if (defined $config_name_opt && scalar(@ARGV) > 1) { die "You cannot use --name with multiple scripts"; }
 
+if ($ider) {
+    $iso_image //= ''; # IDE-R needs an ISO image
+    if (!defined $amt) { die "Error: --ider requires --amt"; }
+}
+
+{
+    my %input_opts = ('--iprelay'    => \$iprelay,
+                     '--iprelay-cmd'=> \$iprelay_cmd,
+                     '--serial'     => \$serial,
+                     '--remote-cmd' => (@remote_cmd ? \$remote_cmd[0] : undef),
+                     '--amt'        => \$amt);
+    my @opts = grep(defined(${$input_opts{$_}}) , keys %input_opts);
+
+    die("novaboot: More than one target connection option: ".join(', ', @opts)) if scalar @opts > 1;
+}
+
 # Default options
 if (defined $serial) {
     $serial ||= "/dev/ttyUSB0";
     $ENV{NB_SERIAL} = $serial;
 }
 if (defined $grub_config) { $grub_config ||= "menu.lst"; }
-if (defined $grub2_config) { $grub2_config ||= "grub.cfg"; }
+if (defined $grub2_config) { $grub2_config ||= "./boot/grub/grub.cfg"; }
 
 ## Parse the novaboot script(s)
 my @scripts;
 my $file;
-my $line;
 my $EOF;
 my $last_fn = '';
-my ($modules, $variables, $generated, $continuation);
-while (<>) {
+my ($modules, $variables, $generated, $copy, $chainload, $continuation) = ([], {}, [], []);
+my $skip_reading = defined($on_opt) || defined($off_opt);
+while (!$skip_reading && ($_ = <<>>)) {
     if ($ARGV ne $last_fn) { # New script
        die "Missing EOF in $last_fn" if $file;
-       die "Unfinished line in $last_fn" if $line;
+       die "Unfinished line in $last_fn" if $continuation;
        $last_fn = $ARGV;
        push @scripts, { 'filename' => $ARGV,
                         'modules' => $modules = [],
                         'variables' => $variables = {},
-                        'generated' => $generated = []};
+                        'generated' => $generated = [],
+                        'copy' => $copy = [],
+                        'chainload' => $chainload = [],
+       };
 
     }
     chomp();
     next if /^#/ || /^\s*$/;   # Skip comments and empty lines
 
-    foreach my $mod(@scriptmod) { eval $mod; }
-
-    print "$_\n" if $dump_opt;
+    $_ =~ s/^[[:space:]]*// if ($continuation);
 
-    if (/^([A-Z_]+)=(.*)$/) {  # Internal variable
-       $$variables{$1} = $2;
+    if (/\\$/) {               # Line continuation
+       $continuation .= substr($_, 0, length($_)-1);
        next;
     }
-    if (/^([^ ]*)(.*?)[[:space:]]*<<([^ ]*)$/) { # Heredoc start
-       push @$modules, "$1$2";
-       $file = [];
-       push @$generated, {filename => $1, content => $file};
-       $EOF = $3;
-       next;
+
+    if ($continuation) {       # Last continuation line
+       $_ = $continuation . $_;
+       $continuation = '';
     }
+
+    foreach my $mod(@scriptmod) { eval $mod; }
+
     if ($file && $_ eq $EOF) { # Heredoc end
        undef $file;
        next;
@@ -229,29 +398,93 @@ while (<>) {
        push @{$file}, "$_\n";
        next;
     }
-    $_ =~ s/^[[:space:]]*// if ($continuation);
-    if (/\\$/) {               # Line continuation
-       $line .= substr($_, 0, length($_)-1);
-       $continuation = 1;
+    if (/^([A-Z_]+)=(.*)$/) {  # Internal variable
+       $$variables{$1} = $2;
+       push(@exiton, $2) if ($1 eq "EXITON");
+       $interaction = $2 if ($1 eq "INTERACTION");
+       next;
+    }
+    sub process_load_copy($) {
+       die("novaboot: '$last_fn' line $.: Missing file name\n") unless /^[^ <]+/;
+       if (/^([^ ]*)(.*?)[[:space:]]*<<([^ ]*)$/) { # Heredoc start
+           $file = [];
+           push @$generated, {filename => $1, content => $file};
+           $EOF = $3;
+           return "$1$2";
+       }
+       if (/^([^ ]*)(.*?)[[:space:]]*< ?(.*)$/) { # Command substitution
+           push @$generated, {filename => $1, command => $3};
+           return "$1$2";
+       }
+       s/\s*$//;               # Strip trailing whitespace
+       return $_;
+    }
+    if (s/^load *//) {         # Load line
+       push @$modules, process_load_copy($_);
+       next;
+    }
+    if (s/^copy *//) {         # Copy line
+       push @$copy, process_load_copy($_);
+       next;
+    }
+    if (s/^chld *//) {         # Chainload line
+       push @$chainload, process_load_copy($_);
+       next;
+    }
+    if (/^run (.*)/) {         # run line
+       push @$generated, {command => $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 = '';
+    if (/^uboot(?::([0-9]+)s)? +(< *)?(.*)/) { # uboot line
+       # TODO: If U-Boot supports some interactive menu, it might
+       # make sense to store uboot lines per novaboot script.
+       my ($timeout, $redir, $string, $dest) = ($1, $2, $3);
+       if ($string =~ /(.*) *> *(.*)/) {
+           $string = $1;
+           $dest = $2;
+       }
+       push @uboot_init, { command => $redir ? "" : $string,
+                           system =>  $redir ? $string : "",
+                           timeout => $timeout,
+                           dest => $dest,
+       };
        next;
     }
-    push @$modules, $line;
-    $line = '';
+
+    die("novaboot: Cannot parse script '$last_fn' line $.. Didn't you forget 'load' keyword?\n");
+}
+# use Data::Dumper;
+# print Dumper(\@scripts);
+
+foreach my $script (@scripts) {
+    $modules = $$script{modules};
+    @$modules[0] =~ s/^[^ ]*/$kernel_opt/ if $kernel_opt;
+    @$modules[0] .= ' ' . join(' ', @append) if @append;
+
+    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 = (@chainloaders, @$modules);
+    @$modules = ("bin/boot/bender", @$modules) if ($bender || defined $ENV{'NOVABOOT_BENDER'});
 }
-#use Data::Dumper;
-#print Dumper(\@scripts);
 
-exit if $dump_opt;
+if ($dump_opt) {
+    foreach my $script (@scripts) {
+       print join("\n", @{$$script{modules}})."\n";
+    }
+    exit(0);
+}
 
 ## Helper functions
 
@@ -265,10 +498,14 @@ sub generate_configs($$$) {
        open(my $f, '>', $fn) || die("$fn: $!");
        map { s|\brom://([^ ]*)|$rom_prefix$base$1|g; print $f "$_"; } @{$config};
        close($f);
-       print "novaboot: Created $fn\n";
+       print STDERR "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}");
+       if (exists $$g{filename}) {
+           system_verbose("( $$g{command} ) > $$g{filename}");
+       } else {
+           system_verbose($$g{command});
+       }
       }
     }
 }
@@ -298,28 +535,73 @@ sub generate_grub_config($$$$;$)
     return $filename;
 }
 
+sub generate_syslinux_config($$$$)
+{
+    my ($filename, $title, $base, $modules_ref) = @_;
+    if ($base && $base !~ /\/$/) { $base = "$base/"; };
+    open(my $fg, '>', $filename) or die "$filename: $!";
+    print $fg "LABEL $title\n";
+    #TODO print $fg "MENU LABEL $human_readable_title\n";
+
+    my ($kbin, $kcmd) = split(' ', @$modules_ref[0], 2);
+
+    if (system("file $kbin|grep 'Linux kernel'") == 0) {
+       my $initrd = @$modules_ref[1];
+       die('Too many "load" lines for Linux kernel') if (scalar @$modules_ref > 2);
+       print $fg "LINUX $base$kbin\n";
+       print $fg "APPEND $kcmd\n";
+       print $fg "INITRD $base$initrd\n";
+    } else {
+       print $fg "KERNEL mboot.c32\n";
+       my @append;
+       foreach (@$modules_ref) {
+           s|\brom://([^ ]*)|$rom_prefix$base$1|g; # Translate rom:// files - needed for vdisk parameter of sigma0
+           push @append, "$base$_";
+           print $fg "APPEND ".join(' --- ', @append)."\n";
+       }
+    }
+    #TODO print $fg "TEXT HELP\n";
+    #TODO print $fg "some help here\n";
+    #TODO print $fg "ENDTEXT\n";
+    close($fg);
+    print("novaboot: Created $builddir/$filename\n");
+    return $filename;
+}
+
 sub generate_grub2_config($$$$;$$)
 {
     my ($filename, $title, $base, $modules_ref, $preamble, $prolog) = @_;
     if ($base && substr($base,-1,1) ne '/') { $base = "$base/"; };
+    my $dir = dirname($filename);
+    make_path($dir, {
+                chmod => 0755,
+    });
     open(my $fg, '>', $filename) or die "$filename: $!";
     print $fg "$preamble\n" if $preamble;
     $title ||= 'novaboot';
     print $fg "menuentry $title {\n";
     print $fg "$prolog\n" if $prolog;
     my $first = 1;
+    my $boot_method = $variables->{BOOT_METHOD} // "multiboot";
+    my $module_load_method = "module";
+    if ($boot_method eq "linux") {
+       $module_load_method = "initrd";
+       die('Too many "load" lines for Linux kernel') if (scalar(@$modules_ref) > 2);
+    }
     foreach (@$modules_ref) {
        if ($first) {
            $first = 0;
            my ($kbin, $kcmd) = split(' ', $_, 2);
            $kcmd = '' if !defined $kcmd;
-           print $fg "  multiboot ${base}$kbin $kcmd\n";
+           print $fg "  $boot_method ${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";
+           if ($boot_method eq "multiboot") {
+               # 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_load_method $base$_\n";
        }
     }
     print $fg "}\n";
@@ -328,26 +610,30 @@ sub generate_grub2_config($$$$;$$)
     return $filename;
 }
 
-sub generate_pulsar_config($$)
+sub generate_pulsar_config($$$)
 {
-    my ($filename, $modules_ref) = @_;
+    my ($filename, $modules_ref, $chainload_ref) = @_;
     open(my $fg, '>', $filename) or die "$filename: $!";
     print $fg "root $pulsar_root\n" if defined $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";
+    if (scalar(@$chainload_ref) > 0) {
+       print $fg "chld $$chainload_ref[0]\n";
+    } else {
+       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";
     }
-    # 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);
     print("novaboot: Created $builddir/$filename\n");
     return $filename;
@@ -355,34 +641,44 @@ sub generate_pulsar_config($$)
 
 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(@)
 {
-    print "novaboot: Running: ".shell_cmd_string(@_)."\n";
+    print STDERR "novaboot: Running: ".shell_cmd_string(@_)."\n";
     exec(@_);
+    exit(1); # should not be reached
 }
 
-sub system_verbose($)
+sub system_verbose
 {
-    my $cmd = shift;
-    print "novaboot: Running: $cmd\n";
-    my $ret = system($cmd);
+    print STDERR "novaboot: Running: ".shell_cmd_string(@_)."\n";
+    my $ret = system(@_);
     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
+sub trim($) {
+    my ($str) = @_;
+    $str =~ s/^\s+|\s+$//g;
+    return $str
+}
+
+## WvTest headline
 
 if (exists $variables->{WVDESC}) {
-    print "Testing \"$variables->{WVDESC}\" in $last_fn:\n";
+    print STDERR "Testing \"$variables->{WVDESC}\" in $last_fn:\n";
 } elsif ($last_fn =~ /\.wv$/) {
-    print "Testing \"all\" in $last_fn:\n";
+    print STDERR "Testing \"all\" in $last_fn:\n";
 }
 
-## Connect to the target and check whether is not occupied
+## Connect to the target and check whether it is not occupied
 
 # We have to do this before file generation phase, because file
 # generation is intermixed with file deployment phase and we want to
@@ -392,27 +688,61 @@ if (exists $variables->{WVDESC}) {
 
 my $exp; # Expect object to communicate with the target over serial line
 
-my ($target_reset, $target_power_on, $target_power_off);
+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;
+}
 
-if (defined $iprelay) {
-    my $IPRELAY;
-    $iprelay =~ /([.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";
-    $exp = Expect->init(\*$IPRELAY);
-    $exp->log_stdout(1);
+
+my ($amt_user, $amt_password, $amt_host, $amt_port);
+
+if (defined $iprelay || defined $iprelay_cmd) {
+    if (defined $iprelay) {
+       my $IPRELAY;
+       $iprelay =~ /([.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 STDERR "novaboot: Connecting to IP relay... ";
+       connect($IPRELAY, $paddr)    || die "connect: $!";
+       print STDERR "done\n";
+       $exp = Expect->init(\*$IPRELAY);
+       $exp->log_stdout(1);
+    }
+    if (defined $iprelay_cmd) {
+       print STDERR "novaboot: Running: $iprelay_cmd\n";
+       $exp = new Expect;
+       $exp->raw_pty(1);
+       $exp->spawn($iprelay_cmd);
+       kill_exp_on_signal();
+    }
 
     while (1) {
        print $exp "\xFF\xF6";  # AYT
        my $connected = $exp->expect(20, # Timeout in seconds
                                     '<iprelayd: connected>',
-                                    '-re', '<WEB51 HW[^>]*>');
+                                    '-re', '<WEB51 HW[^>]*>')
+           || die "iprelay connection: " . ($! || "timeout");
        last if $connected;
     }
 
@@ -437,7 +767,8 @@ if (defined $iprelay) {
        $exp->log_stdout(0);
        print $exp relaycmd($relay, $onoff);
        my $confirmed = $exp->expect(20, # Timeout in seconds
-                                    relayconf($relay, $onoff));
+                                    relayconf($relay, $onoff))
+           || die "iprelay command: " . ($! || "timeout");
        if (!$confirmed) {
            if ($can_giveup) {
                print("Relay confirmation timeout - ignoring\n");
@@ -471,18 +802,137 @@ elsif ($serial) {
     system_verbose("stty -F $serial $stty");
     open($CONN, "+<", $serial) || die "open $serial: $!";
     $exp = Expect->init(\*$CONN);
-} elsif ($remote_cmd) {
-    print "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;
+    require LWP::Authen::Digest;
+
+    sub genXML {
+        my ($host, $username, $password, $schema, $className, $pstate) = @_;
+       #AMT numbers for PowerStateChange (MNI => bluescreen on windows;-)
+       my %pstates = ("on"        => 2,
+                      "standby"   => 4,
+                      "hibernate" => 7,
+                      "off"       => 8,
+                      "reset"     => 10,
+                      "MNI"       => 11);
+        return <<END;
+               <s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:w="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd">
+               <s:Header><a:To>http://$host:16992/wsman</a:To>
+               <w:ResourceURI s:mustUnderstand="true">$schema</w:ResourceURI>
+               <a:ReplyTo><a:Address s:mustUnderstand="true">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address></a:ReplyTo>
+               <a:Action s:mustUnderstand="true">$schema$className</a:Action>
+               <w:MaxEnvelopeSize s:mustUnderstand="true">153600</w:MaxEnvelopeSize>
+               <a:MessageID>uuid:709072C9-609C-4B43-B301-075004043C7C</a:MessageID>
+               <w:Locale xml:lang="en-US" s:mustUnderstand="false" />
+               <w:OperationTimeout>PT60.000S</w:OperationTimeout>
+               <w:SelectorSet><w:Selector Name="Name">Intel(r) AMT Power Management Service</w:Selector></w:SelectorSet>
+               </s:Header><s:Body>
+               <p:RequestPowerStateChange_INPUT xmlns:p="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_PowerManagementService">
+               <p:PowerState>$pstates{$pstate}</p:PowerState>
+               <p:ManagedElement><a:Address>http://$host:16992/wsman</a:Address>
+               <a:ReferenceParameters><w:ResourceURI>http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ComputerSystem</w:ResourceURI>
+               <w:SelectorSet><w:Selector Name="Name">ManagedSystem</w:Selector></w:SelectorSet>
+               </a:ReferenceParameters></p:ManagedElement>
+               </p:RequestPowerStateChange_INPUT>
+               </s:Body></s:Envelope>
+END
+    }
+
+    sub sendPOST {
+        my ($host, $username, $password, $content) = @_;
+
+        my $ua = LWP::UserAgent->new();
+        $ua->agent("novaboot");
+
+       my $req = HTTP::Request->new(POST => "http://$host:16992/wsman");
+        my $res = $ua->request($req);
+        die ("Unexpected AMT response: " . $res->status_line) unless $res->code == 401;
+
+        my ($realm) = $res->header("WWW-Authenticate") =~ /Digest realm="(.*?)"/;
+        $ua->credentials("$host:16992", $realm, $username => $password);
+
+        # Create a request
+        $req = HTTP::Request->new(POST => "http://$host:16992/wsman");
+        $req->content_type('application/x-www-form-urlencoded');
+        $req->content($content);
+        $res = $ua->request($req);
+        die ("AMT power change request failed: " . $res->status_line) unless $res->is_success;
+        $res->content() =~ /<g:ReturnValue>(\d+)<\/g:ReturnValue>/;
+        return $1;
+    }
+
+    sub powerChange  {
+        my ($host, $username, $password, $pstate)=@_;
+        my $schema="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_PowerManagementService";
+        my $className="/RequestPowerStateChange";
+        my $content = genXML($host, $username, $password ,$schema, $className, $pstate);
+        return sendPOST($host, $username, $password, $content);
+    }
+
+    ($amt_user,$amt_password,$amt_host,$amt_port) = ($amt =~ /(?:(.*?)(?::(.*))?@)?([^:]*)(?::([0-9]*))?/);;
+    $amt_user ||= "admin";
+    $amt_password ||= $ENV{'AMT_PASSWORD'} || die "AMT password not specified";
+    $amt_host || die "AMT host not specified";
+    $amt_port ||= 16994;
+
+
+    $target_power_off = sub {
+       $exp->close();
+       my $result = powerChange($amt_host,$amt_user,$amt_password, "off");
+       die "AMT power off failed (ReturnValue $result)" if $result != 0;
+    };
+
+    $target_power_on = sub {
+       my $result = powerChange($amt_host,$amt_user,$amt_password, "on");
+       die "AMT power on failed (ReturnValue $result)" if $result != 0;
+    };
+
+    $target_reset = sub {
+       my $result = powerChange($amt_host,$amt_user,$amt_password, "reset");
+       if ($result != 0) {
+           print STDERR "Warning: Cannot reset $amt_host, trying power on. ";
+           $result = powerChange($amt_host,$amt_user,$amt_password, "on");
+       }
+       die "AMT reset failed (ReturnValue $result)" if $result != 0;
+    };
+
+    my $cmd = "amtterm -u $amt_user -p $amt_password $amt_host $amt_port";
+    print STDERR "novaboot: Running: $cmd\n" =~ s/\Q$amt_password\E/???/r;
+    $exp = Expect->spawn($cmd);
+    $exp->expect(10, "RUN_SOL") || die "Expect for 'RUN_SOL': " . ($! || "timeout");
 }
 
+
 if ($remote_expect) {
-    $exp->expect(10, $remote_expect) || die "Expect for '$remote_expect' timed out";
+    $exp || die("No serial line connection");
+    my $log = $exp->log_stdout;
+    if (defined $remote_expect_silent) {
+       $exp->log_stdout(0);
+    }
+    $exp->expect($remote_expect_timeout >= 0 ? $remote_expect_timeout : undef,
+                $remote_expect) || die "Expect for '$remote_expect':" . ($! || "timeout");;
+    if (defined $remote_expect_silent) {
+       $exp->log_stdout($log);
+       print $exp->after() if $log;
+    }
+}
+
+if (@reset_cmd) {
+    $target_reset = sub {
+       system_verbose(@reset_cmd);
+    };
 }
 
-if (defined $reset_cmd) {
+if (defined $reset_send) {
     $target_reset = sub {
-       system_verbose($reset_cmd);
+       $reset_send =~ s/\\n/\n/g;
+       $exp->send($reset_send);
     };
 }
 
@@ -491,7 +941,7 @@ if (defined $on_opt && defined $target_power_on) {
     exit;
 }
 if (defined $off_opt && defined $target_power_off) {
-    print "novaboot: Switching the target off...\n";
+    print STDERR "novaboot: Switching the target off...\n";
     &$target_power_off();
     exit;
 }
@@ -499,12 +949,15 @@ if (defined $off_opt && defined $target_power_off) {
 $builddir ||= dirname(File::Spec->rel2abs( ${$scripts[0]}{filename})) if scalar @scripts;
 if (defined $builddir) {
     chdir($builddir) or die "Can't change directory to $builddir: $!";
-    print "novaboot: Entering directory `$builddir'\n";
+    print STDERR "novaboot: Entering directory `$builddir'\n";
+} else {
+    $builddir = $invocation_dir;
 }
 
 ## File generation phase
 my (%files_iso, $menu_iso, $filename);
 my $config_name = '';
+my $prefix = '';
 
 foreach my $script (@scripts) {
     $filename = $$script{filename};
@@ -518,29 +971,14 @@ foreach my $script (@scripts) {
     if (exists $variables->{BUILDDIR}) {
        $builddir = File::Spec->rel2abs($variables->{BUILDDIR});
        chdir($builddir) or die "Can't change directory to $builddir: $!";
-       print "novaboot: Entering directory `$builddir'\n";
+       print STDERR "novaboot: Entering directory `$builddir'\n";
     }
 
-    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;
-           }
-       }
+    if ($grub_prefix) {
+       $prefix = $grub_prefix;
+       $prefix =~ s/\$NAME/$config_name/;
+       $prefix =~ s/\$BUILDDIR/$builddir/;
     }
-    @$modules = ($kernel, @$modules) if $kernel;
-    @$modules = (@chainloaders, @$modules);
-    @$modules = ("bin/boot/bender", @$modules) if ($bender || defined $ENV{'NOVABOOT_BENDER'});
-
-    my $prefix;
-    ($prefix = $grub_prefix) =~ s/\$NAME/$config_name/ if defined $grub_prefix;
-    $prefix ||= $builddir;
     # TODO: use $grub_prefix as first parameter if some switch is given
     generate_configs('', $generated, $filename);
 
@@ -548,13 +986,18 @@ foreach my $script (@scripts) {
     my @bootloader_configs;
     push @bootloader_configs, generate_grub_config($grub_config, $config_name, $prefix, $modules, $grub_preamble) if (defined $grub_config);
     push @bootloader_configs, generate_grub2_config($grub2_config, $config_name, $prefix, $modules, $grub_preamble, $grub2_prolog) if (defined $grub2_config);
-    push @bootloader_configs, generate_pulsar_config('config-'.($pulsar||'novaboot'), $modules) if (defined $pulsar);
+    push @bootloader_configs, generate_pulsar_config('config-'.($pulsar||'novaboot'), $modules, $chainload) if (defined $pulsar);
 
 ### Run scons or make
     {
-       my @files = map({ ($file) = m/([^ ]*)/; $file; } @$modules);
+       my @all;
+       push @all, @$modules;
+       push @all, @$copy;
+       push @all, @$chainload;
+       my @files = map({ ($file) = m/([^ ]*)/; $file; } @all);
+
        # Filter-out generated files
-       my @to_build = grep({ my $file = $_; !scalar(grep($file eq $$_{filename}, @$generated)) } @files);
+       my @to_build = grep({ my $file = $_; !scalar(grep($file eq ($$_{filename} || ''), @$generated)) } @files);
 
        system_verbose($scons || $CFG::scons." ".join(" ", @to_build)) if (defined $scons);
        system_verbose($make  || $CFG::make ." ".join(" ", @to_build)) if (defined $make);
@@ -569,14 +1012,16 @@ foreach my $script (@scripts) {
            $path = $hostname;
            $hostname = "";
        }
-       my $files = join(" ", map({ ($file) = m/([^ ]*)/; $file; } ( @$modules, @bootloader_configs)));
-       map({ my $file = (split)[0]; die "$file: $!" if ! -f $file; } @$modules);
+       my $files = join(" ", map({ ($file) = m/([^ ]*)/; $file; } ( @$modules, @bootloader_configs, @$copy)));
+       map({ my $file = (split)[0]; die "Not a file: $file: $!" if ! -e $file || -d $file; } @$modules);
        my $istty = -t STDOUT && ($ENV{'TERM'} || 'dumb') ne 'dumb';
        my $progress = $istty ? "--progress" : "";
-       system_verbose("rsync $progress -RLp $rsync_flags $files $real_server");
-       if ($server =~ m|/\$NAME$| && $concat) {
-           my $cmd = join("; ", map { "( cd $path/.. && cat */$_ > $_ )" } @bootloader_configs);
-           system_verbose($hostname ? "ssh $hostname '$cmd'" : $cmd);
+       if ($files) {
+           system_verbose("rsync $progress -RL --chmod=ugo=rwX $rsync_flags $files $real_server");
+           if ($server =~ m|/\$NAME$| && $concat) {
+               my $cmd = join("; ", map { "( cd $path/.. && cat */$_ > $_ )" } @bootloader_configs);
+               system_verbose($hostname ? "ssh $hostname '$cmd'" : $cmd);
+           }
        }
     }
 
@@ -584,7 +1029,7 @@ foreach my $script (@scripts) {
     if (defined $iso_image) {
        generate_configs("(cd)", $generated, $filename);
        my $menu;
-       generate_grub_config(\$menu, $config_name, "(cd)", $modules);
+       generate_syslinux_config(\$menu, $config_name, "/", $modules);
        $menu_iso .= "$menu\n";
        map { ($file,undef) = split; $files_iso{$file} = 1; } @$modules;
     }
@@ -592,12 +1037,41 @@ foreach my $script (@scripts) {
 
 ## Generate ISO image
 if (defined $iso_image) {
-    open(my $fh, ">menu-iso.lst");
-    print $fh "timeout 5\n\n$menu_iso";
+    system_verbose("mkdir -p isolinux");
+
+    my @files;
+    if (-f '/usr/lib/ISOLINUX/isolinux.bin') {
+       # Newer ISOLINUX version
+       @files = qw(/usr/lib/ISOLINUX/isolinux.bin /usr/lib/syslinux/modules/bios/mboot.c32 /usr/lib/syslinux/modules/bios/libcom32.c32 /usr/lib/syslinux/modules/bios/menu.c32 /usr/lib/syslinux/modules/bios/ldlinux.c32);
+    } elsif (-f '/usr/lib/syslinux/isolinux.bin') {
+       # Older ISOLINUX version
+       @files = qw(/usr/lib/syslinux/isolinux.bin /usr/lib/syslinux/mboot.c32 /usr/lib/syslinux/menu.c32);
+    } else {
+       # NixOS and maybe others
+       my $syslinux = `which syslinux` || die "Cannot find syslinux";
+       chomp $syslinux;
+       $syslinux =~ s,/bin/syslinux$,,;
+       @files = ("$syslinux/share/syslinux/isolinux.bin", "$syslinux/share/syslinux/mboot.c32", "$syslinux/share/syslinux/libcom32.c32", "$syslinux/share/syslinux/menu.c32", "$syslinux/share/syslinux/ldlinux.c32");
+    }
+    system_verbose("cp @files isolinux && chmod +w isolinux/*");
+    open(my $fh, ">isolinux/isolinux.cfg");
+    if ($#scripts) {
+       print $fh "TIMEOUT 50\n";
+       print $fh "DEFAULT menu\n";
+    } else {
+       print $fh "DEFAULT $config_name\n";
+    }
+    print $fh "$menu_iso";
     close($fh);
-    my $files = "boot/grub/menu.lst=menu-iso.lst " . join(" ", map("$_=$_", keys(%files_iso)));
+
+    my $files = join(" ", map("$_=$_", (keys(%files_iso), 'isolinux/isolinux.cfg', map(s|.*/|isolinux/|r, @files))));
     $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");
+
+    # Note: We use -U flag below to "Allow 'untranslated' filenames,
+    # completely violating the ISO9660 standards". Without this
+    # option, isolinux is not able to read files names for example
+    # bzImage-3.0.
+    system_verbose("$CFG::genisoimage -R -b isolinux/isolinux.bin -c isolinux/boot.cat -no-emul-boot -boot-load-size 4 -boot-info-table -hide-rr-moved -U -o $iso_image -graft-points $files");
     print("ISO image created: $builddir/$iso_image\n");
 }
 
@@ -610,16 +1084,10 @@ if (scalar(@scripts) > 1 && ( defined $dhcp_tftp || defined $serial || defined $
 }
 
 if ($variables->{WVTEST_TIMEOUT}) {
-    print "wvtest: timeout ", $variables->{WVTEST_TIMEOUT}, "\n";
-}
-
-sub trim($) {
-    my ($str) = @_;
-    $str =~ s/^\s+|\s+$//g;
-    return $str
+    print STDERR "wvtest: timeout ", $variables->{WVTEST_TIMEOUT}, "\n";
 }
 
-### Qemu
+### Start in Qemu
 
 if (defined $qemu) {
     # Qemu
@@ -633,7 +1101,7 @@ if (defined $qemu) {
 
     if (defined $iso_image) {
        # Boot NOVA with grub (and test the iso image)
-       push(@qemu_flags, ('-cdrom', "$config_name.iso"));
+       push(@qemu_flags, ('-cdrom', $iso_image));
     } else {
        # Boot NOVA without GRUB
 
@@ -653,9 +1121,11 @@ if (defined $qemu) {
            push(@qemu_flags, ('-dtb', $dtb)) if $dtb;
        }
     }
-    push(@qemu_flags,  qw(-serial stdio)); # Redirect serial output (for collecting test restuls)
+    if (!grep /^-serial$/, @qemu_flags) {
+       push(@qemu_flags,  qw(-serial stdio)); # Redirect serial output (for collecting test restuls)
+    }
     unshift(@qemu_flags, ('-name', $config_name));
-    print "novaboot: Running: ".shell_cmd_string($qemu, @qemu_flags)."\n";
+    print STDERR "novaboot: Running: ".shell_cmd_string($qemu, @qemu_flags)."\n";
     $exp = Expect->spawn(($qemu, @qemu_flags)) || die("exec() failed: $!");
 }
 
@@ -663,26 +1133,37 @@ if (defined $qemu) {
 
 my ($dhcpd_pid, $tftpd_pid);
 
+$tftp=1 if $tftp_port;
+
 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`;
+    my $mac = `cat /sys/class/net/$netif/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;
+    print $fh "
+subnet 10.23.23.0 netmask 255.255.255.0 {
+       range 10.23.23.10 10.23.23.100;
+       next-server 10.23.23.1;
+}
+class \"pxe-clients\" {
+     match option vendor-class-identifier;
+}
+subclass \"pxe-clients\"  \"PXEClient:Arch:00000:UNDI:002001\" {
+     option bootfile-name \"boot/grub/i386-pc/core.0\";
+}
+subclass \"pxe-clients\"  \"PXEClient:Arch:00007:UNDI:003016\" {
+     option bootfile-name \"boot/grub/x86_64-efi/core.efi\";
 }
 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;
+    system_verbose("sudo ip a add 10.23.23.1/24 dev $netif;
+           sudo ip l set dev $netif up;
            sudo touch dhcpd.leases");
 
     # We run servers by forking ourselves, because the servers end up
@@ -690,186 +1171,437 @@ host server {
     # process group (e.g. Ctrl-C on terminal).
     $dhcpd_pid = fork();
     exec_verbose("sudo dhcpd -d -cf dhcpd.conf -lf dhcpd.leases -pf dhcpd.pid") if ($dhcpd_pid == 0);
-    $tftpd_pid = fork();
-    exec_verbose("sudo in.tftpd --foreground --secure -v -v -v --pidfile tftpd.pid $builddir") if ($tftpd_pid == 0);
+}
+
+if (defined $dhcp_tftp || defined $tftp) {
+    $tftp_port ||= 69;
+    my $tftp_root = "$builddir";
+    $tftp_root = "$server" if(defined $server);
+
+    # Prepare a GRUB netboot directory
+    system_verbose("grub-mknetdir --net-directory=$tftp_root") if (defined $grub2_config);
+
+    # Generate TFTP mapfile
+    open(my $fh, '>', "$tftp_root/mapfile");
+    print $fh "# Some PXE clients (mainly UEFI) have bug. They add zero byte to the end of the
+# path name. This rule removes it
+r     \\.efi.*   \\.efi";
+    close($fh);
+    # Unfortunately, tftpd requires root privileges even with
+    # non-privileged (>1023) port due to initgroups().
+    system_verbose("sudo in.tftpd --listen --secure -v -v -v --pidfile tftpd.pid -m mapfile --address :$tftp_port $tftp_root");
 
     # Kill server when we die
-    $SIG{__DIE__} = sub { system_verbose('sudo pkill --pidfile=dhcpd.pid');
-                         system_verbose('sudo pkill --pidfile=tftpd.pid'); };
+    $SIG{__DIE__} = sub { system_verbose('sudo pkill --pidfile=dhcpd.pid') if (defined $dhcp_tftp);
+                         system_verbose("sudo pkill --pidfile=$tftp_root/tftpd.pid"); };
+
+    # We have to kill tftpd explicitely, because it is not in our process group
+    $SIG{INT} = sub { system_verbose("sudo pkill --pidfile=$tftp_root/tftpd.pid"); exit(0); };
 }
 
-### Serial line or IP relay
+### AMT IDE-R
+if (defined $ider) {
+    my $ider_cmd= "amtider -c $iso_image -u $amt_user -p $amt_password $amt_host $amt_port"  ;
+    print STDERR "novaboot: Running: $ider_cmd\n" =~ s/\Q$amt_password\E/???/r;
+    my $ider_pid = fork();
+    if ($ider_pid == 0) {
+       exec($ider_cmd);
+       die "IDE redirection failed";
+    }
+    # FIXME: This collides with --tftp option. Hopefully, nobody needs
+    # to use both simultaneously.
+    $SIG{__DIE__} = sub { system_verbose('kill $ider_pid'); };
+}
+
+### Reset target (IP relay, AMT, ...)
 
-if (defined $target_reset) {
-    print "novaboot: Reseting the test box... ";
+if (defined $target_reset && $reset) {
+    print STDERR "novaboot: Resetting the test box... ";
     &$target_reset();
-    print "done\n";
+    print STDERR "done\n";
+    if (defined $exp) {
+       # We don't want to output anything printed by the target
+       # before reset so we clear the buffers now. This is, however,
+       # not ideal because we may loose some data that were sent
+       # after the reset. If this is a problem, one should reset and
+       # connect to serial line in atomic manner. For example, if
+       # supported by hardware, use --remote-cmd 'sterm -d ...' and
+       # do not use separate --reset-cmd.
+       my $log = $exp->log_stdout;
+       $exp->log_stdout(0);
+       $exp->expect(0); # Read data from target
+       $exp->clear_accum();    # Clear the read data
+       $exp->log_stdout($log);
+    }
 }
 
+### U-boot conversation
 if (defined $uboot) {
-    print "novaboot: Waiting for uBoot prompt...\n";
+    my $uboot_prompt = $uboot || '=> ';
+    print STDERR "novaboot: Waiting for U-Boot prompt...\n";
+    $exp || die("No serial line connection");
     $exp->log_stdout(1);
+    #$exp->exp_internal(1);
     $exp->expect(20,
-                [qr/Hit any key to stop autoboot:/, sub { $exp->send("\n"); exp_continue; }],
-                '=> ') || die "No uBoot prompt deteceted";
-    $exp->send("$uboot_init\n") if $uboot_init;
-    $exp->expect(10, '=> ') || die "uBoot prompt timeout";
-
-    my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);
-    my $dtb;
-    @$modules = map { if (/\.dtb$/) { $dtb=$_; (); } else { $_ } } @$modules;
-    my $initrd = shift @$modules;
-
-    my $kern_addr = '800000';
-    my $initrd_addr = '-';
-    my $dtb_addr = '';
-
-    $exp->send("tftp $kern_addr $kbin\n");
-    $exp->expect(10,
-                [qr/#/, sub { exp_continue; }],
-                '=> ') || die "Kernel load failed";
-    if (defined $dtb) {
-       $dtb_addr = '7f0000';
-       $exp->send("tftp $dtb_addr $dtb\n");
-       $exp->expect(10,
-                    [qr/#/, sub { exp_continue; }],
-                    '=> ') || die "Device tree load failed";
+                [qr/Hit any key to stop autoboot:/, sub {
+                    $exp->send($uboot_stop_key // "\n");
+                    exp_continue; }],
+                $uboot_prompt) || die "No U-Boot prompt deteceted";
+    foreach my $cmdspec (@uboot_init) {
+       my ($cmd, $timeout);
+       die "Internal error - please report a bug" unless ref($cmdspec) eq "HASH";
+
+       if ($cmdspec->{system}) {
+           $cmd = `$cmdspec->{system}`;
+       } else {
+           $cmd = $cmdspec->{command};
+       }
+       $timeout = $cmdspec->{timeout} // 10;
+
+       if ($cmd =~ /\$NB_MYIP/) {
+           my $ip = (grep /inet /, `ip addr show $netif`)[0] || die "Problem determining IP address of $netif";
+           $ip =~ s/\s*inet ([0-9.]*).*/$1/;
+           $cmd =~ s/\$NB_MYIP/$ip/g;
+       }
+       if ($cmd =~ /\$NB_PREFIX/) {
+           my $p = $prefix;
+           $p =~ s|/*$||;
+           $cmd =~ s/\$NB_PREFIX/$p/g;
+       }
+       chomp($cmd);
+       $exp->send("$cmd\n");
+
+       my ($matched_pattern_position, $error,
+           $successfully_matching_string,
+           $before_match, $after_match) =
+               $exp->expect($timeout, $uboot_prompt);
+       die "No U-Boot prompt: $error" if $error;
+
+       if ($cmdspec->{dest}) {
+           open(my $fh, ">", $cmdspec->{dest}) or die "Cannot open '$cmdspec->{dest}': $!";
+           print $fh $before_match;
+           close($fh);
+       }
+    }
+
+    # Load files if there are some load lines in the script
+    if (scalar(@$modules) > 0  && !$variables->{NO_BOOT}) {
+       my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);
+       my $dtb;
+       @$modules = map { if (/\.dtb$/) { $dtb=$_; (); } else { $_ } } @$modules;
+       my $initrd = shift @$modules;
+
+       if (defined $kbin && $kbin ne '/dev/null') {
+           die "No '--uboot-addr kernel' given" unless $uboot_addr{kernel};
+           $exp->send("tftpboot $uboot_addr{kernel} $prefix$kbin\n");
+           $exp->expect(15,
+                         $uboot_prompt,
+                        [qr/#/, sub { exp_continue; }]
+                ) || die "Kernel load: " . ($! || "timeout");
+       }
+       if (defined $dtb) {
+           die "No '--uboot-addr fdt' given" unless $uboot_addr{fdt};
+           $exp->send("tftpboot $uboot_addr{fdt} $prefix$dtb\n");
+           $exp->expect(15,
+                        [qr/##/, sub { exp_continue; }],
+                        $uboot_prompt) || die "Device tree load: " . ($! || "timeout");
+       } else  {
+           $uboot_addr{fdt} = '';
+       }
+       if (defined $initrd) {
+           die "No '--uboot-addr ramdisk' given" unless $uboot_addr{ramdisk};
+           $exp->send("tftpboot $uboot_addr{ramdisk} $prefix$initrd\n");
+           $exp->expect(15,
+                        [qr/##/, sub { exp_continue; }],
+                        $uboot_prompt) || die "Initrd load: " . ($! || "timeout");
+       } else {
+           $uboot_addr{ramdisk} = '-';
+       }
+
+       $kcmd //= '';
+       $exp->send("setenv bootargs $kcmd\n");
+       $exp->expect(5, $uboot_prompt)  || die "U-Boot prompt: " . ($! || "timeout");
+
     }
-    if (defined $initrd) {
-       $initrd_addr = 'b00000';
-       $exp->send("tftp $initrd_addr $initrd\n");
-       $exp->expect(10,
-                    [qr/#/, sub { exp_continue; }],
-                    '=> ') || die "Initrd load failed";
+    $uboot_cmd //= $variables->{UBOOT_CMD} // 'bootm $kernel_addr $ramdisk_addr $fdt_addr';
+    if (!$variables->{NO_BOOT} && $uboot_cmd ne '') {
+       $uboot_cmd =~ s/\$kernel_addr/$uboot_addr{kernel}/g;
+       $uboot_cmd =~ s/\$ramdisk_addr/$uboot_addr{ramdisk}/g;
+       $uboot_cmd =~ s/\$fdt_addr/$uboot_addr{fdt}/g;
+
+       $exp->send($uboot_cmd . "\n");
+       $exp->expect(5, "\n")  || die "U-Boot command: " . ($! || "timeout");
     }
-    $exp->send("set bootargs $kcmd\n");
-    $exp->expect(1, '=> ')  || die "uBoot prompt timeout";
-    $exp->send("bootm $kern_addr $initrd_addr $dtb_addr\n");
-    $exp->expect(1, "\n")  || die "uBoot command timeout";
 }
 
-if (defined $exp) {
+### Serial line interaction
+if ($interaction && defined $exp) {
     # Serial line of the target is available
-    print "novaboot: Serial line interaction (press Ctrl-C to interrupt)...\n";
+    my $interrupt = 'Ctrl-C';
+    if ($interactive && !@exiton) {
+       $interrupt = '"~~."';
+    }
+    print STDERR "novaboot: Serial line interaction (press $interrupt to interrupt)...\n";
     $exp->log_stdout(1);
-    my @inputs = ($exp);
-    if (-t STDIN) { # Set up bi-directional communication if we run on terminal
+    if (@exiton) {
+       $exp->expect($exiton_timeout, @exiton, @expect_raw) || die("exiton: " . ($! || "timeout"));
+    } else {
+       my @inputs = ($exp);
        my $infile = new IO::File;
        $infile->IO::File::fdopen(*STDIN,'r');
        my $in_object = Expect->exp_init($infile);
        $in_object->set_group($exp);
-       #$in_object->set_seq("\cC",undef);
 
-       # I'm not sure when to use raw mode and when not. With
-       # --dhcp-tftp, I want the output of daemons to be normally
-       # formated (no raw mode). On the other hand, I don't want
-       # input for qemu to be echoed. Need to think more about this.
-       $in_object->manual_stty(1);
+       if ($interactive) {
+           $in_object->set_seq('~~\.', sub { print STDERR "novaboot: Escape sequence detected\r\n"; undef; });
+           $in_object->manual_stty(0);   # Use raw terminal mode
+       } else {
+           $in_object->manual_stty(1);   # Do not modify terminal settings
+       }
        push(@inputs, $in_object);
+       #use Data::Dumper;
+       #print Dumper(\@expect_raw);
+       $exp->expect(undef, @expect_raw) if @expect_raw;
+
+       $^W = 0; # Suppress Expect warning: handle id(3) is not a tty. Not changing mode at /usr/share/perl5/Expect.pm line 393, <> line 8.
+       Expect::interconnect(@inputs) unless defined($exp->exitstatus);
+       $^W = 1;
     }
-    Expect::interconnect(@inputs);
 }
 
+# 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) {
+if (defined $dhcp_tftp || defined $tftp) {
     die("novaboot: This should kill servers on background\n");
 }
 
+# Always finish novaboot output with newline
+print "\n" if $final_eol;
+
 ## Documentation
 
+=encoding utf8
+
 =head1 NAME
 
-novaboot - A tool for booting various operating systems on various hardware or in qemu
+novaboot - Boots a locally compiled operating system on a remote
+target or in qemu
 
 =head1 SYNOPSIS
 
-B<novaboot> [ options ] [--] script...
+B<novaboot> --help
 
-B<./script> [ options ]
+B<novaboot> [option]... [--] script...
+
+B<./script> [option]...
 
 =head1 DESCRIPTION
 
-This program makes it easier to boot NOVA or other operating system
-(OS) on different targets (machines or emulators). It reads a so
-called novaboot script, that specifies the boot configuration, and
-setups the target to boot that configuration. Setting up the target
-means to generate the bootloader configuration files, deploy the
-binaries and other needed files to proper locations, perhaps on a
-remote boot server and reset the target. Then, target's serial output
-is redirected to standard output if that is possible.
+Novaboot makes booting of a locally compiled operating system (OS)
+(e.g. NOVA or Linux) on remote targets as simple as running a program
+locally. It automates things like copying OS images to a TFTP server,
+generation of bootloader configuration files, resetting of target
+hardware or redirection of target's serial line to stdin/out. Novaboot
+is highly configurable and makes it easy to boot a single image on
+different targets or different images on a single target.
+
+Novaboot operation is controlled by configuration files, command line
+options and by a so-called novaboot script, which can be thought as a
+generalization of bootloader configuration files (see L</"NOVABOOT
+SCRIPT SYNTAX">). The 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.
+
+Novaboot uses configuration files to, among other things, define
+command line options needed for different targets. Users typically use
+only the B<-t>/B<--target> command line option to select the target.
+Internally, this option expands to the pre-configured options.
+Novaboot searches configuration files at multiple places, which allows
+having per-system, per-user or per-project configurations.
+Configuration file syntax is described in section L</"CONFIGURATION
+FILES">.
+
+Novaboot newcomers may be confused by a large number of configuration
+options. Understanding all these options is not always needed,
+depending on the used setup. The L<figure from the doc directory
+|https://github.com/wentasah/novaboot/blob/master/doc/typical-setups.svg>
+shows different setups that vary in how much effort is needed
+to configure novaboot for them. The setups are:
+
+=over 3
+
+=item A: Laptop and target device only
+
+This requires to configure everything on the laptop side, including a
+serial line connection (L</--serial>, L</--remote-cmd>, ...), power
+on/off/reset commands (L</--reset-cmd>, ...), TFTP server
+(L</--copy>, L</--prefix>...), device IP addresses, etc.
+
+=item B: Laptop, target device and external TFTP server
+
+Like the previous setup, but the TFTP (and maybe DHCP) configuration
+is handled by a server. Novaboot users need to understand where to
+copy their files to the TFTP server (L</--copy>) and which IP
+addresses their target will get, but do not need to configure the
+servers themselves.
 
-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.
+=item C: Novaboot server running novaboot-shell
 
-For example, with C<novaboot> you can:
+With this setup, the configuration is done on the server. Users only
+need to know the SSH account (L</--ssh>) used to communicate between
+novaboot and novaboot server. The server is implemented as a
+restricted shell (L<novaboot-shell(1)>) on the server. No need to give
+full shell access to novaboot users on the server.
+
+=back
+
+=head2 Simple examples of using C<novaboot>:
+
+To boot Linux (files F<bzImage> and F<rootfs.cpio> in current
+directory), create F<mylinux> file with this content:
+
+    #!/usr/bin/env novaboot
+    load bzImage console=ttyS0,115200
+    load rootfs.cpio
 
 =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<novaboot ./script>
-(or C<./script> as described above) will run Qemu and make it boot the
-configuration specified in the I<script>.
+Booting an OS in Qemu can be accomplished by giving the B<--qemu> option.
+Thus running
+
+ novaboot --qemu mylinux
+
+(or C<./mylinux --qemu> as described above) will run Qemu and make it
+boot the configuration specified in the F<mylinux> script. How is qemu
+started can be configured in various ways (see below).
 
 =item 2.
 
 Create a bootloader configuration file (currently supported
-bootloaders are GRUB, GRUB2, Pulsar and uBoot) and copy it with all
-other files needed for booting to another, perhaps remote, location.
+bootloaders are GRUB, GRUB2, ISOLINUX, Pulsar, and U-Boot) and copy it
+with all other files needed for booting to a remote TFTP server. Then
+use a TCP/IP-controlled relay/serial-to-TCP converter to reset the
+target and receive its serial output.
 
- ./script --server --iprelay=192.168.1.2
+ ./mylinux --grub2 --copy=192.168.1.1:/tftp --iprelay=192.168.1.2
 
-This command copies files to a TFTP server specified in the
-configuration file and uses TCP/IP-controlled relay to reset the test
-box and receive its serial output.
+Alternatively, you can put these switches to the configuration file
+and run:
+
+ ./mylinux --target mytarget
 
 =item 3.
 
-Run DHCP and TFTP server on developer's machine to PXE-boot the OS
-from it. E.g.
+Specifying all the options needed by novaboot to successfully control
+the target, either on command line or in configuration files, can be
+difficult for users. Novaboot supports configuring the target
+centrally via L<novaboot-shell(1)> on a server. With such a
+configuration, users only need to use the B<--ssh> option to specify
+where to boot their OS:
 
- ./script --dhcp-tftp
+ ./mylinux --ssh myboard@example.com
 
-When a PXE-bootable machine is connected via Ethernet to developer's
-machine, it will boot the configuration described in I<script>.
+Typically, the server is the computer connected to and controlling the
+target board and running the TFTP server.
 
 =item 4.
 
-Create bootable ISO images. E.g.
+Run DHCP and TFTP server on developer's machine to boot the target
+from it.
+
+ ./mylinux --dhcp-tftp
+
+This usage is useful when no network infrastructure is in place, and
+the target is connected directly to developer's box.
+
+=item 5.
+
+Create bootable ISO image.
 
  novaboot --iso -- script1 script2
 
-The created ISO image will have GRUB bootloader installed on it and
-the boot menu will allow selecting between I<script1> and I<script2>
-configurations.
+The created ISO image will have ISOLINUX bootloader installed on it,
+and the boot menu will allow selecting between I<script1> and
+I<script2> configurations.
 
 =back
 
-=head1 PHASES AND OPTIONS
+=head1 OPTIONS AND PHASES
+
+Novaboot performs its work in several phases. Command line options
+described bellow influence the execution of each phase or allow their
+skipping. The list of phases (in the execution order) is as follows.
+
+=over
 
-Novaboot performs its work in several phases. Each phase can be
-influenced by several options, certain phases can be skipped. The list
-of phases (in the execution order) and the corresponding options
-follows.
+=item 1. L<Configuration reading|/Configuration reading phase>
+
+=item 2. L<Command line processing|/Command line processing phase>
+
+=item 3. L<Script preprocessing|/Script preprocessing phase>
+
+=item 4. L<File generation|/File generation phase>
+
+=item 5. L<Target connection|/Target connection check>
+
+=item 6. L<File deployment|/File deployment phase>
+
+=item 7. L<Target power-on and reset|/Target power-on and reset phase>
+
+=item 8. L<Interaction with the bootloader|/Interaction with the bootloader on the target>
+
+=item 9. L<Target interaction|/Target interaction phase>
+
+=back
+
+Each phase is described in the following sections together with the
+command line options that control it.
 
 =head2 Configuration reading phase
 
-After starting, novaboot reads configuration files. By default, it
-searches for files named F<.novaboot> starting from the directory of
-the novaboot script (or working directory, see bellow) and continuing
-upwards up to the root directory. The configuration files are read in
-order from the root directory downwards with latter files overriding
-settings from the former ones.
+After starting, novaboot reads zero or more configuration files. We
+describe their content in section L</"CONFIGURATION FILES">. By default, the
+configuration is read from multiple locations. First from the system
+configuration directory (F</etc/novaboot.d/>), second from the user
+configuration file (F<~/.config/novaboot>) and third from F<.novaboot>
+files along the path to the current directory. Alternatively, a single
+configuration file specified with the B<-c> switch or with the
+C<NOVABOOT_CONFIG> environment variable is read. The latter read files
+override settings from the former ones.
+
+The system configuration directory is determined by the content of
+NOVABOOT_CONFIG_DIR environment variable and defaults to
+F</etc/novaboot.d>. Files in this directory with names consisting
+solely of English letters, numbers, dashes '-' and underscores '_'
+(note that dot '.' is not included) are read in alphabetical order.
+
+Then, the user configuration file is read from
+F<$XDG_CONFIG_HOME/novaboot>. If C<$XDG_CONFIG_HOME> environment
+variable is not set F<~/.config/novaboot> is read instead.
+
+Finally, novaboot searches for files named F<.novaboot> starting from the
+directory of the novaboot script (or working directory, see bellow)
+and continuing upwards up to the root directory. The found
+configuration files are then read in the opposite order (i.e. from the
+root directory downwards). This ordering allows having, for example, a project
+specific configuration in F<~/project/.novaboot>.
+
+Note the difference between F<~/.config/novaboot> and F<~/.novaboot>.
+The former one is always read, whereas the latter only when novaboot
+script or working directory is under the C<$HOME> directory.
 
 In certain cases, the location of the novaboot script cannot be
-determined in this early phase. This happens either when the script is
-read from the standard input or when novaboot is invoked explicitly
-and options precede the script name, as in the example L</"4."> above.
-In this case the current working directory is used as a starting point
-for configuration file search.
+determined in this early phase. This situation happens either when the script is
+read from the standard input or when novaboot is invoked explicitly as
+in the example L</"4."> above. In this case, the current working
+directory is used as a starting point for configuration file search
+instead of the novaboot script directory.
 
 =over 8
 
@@ -885,7 +1617,7 @@ Use the specified configuration file instead of the default one(s).
 
 =item --dump-config
 
-Dump the current configuration to stdout end exits. Useful as an
+Dump the current configuration to stdout end exit. Useful as an
 initial template for a configuration file.
 
 =item -h, --help
@@ -895,45 +1627,71 @@ Print short (B<-h>) or long (B<--help>) help.
 =item -t, --target=I<target>
 
 This option serves as a user configurable shortcut for other novaboot
-options. The effect of this option is the same as the options stored
-in the C<%targets> configuration variable under key I<target>. See
-also L</"CONFIGURATION FILE">.
+options. The effect of this option is the same as specifying the
+options stored in the C<%targets> configuration variable under key
+I<target>. See also L</"CONFIGURATION FILES">.
+
+When this option is not given, novaboot tries to determine the target
+to use from either B<NOVABOOT_TARGET> environment variable or
+B<$default_target> configuration file variable.
+
+=item --ssh=I<user@hostname>
+
+Configures novaboot to control the target via C<novaboot-shell>
+running remotely via SSH.
+
+Using this option is the same as specifying B<--remote-cmd>,
+B<--remote-expect>, B<--copy> B<--rsync-flags>, B<--prefix> and
+B<--reset-cmd> manually in a way compatible with C<novaboot-shell>.
+The server can be configured to provide other, safe bootloader-related
+options, to the client. When this happens, novaboot prints them to
+stdout.
+
+Currently, this in an initial experimental implementation. We plan to
+change/extend this feature soon!
 
 =back
 
 =head2 Script preprocessing phase
 
-This phases allows to modify the parsed novaboot script before it is
+This phase allows modifying the parsed novaboot script before it is
 used in the later phases.
 
 =over 8
 
 =item -a, --append=I<parameters>
 
-Appends a string to the first "filename" line in the novaboot script.
-This can be used to append parameters to the kernel's or root task's
-command line.
+Append a string to the first C<load> line in the novaboot script. This option
+can be used to append parameters to the kernel's or root task's
+command line. This option can appear multiple times.
 
 =item -b, --bender
 
-Use F<bender> chainloader. Bender scans the PCI bus for PCI serial
-ports and stores the information about them in the BIOS data area for
-use by the kernel.
+Use L<Bender|https://github.com/TUD-OS/morbo/blob/master/standalone/bender.c>
+chainloader. Bender scans the PCI bus for PCI serial ports and stores
+the information about them in the BIOS data area for use by the
+kernel.
 
 =item --chainloader=I<chainloader>
 
-Chainloader that is loaded before the kernel and other files specified
-in the novaboot script. E.g. 'bin/boot/bender promisc'.
+Specifies a chainloader that is loaded before the kernel and other
+files specified in the novaboot script. E.g. 'bin/boot/bender
+promisc'.
 
 =item --dump
 
-Prints the content of the novaboot script after removing comments and
-evaluating all I<--scriptmod> expressions. Exit after reading (and
-dumping) the script.
+Print the modules to boot and their parameters, after this phase
+finishes. Then exit. This is useful for seeing the effect of other
+options in this section.
+
+=item -k, --kernel=F<file>
+
+Replace the first word on the first C<load> line in the novaboot
+script with F<file>.
 
-=item --scriptmod=I<perl expression>
+=item --scriptmod=I<Perl expression>
 
-When novaboot script is read, I<perl expression> is executed for every
+When novaboot reads the script, I<Perl expression> is executed for every
 line (in $_ variable). For example, C<novaboot
 --scriptmod=s/sigma0/omega6/g> replaces every occurrence of I<sigma0>
 in the script with I<omega6>.
@@ -943,21 +1701,16 @@ from the configuration file, which has the same effect. If this option
 is given multiple times all expressions are evaluated in the command
 line order.
 
-=item --strip-rom
-
-Strip I<rom://> prefix from command lines and generated config files.
-The I<rom://> prefix is used by NUL. For NRE, it has to be stripped.
-
 =back
 
 =head2 File generation phase
 
-In this phase, files needed for booting are generated in a so called
-I<build directory> (see TODO). In most cases configuration for a
-bootloader is generated automatically by novaboot. It is also possible
-to generate other files using I<heredoc> or I<"<"> syntax in novaboot
-scripts. Finally, binaries can be generated in this phases by running
-C<scons> or C<make>.
+In this phase, files needed for booting are generated in a so-called
+I<build directory> (see L</--build-dir>). In most cases configuration
+for a bootloader is generated automatically by novaboot. It is also
+possible to generate other files using I<heredoc> or I<"<"> syntax in
+novaboot scripts. Finally, novaboot can generate binaries in this phases by
+running C<scons> or C<make>.
 
 =over 8
 
@@ -970,6 +1723,8 @@ configuration file defines the C<$builddir> variable, its value is
 used. Otherwise, it is the directory that contains the first processed
 novaboot script.
 
+See also L</BUILDDIR> variable.
+
 =item -g, --grub[=I<filename>]
 
 Generates grub bootloader menu file. If the I<filename> is not
@@ -978,26 +1733,38 @@ build directory (see B<--build-dir>).
 
 =item --grub-preamble=I<prefix>
 
-Specifies the I<preable> that is at the beginning of the generated
+Specifies the I<preamble> that is at the beginning of the generated
 GRUB or GRUB2 config files. This is useful for specifying GRUB's
 timeout.
 
-=item --grub-prefix=I<prefix>
+=item --prefix=I<prefix>
 
-Specifies I<prefix> that is put in front of every file name in GRUB's
-F<menu.lst>. The default value is the absolute path to the build directory.
+Specifies I<prefix> (e.g. F</srv/tftp>) that is put in front of every
+filename in generated bootloader configuration files (or in U-Boot
+commands).
 
 If the I<prefix> contains string $NAME, it will be replaced with the
 name of the novaboot script (see also B<--name>).
 
+If the I<prefix> contains string $BUILDDIR, it will be replaced with
+the build directory (see also B<--build-dir>).
+
+=item --grub-prefix
+
+Alias for B<--prefix>.
+
 =item --grub2[=I<filename>]
 
-Generate GRUB2 menuentry in I<filename>. If I<filename> is not
-specified F<grub.cfg> is used. The content of the menuentry can be
-customized with B<--grub-preable>, B<--grub2-prolog> or
+Generate GRUB2 menu entry in I<filename>. If I<filename> is not
+specified F<./boot/grub/grub.cfg> is used. The content of the menu entry can be
+customized with B<--grub-preamble>, B<--grub2-prolog> or
 B<--grub_prefix> options.
 
-In order to use the the generated menuentry on your development
+GRUB2 can boot multiboot-compliant kernels and a few kernels with specific
+support. L</BOOT_METHOD> could be used to specify the command used by GRUB2 to
+load the kernel. See L<GNU GRUB Manual|https://www.gnu.org/software/grub/manual/grub/grub.html#Booting>.
+
+To use the generated menu entry on your development
 machine that uses GRUB2, append the following snippet to
 F</etc/grub.d/40_custom> file and regenerate your grub configuration,
 i.e. run update-grub on Debian/Ubuntu.
@@ -1008,8 +1775,7 @@ i.e. run update-grub on Debian/Ubuntu.
 
 =item --grub2-prolog=I<prolog>
 
-Specifies text I<preable> that is put at the begiging of the entry
-GRUB2 entry.
+Specifies the text that novaboot puts at the beginning of the GRUB2 menu entry.
 
 =item -m, --make[=make command]
 
@@ -1023,8 +1789,9 @@ server directory where the boot files are copied to.
 
 =item --no-file-gen
 
-Do not generate files on the fly (i.e. "<" syntax) except for the
-files generated via "<<WORD" syntax.
+Do not run external commands to generate files (i.e. "<" syntax and
+C<run> keyword). This switch does not influence the generation of files
+specified with "<<WORD" syntax.
 
 =item -p, --pulsar[=mac]
 
@@ -1037,6 +1804,11 @@ I<novaboot>.
 Runs C<scons> to build files that are not generated by novaboot
 itself.
 
+=item --strip-rom
+
+Strip I<rom://> prefix from command lines and generated config files.
+The I<rom://> prefix is used by NUL. For NRE, it has to be stripped.
+
 =item --gen-only
 
 Exit novaboot after file generation phase.
@@ -1045,20 +1817,34 @@ Exit novaboot after file generation phase.
 
 =head2 Target connection check
 
-If supported by the target, the connection to it is made and it is
-checked whether the target is not occupied by another novaboot
-user/instance.
+In this phase novaboot connects to target's serial port (if it has
+one). If another novaboot user/instance occupies the target, novaboot
+exits here with an error message.
 
 =over 8
 
+=item --amt=I<"[user[:password]@]host[:port]>
+
+Use Intel AMT technology to control the target machine. WS management
+is used to powercycle it and Serial-Over-Lan (SOL) for input/output.
+The hostname or (IP address) is given by the I<host> parameter. If the
+I<password> is not specified, environment variable AMT_PASSWORD is
+used. The I<port> specifies a TCP port for SOL. If not specified, the
+default is 16992. The default I<user> is admin.
+
 =item --iprelay=I<addr[:port]>
 
 Use TCP/IP relay and serial port to access the target's serial port
-and powercycle it. The IP address of the relay is given by I<addr>
-parameter. If I<port> is not specified, it default to 23.
+and powercycle it. The I<addr> parameter specifies the IP address of
+the relay. If I<port> is not specified, it defaults to 23.
 
 Note: This option is supposed to work with HWG-ER02a IP relays.
 
+=item --iprelay-cmd=I<command>
+
+Similar to B<--iprelay> but uses I<command> to talk to the iprelay
+rather than direct network connection.
+
 =item -s, --serial[=device]
 
 Target's serial line is connected to host's serial line (device). The
@@ -1080,8 +1866,36 @@ example C<ssh server 'cu -l /dev/ttyS0'>.
 
 =item --remote-expect=I<string>
 
-Wait for reception of I<string> on the remote connection before
-continuing.
+Wait for reception of I<string> after establishing the remote serial
+line connection. Novaboot assumes that after establishing the serial
+line connection, the user running novaboot has exclusive access to the
+target. If establishing of the serial line connection happens
+asynchronously (e.g. running a command remotely via SSH), we need this
+option to wait until the exclusive access is confirmed by the remote
+side.
+
+Depending on target configuration, this option can solve two practical
+problems: 1) Overwriting of files deployed by another user currently
+using the target. 2) Resetting the target board before serial line
+connection is established and thus missing bootloader interaction.
+
+Example of usage with the L<sterm
+tool|https://rtime.felk.cvut.cz/gitweb/sojka/sterm.git>:
+
+  --remote-cmd='ssh -tt example.com sterm -v /dev/ttyUSB0' --remote-expect='sterm: Connected'
+
+=item --remote-expect-silent=I<string>
+
+The same as B<--remote-expect> except that the remote output is not
+echoed to stdout while waiting for the I<string>. Everything after the
+matched string is printed to stdout, so you may want to include line
+end characters in the I<string> as well.
+
+=item --remote-expect-timeout=I<seconds>
+
+Timeout in seconds for B<--remote-expect> or
+B<--remote-expect-seconds>. When negative, waits forever. The default
+is -1 seconds.
 
 =back
 
@@ -1101,13 +1915,37 @@ directly connected by a plain Ethernet cable to your workstation.
 
 The DHCP and TFTP servers require root privileges and C<novaboot>
 uses C<sudo> command to obtain those. You can put the following to
-I</etc/sudoers> to allow running the necessary commands without
-asking for password.
+I</etc/sudoers> to allow running the necessary commands without asking
+for a password.
+
+ 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 --listen --secure -v -v -v --pidfile tftpd.pid *, /usr/bin/touch dhcpd.leases, /usr/bin/pkill --pidfile=dhcpd.pid, /usr/bin/pkill --pidfile=tftpd.pid
+ your_login ALL=NOPASSWD: NOVABOOT
+
+=item --tftp
+
+Starts a TFTP server on your workstation. This is similar to
+B<--dhcp-tftp> except that DHCP server is not started.
+
+The TFTP server requires root privileges and C<novaboot> uses C<sudo>
+command to obtain those. You can put the following to I</etc/sudoers>
+to allow running the necessary commands without asking for a password.
 
- 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 --pidfile tftpd.pid *, /usr/bin/touch dhcpd.leases, /usr/bin/pkill --pidfile=dhcpd.pid, /usr/bin/pkill --pidfile=tftpd.pid
+ Cmnd_Alias NOVABOOT =  /usr/sbin/in.tftpd --listen --secure -v -v -v --pidfile tftpd.pid *, /usr/bin/pkill --pidfile=tftpd.pid
  your_login ALL=NOPASSWD: NOVABOOT
 
-=item -i, --iso[=filename]
+=item --tftp-port=I<port>
+
+Port to run the TFTP server on. Implies B<--tftp>.
+
+=item --netif=I<network interface>
+
+Network interface used to deploy files to the target. This option
+influences the configuration of the DHCP server started by
+B<--dhcp-tftp> and the value that B<$NB_MYIP> get replaced with during
+U-Boot conversation. The default value is C<$netif> variable from
+configuration files, which defaults to I<eth0>.
+
+=item --iso[=filename]
 
 Generates the ISO image that boots NOVA system via GRUB. If no filename
 is given, the image is stored under I<NAME>.iso, where I<NAME> is the name
@@ -1115,36 +1953,53 @@ of the novaboot script (see also B<--name>).
 
 =item --server[=[[user@]server:]path]
 
-Copy all files needed for booting to another location (implies B<-g>
-unless B<--grub2> is given). The files will be copied (by B<rsync>
-tool) to the directory I<path>. If the I<path> contains string $NAME,
-it will be replaced with the name of the novaboot script (see also
-B<--name>).
+Alias of B<--copy> (kept for backward compatibility).
+
+=item --copy[=[[user@]server:]path]
+
+Copy all files needed for booting to another location. The files will
+be copied (by B<rsync> tool) to the directory I<path>. If the I<path>
+contains string $NAME, it will be replaced with the name of the
+novaboot script (see also B<--name>).
+
+=item --rsync-flags=I<flags>
+
+Specifies I<flags> to append to F<rsync> command line when
+copying files as a result of I<--copy> option.
 
 =item --concat
 
-If B<--server> is used and its value ends with $NAME, then after
+If B<--copy> is used and its value ends with $NAME, then after
 copying the files, a new bootloader configuration file (e.g. menu.lst)
-is created at I<path-wo-name>, i.e. the path specified by B<--server>
+is created at I<path-wo-name>, i.e. the path specified by B<--copy>
 with $NAME part removed. The content of the file is created by
 concatenating all files of the same name from all subdirectories of
 I<path-wo-name> found on the "server".
 
-=item --rsync-flags=I<flags>
+=item --ider
+
+Use Intel AMT technology for IDE redirection. This allows the target
+machine to boot from novaboot created ISO image. Implies B<--iso>.
 
-Specifies which I<flags> are appended to F<rsync> command line when
-copying files as a result of I<--server> option.
+The experimental C<amtider> utility needed by this option can be
+obtained from https://github.com/wentasah/amtterm.
 
 =back
 
 =head2 Target power-on and reset phase
 
+At this point, the target is reset (or switched on/off). There are
+several ways how this can be accomplished. Resetting a physical target
+can currently be accomplished by the following options: B<--amt>,
+B<--iprelay>, B<--reset-cmd> and B<--reset-send>.
+
 =over 8
 
 =item --on, --off
 
-Switch on/off the target machine. Currently works only with
-B<--iprelay>.
+Switch on/off the target machine and exit. The script (if any) is
+completely ignored. Currently, it works only with the following
+options: B<--iprelay>, B<--amt>, B<--ssh>.
 
 =item -Q, --qemu[=I<qemu-binary>]
 
@@ -1163,7 +2018,16 @@ Replace the default qemu flags (QEMU_FLAGS variable or C<-cpu coreduo
 
 =item --reset-cmd=I<cmd>
 
-Command that resets the target.
+Runs command I<cmd> to reset the target.
+
+=item --reset-send=I<string>
+
+Reset the target by sending the given I<string> to the remote serial
+line. "\n" sequences are replaced with the newline character.
+
+=item --no-reset, --reset
+
+Disable/enable resetting of the target.
 
 =back
 
@@ -1171,70 +2035,284 @@ Command that resets the target.
 
 =over 8
 
-=item --uboot
+=item --uboot[=I<prompt>]
+
+Interact with U-Boot bootloader to boot the thing described in the
+novaboot script. I<prompt> specifies the U-Boot's prompt (default is
+"=> ", other common prompts are "U-Boot> " or "U-Boot# ").
+
+=item --no-uboot
+
+Disable U-Boot interaction previously enabled with B<--uboot>.
+
+=item --uboot-stop-key=I<key>
 
-Interact with uBoot bootloader to boot the thing described in the
-novaboot script. Implementation of this option is currently tied to a
-particular board that we use. It may be subject to changes in the
-future!
+Character, which is sent as a response to U-Boot's "Hit any key to
+stop autoboot" message. The default value is newline, but some devices
+(e.g. TP-Link TD-W8970) require a specific key to be pressed.
 
 =item --uboot-init
 
-Command(s) to send the uBoot bootloader before loading the images and
-botting them.
+Command(s) to send the U-Boot bootloader before loading the images and
+booting them. This option can be given multiple times. After sending
+commands from each option novaboot waits for U-Boot I<prompt>.
+
+If the command contains string I<$NB_MYIP> then this string is
+replaced by IPv4 address of eth0 interface (see also B<--netif>).
+Similarly, I<$NB_PREFIX> is replaced with prefix given by B<--prefix>.
+
+See also C<uboot> keyword in L</"NOVABOOT SCRIPT SYNTAX">).
+
+=item --uboot-addr I<name>=I<address>
+
+Load address of U-Boot's C<tftpboot> command for loading I<name>,
+where name is one of I<kernel>, I<ramdisk> or I<fdt> (flattened device
+tree).
+
+The default addresses are ${I<name>_addr_r}, i.e. U-Boot environment
+variables used by convention for this purpose.
+
+=item --uboot-cmd=I<command>
+
+Specifies U-Boot command used to execute the OS. If the command
+contains strings C<$kernel_addr>, C<$ramdisk_addr>, C<$fdt_addr>,
+these are replaced with the addresses configured with B<--uboot-addr>.
+
+The default value is
+
+    bootm $kernel_addr $ramdisk_addr $fdt_addr
+
+or the C<UBOOT_CMD> variable if defined in the novaboot script.
 
 =back
 
 =head2 Target interaction phase
 
-In this phase, target's serial output is passed to C<novaboot> stdout.
-If C<novaboot>'s stdin is on TTY, the stdin is passed to the target
-allowing interactive work with the target.
+In this phase, target's serial output is redirected to stdout and if
+stdin is a TTY, it is redirected to the target's serial input allowing
+interactive work with the target.
+
+=over 8
+
+=item --exiton=I<string>
+
+When the I<string> is sent by the target, novaboot exits. This option can
+be specified multiple times, in which case novaboot exits whenever
+either of the specified strings is sent.
+
+If the I<string> is C<-re>, then the next B<--exiton>'s I<string> is
+treated as a regular expression. For example:
+
+    --exiton -re --exiton 'error:.*failed'
+
+=item --exiton-re=I<regex>
+
+The same as --exiton -re --exiton I<regex>.
+
+=item --exiton-timeout=I<seconds>
+
+By default B<--exiton> waits for the string match forever. When this
+option is specified, "exiton" timeouts after the specified number of
+seconds and novaboot returns non-zero exit code.
+
+=item -i, --interactive
+
+Setup things for the interactive use of the target. Your terminal will
+be switched to raw mode. In raw mode, your local terminal does not
+process input in any way (no echoing of entered characters, no
+interpretation of special characters). This, among others, means that
+Ctrl-C is passed to the target and does not interrupt novaboot. To
+exit from novaboot interactive mode type "~~.".
+
+=item --no-interaction, --interaction
+
+Skip resp. force target interaction phase. When skipped, novaboot exits
+immediately after the boot is initiated.
+
+=item --expect=I<string>
+
+When the I<string> is received from the target, send the string specified
+with the subsequent B<--send*> option to the target.
+
+=item --expect-re=I<regex>
+
+When target's output matches regular expression I<regex>, send the
+string specified with the subsequent B<--send*> option to the target.
 
-This phase end when the target hangs up or when Ctrl-C is pressed.
+=item --expect-raw=I<perl-code>
+
+Provides direct control over Perl's Expect module.
+
+=item --send=I<string>
+
+Send I<string> to the target after the previously specified
+B<--expect*> was matched in the target's output. The I<string> may
+contain escape sequences such as "\n".
+
+Note that I<string> is actually interpreted by Perl, so it can contain
+much more that escape sequences. This behavior may change in the
+future.
+
+Example: C<--expect='login: ' --send='root\n'>
+
+=item --sendcont=I<string>
+
+Similar to B<--send> but continue expecting more input.
+
+Example: C<--expect='Continue?' --sendcont='yes\n'>
+
+=item --final-eol, --no-final-eol
+
+By default, B<novaboot> always prints an end-of-line character at the
+end of its execution in order to ensure that the output of programs
+started after novaboot appears at the beginning of the line. When this
+is not desired B<--no-final-eol> option can be used to override this
+behavior.
+
+=back
 
 =head1 NOVABOOT SCRIPT SYNTAX
 
-The syntax tries to mimic POSIX shell syntax. The syntax is defined with the following rules.
+The syntax tries to mimic POSIX shell syntax. The syntax is defined
+by the following rules.
 
-Lines starting with "#" are ignored.
+Lines starting with "#" and empty lines are ignored.
 
 Lines that end with "\" are concatenated with the following line after
 removal of the final "\" and leading whitespace of the following line.
 
-Lines in the form I<VARIABLE=...> (i.e. matching '^[A-Z_]+=' regular
-expression) assign values to internal variables. See VARIABLES
+Lines of the form I<VARIABLE=...> (i.e. matching '^[A-Z_]+=' regular
+expression) assign values to internal variables. See L</VARIABLES>
 section.
 
-Otherwise, the first word on the line represents the filename
-(relative to the build directory (see B<--build-dir>) of the module to
-load and the remaining words are passed as the command line
-parameters.
+Otherwise, the first word on the line defines the meaning of the line.
+The following keywords are supported:
+
+=over 4
+
+=item C<load>
+
+These lines represent modules to boot. The
+word after C<load> is a file name (relative to the build directory
+(see B<--build-dir>) of the module to load and the remaining words are
+passed to it as the command line parameters.
+
+When booting Linux, the first C<load> line usually refers to the
+kernel image and its command line parameters (unless you use some
+special pre-loader). Other C<load> lines may refer to an initramfs
+image and/or a device tree blob. Their order is not important, as the
+device tree is recognized as the file name ending with C<.dtb>.
+
+When the C<load> line ends with "<<WORD" then the subsequent lines
+until the line containing solely WORD are copied literally to the file
+named on that line. This is similar to the heredoc feature of UNIX
+shells.
+
+When the C<load> line ends with "< CMD" then command CMD is executed
+with F</bin/sh> and its standard output is stored in the file named on
+that line. The SRCDIR variable in CMD's environment is set to the
+absolute path of the directory containing the interpreted novaboot
+script.
+
+=item C<copy>
+
+These lines are similar to C<load> lines. The
+file mentioned there is copied to the same place as in the case of C<load>
+(e.g. tftp server), but the file is not used in the bootloader
+configuration. Such a file can be used by the target for other
+purposes than booting, e.g. at OS runtime or for firmware update.
+
+=item C<chld>
+
+Chainload another bootloader. Instead of loading multiboot modules
+identified with C<load> keyword, run another bootloader. This is
+currently supported only by pulsar and can be used to load e.g. Grub
+as in the example below:
+
+ chld boot/grub/i386-pc/core.0
+
+
+=item C<run>
+
+Lines starting with C<run> keyword contain shell commands that are run
+during file generation phase. This is the same as the "< CMD" syntax
+for C<load> keyboard except that the command's output is not
+redirected to a file. The ordering of commands is the same as they
+appear in the novaboot script.
+
+=item C<uboot>
+
+These lines represent U-Boot commands that are sent to the target if
+B<--uboot> option is given. Having a U-Boot line in the novaboot
+script is the same as giving B<--uboot-init> option to novaboot. The
+following syntax variants are supported:
 
-When the line ends with "<<WORD" then the subsequent lines until the
-line containing only WORD are copied literally to the file named on
-that line.
 
-When the line ends with "< CMD" the command CMD is executed with
-C</bin/sh> and its standard output is stored in the file named on that
-line. The SRCDIR variable in CMD's environment is set to the absolute
-path of the directory containing the interpreted novaboot script.
+  uboot[:<timeout>] <string> [> <file>]
+  uboot[:<timeout>] < <shell> [> <file>]
+
+C<string> is the literal U-Boot command.
+
+The C<uboot> keyword can be suffixed with timeout specification. The
+syntax is C<uboot:Ns>, where C<N> is the whole number of seconds. If
+the U-Boot command prompt does not appear before the timeout, novaboot
+fails. The default timeout is 10 seconds.
+
+In the second variant with the C<<> character the shell code is
+executed and its standard output is sent to U-Boot. Example:
+
+  uboot < printf "mmc write \$loadaddr 1 %x" $(($(/usr/bin/stat -c%s rootfs.ext4) / 512))
+
+When C<E<gt> file> part is present, the output of the U-Boot command
+is written into the given file.
+
+=back
+
+Example (Linux):
+
+  #!/usr/bin/env novaboot
+  load bzImage console=ttyS0,115200
+  run  make -C buildroot
+  load rootfs.cpio < gen_cpio buildroot/images/rootfs.cpio "myapp->/etc/init.d/S99myapp"
+
+Example (NOVA User Land - NUL):
 
-Example:
   #!/usr/bin/env novaboot
   WVDESC=Example program
-  bin/apps/sigma0.nul S0_DEFAULT script_start:1,1 \
-    verbose hostkeyb:0,0x60,1,12,2
-  bin/apps/hello.nul
-  hello.nulconfig <<EOF
+  load bin/apps/sigma0.nul S0_DEFAULT script_start:1,1 \
+                           verbose hostkeyb:0,0x60,1,12,2
+  load bin/apps/hello.nul
+  load hello.nulconfig <<EOF
   sigma0::mem:16 name::/s0/log name::/s0/timer name::/s0/fs/rom ||
   rom://bin/apps/hello.nul
   EOF
 
-This example will load three modules: sigma0.nul, hello.nul and
-hello.nulconfig. sigma0 gets some command line parameters and
-hello.nulconfig file is generated on the fly from the lines between
-<<EOF and EOF.
+This example will load three modules: F<sigma0.nul>, F<hello.nul> and
+F<hello.nulconfig>. sigma0 receives some command line parameters and
+F<hello.nulconfig> file is generated on the fly from the lines between
+C<<<EOF> and C<EOF>.
+
+Example (Zynq system update via U-Boot):
+
+  #!/usr/bin/env novaboot
+
+  uboot dhcp
+
+  # Write kernel to FAT filesystem on the 1st SD card partition
+  run mkimage -f uboot-image.its image.ub
+  copy image.ub
+  uboot:60s tftpboot ${loadaddr} $NB_PREFIX/image.ub
+  uboot fatwrite mmc 0:1 ${loadaddr} image.ub $filesize
+  uboot set bootargs console=ttyPS0,115200 root=/dev/mmcblk0p2
+
+  # Write root FS image to the 2nd SD card partition
+  copy rootfs/images/rootfs.ext4
+  uboot:60s tftpboot ${loadaddr} $NB_PREFIX/rootfs/images/rootfs.ext4
+  uboot mmc part > mmc-part.txt
+  uboot < printf "mmc write \$loadaddr %x %x" $(awk '{ if ($1 == "2") { print $2 }}' mmc-part.txt) $(($(/usr/bin/stat -L --printf=%s rootfs/images/rootfs.ext4) / 512))
+
+  UBOOT_CMD=boot
+
 
 =head2 VARIABLES
 
@@ -1242,61 +2320,91 @@ The following variables are interpreted in the novaboot script:
 
 =over 8
 
+=item BOOT_METHOD
+
+Specifies the way GRUB2 boots the kernel. For kernels with multiboot
+support use C<multiboot> method (the default). For Linux kernel use C<linux> method.
+
 =item BUILDDIR
 
 Novaboot chdir()s to this directory before file generation phase. The
 directory name specified here is relative to the build directory
 specified by other means (see L</--build-dir>).
 
-=item WVDESC
+=item EXITON
 
-Description of the wvtest-compliant program.
+Assigning this variable has the same effect as specifying L</--exiton>
+option.
 
-=item WVTEST_TIMEOUT
+=item INTERACTION
 
-The timeout in seconds for WvTest harness. If no complete line appears
-in the test output within the time specified here, the test fails. It
-is necessary to specify this for long running tests that produce no
-intermediate output.
+Setting this variable to zero is the same as giving
+L</--no-interaction>, specifying to one corresponds to
+L</--interaction>.
+
+=item HYPERVISOR_PARAMS
+
+Parameters passed to the hypervisor. The default value is "serial", unless
+overridden in the configuration file.
+
+=item KERNEL
+
+The kernel to use instead of the hypervisor specified in the
+configuration file with the C<$hypervisor> variable. The value should
+contain the name of the kernel image as well as its command line
+parameters. If this variable is defined and non-empty, the variable
+HYPERVISOR_PARAMS is not used.
+
+=item NO_BOOT
+
+If this variable is 1, the system is not booted. This is currently
+only implemented for U-Boot bootloader where it is useful for
+interacting with the bootloader without booting the system - e.g. for
+flashing.
 
 =item QEMU
 
-Use a specific qemu binary (can be overriden with B<-Q>) and flags
+Use a specific qemu binary (can be overridden with B<-Q>) and flags
 when booting this script under qemu. If QEMU_FLAGS variable is also
 specified flags specified in QEMU variable are replaced by those in
 QEMU_FLAGS.
 
 =item QEMU_FLAGS
 
-Use specific qemu flags (can be overriden with B<-q>).
+Use specific qemu flags (can be overridden with B<-q>).
 
-=item HYPERVISOR_PARAMS
+=item UBOOT_CMD
 
-Parameters passed to hypervisor. The default value is "serial", unless
-overriden in configuration file.
+See L</--uboot-cmd>.
 
-=item KERNEL
+=item WVDESC
 
-The kernel to use instead of NOVA hypervisor specified in the
-configuration file. The value should contain the name of the kernel
-image as well as its command line parameters. If this variable is
-defined and non-empty, the variable HYPERVISOR_PARAMS is not used.
+Description of the WvTest-compliant program.
+
+=item WVTEST_TIMEOUT
+
+The timeout in seconds for WvTest harness. If no complete line appears
+in the test output within the time specified here, the test fails. It
+is necessary to specify this for long running tests that produce no
+intermediate output.
 
 =back
 
-=head1 CONFIGURATION FILE
+=head1 CONFIGURATION FILES
 
 Novaboot can read its configuration from one or more files. By
-default, novaboot looks for files named F<.novaboot> as described in
-L</Configuration reading phase>. Alternatively, its location can be
-specified with the B<-c> switch or with the NOVABOOT_CONFIG
-environment variable. The configuration file has perl syntax and
-should set values of certain Perl variables. The current configuration
-can be dumped with the B<--dump-config> switch. Some configuration
-variables can be overriden by environment variables (see below) or by
-command line switches.
-
-Supporte configuration variables include:
+default, novaboot looks for files in F</etc/novaboot.d>, file
+F<~/.config/novaboot> and files named F<.novaboot> as described in
+L</Configuration reading phase>. Alternatively, configuration file
+location can be specified with the B<-c> switch or with the
+NOVABOOT_CONFIG environment variable. The configuration file has Perl
+syntax (i.e. it is better to put C<1;> as the last line) and should set
+values of certain Perl variables. The current configuration can be
+dumped with the B<--dump-config> switch. Some configuration variables
+can be overridden by environment variables (see below) or by command
+line switches.
+
+Supported configuration variables include:
 
 =over 8
 
@@ -1307,20 +2415,27 @@ file.
 
 =item $default_target
 
-Default target (see below) to use when no target is explicitely
-specified on command line with the B<--target> option.
+Default target (see below) to use when no target is explicitly
+specified with the B<--target> command line option or
+B<NOVABOOT_TARGET> environment variable.
+
+=item $netif
+
+Default value for the B<--netif> option. If not specified, it defaults
+to I<eth0>.
 
 =item %targets
 
-Hash of shortcuts to be used with the B<--target> option. If the hash
-contains, for instance, the following pair of values
+Hash of target definitions to be used with the B<--target> option. The
+key is the identifier of the target, the value is the string with
+command line options. For instance, if the configuration file contains:
 
'mybox' => '--server=boot:/tftproot --serial=/dev/ttyUSB0 --grub',
$targets{'mybox'} = '--copy=boot:/tftproot --serial=/dev/ttyUSB0 --grub',
 
 then the following two commands are equivalent:
 
- ./script --server=boot:/tftproot --serial=/dev/ttyUSB0 --grub
- ./script -t mybox
+ ./myos --copy=boot:/tftproot --serial=/dev/ttyUSB0 --grub
+ ./myos -t mybox
 
 =back
 
@@ -1328,7 +2443,7 @@ then the following two commands are equivalent:
 
 Some options can be specified not only via config file or command line
 but also through environment variables. Environment variables override
-the values from configuration file and command line parameters
+the values from the configuration file and command line parameters
 override the environment variables.
 
 =over 8
@@ -1338,12 +2453,34 @@ override the environment variables.
 Name of the novaboot configuration file to use instead of the default
 one(s).
 
+=item NOVABOOT_CONFIG_DIR
+
+Name of the novaboot configuration directory. When not specified
+F</etc/novaboot.d> is used.
+
+=item NOVABOOT_TARGET
+
+Name of the novaboot target to use. This overrides the value of
+B<$default_target> from the configuration file and can be overridden
+with the B<--target> command line option.
+
 =item NOVABOOT_BENDER
 
-Defining this variable has the same meaning as B<--bender> option.
+Defining this variable has the same effect as using B<--bender>
+option.
 
 =back
 
 =head1 AUTHORS
 
 Michal Sojka <sojka@os.inf.tu-dresden.de>
+
+Latest novaboot version can be found at
+L<https://github.com/wentasah/novaboot>.
+
+=cut
+
+# LocalWords:  novaboot Novaboot NOVABOOT TFTP PXE DHCP filename stty
+# LocalWords:  chainloader stdout Qemu qemu preprocessing ISOLINUX bootable
+# LocalWords:  config subprocesses sudo sudoers tftp dhcp IDE stdin
+# LocalWords:  subdirectories TTY whitespace heredoc POSIX WvTest