]> rtime.felk.cvut.cz Git - novaboot.git/blobdiff - novaboot
Add support for $NB_MYIP in --uboot-init
[novaboot.git] / novaboot
index 501dce074e5a748226c2218848aaad7d60f8ddaf..76b48bcbf060cccc3f7c12459d1d83759ac2bad4 100755 (executable)
--- a/novaboot
+++ b/novaboot
@@ -15,7 +15,8 @@
 
 use strict;
 use warnings;
-use Getopt::Long qw(GetOptionsFromString);
+use warnings (exists $ENV{NOVABOOT_TEST} ? (FATAL => 'all') : ());
+use Getopt::Long qw(GetOptionsFromString GetOptionsFromArray);
 use Pod::Usage;
 use File::Basename;
 use File::Spec;
@@ -26,13 +27,12 @@ use FileHandle;
 use IPC::Open2;
 use POSIX qw(:errno_h);
 use Cwd qw(getcwd abs_path);
+use Expect;
 
 # always flush
 $| = 1;
 
-my $invocation_dir = getcwd();
-
-chomp(my $gittop = `git rev-parse --show-toplevel 2>/dev/null`);
+my $invocation_dir = $ENV{PWD} || getcwd();
 
 ## Configuration file handling
 
@@ -40,13 +40,22 @@ chomp(my $gittop = `git rev-parse --show-toplevel 2>/dev/null`);
 $CFG::hypervisor = "";
 $CFG::hypervisor_params = "serial";
 $CFG::genisoimage = "genisoimage";
-$CFG::qemu = 'qemu -cpu coreduo -smp 2';
+$CFG::qemu = 'qemu-system-i386 -cpu coreduo -smp 2';
+$CFG::default_target = 'qemu';
 %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}; sleep 1" --remote-cmd="ssh -tt pc-sojkam.felk.cvut.cz \"sterm -d -s 115200 /dev/ttyUSB0\""',
+    "ryulocal" => '--dhcp-tftp --serial --uboot --uboot-init="dhcp; mw f0000b00 \${psc_cfg}; sleep 1" --reset-cmd="if which dtrrts; then dtrrts $NB_SERIAL 0 1; sleep 0.1; dtrrts $NB_SERIAL 1 1; fi"',
+
     );
-$CFG::scons = "scons -j2";
+chomp(my $nproc = `nproc`);
+$CFG::scons = "scons -j$nproc";
+$CFG::make = "make -j$nproc";
+
+my $builddir;
 
 sub read_config($) {
     my ($cfg) = @_;
@@ -63,6 +72,7 @@ sub read_config($) {
            die("ERROR: Failure processing '$cfg'");
        }
     }
+    $builddir = File::Spec->rel2abs($CFG::builddir, dirname($cfg)) if defined $CFG::builddir;
     print STDERR "novaboot: Read $cfg\n";
 }
 
@@ -72,65 +82,132 @@ my @cfgs;
     # not to the novaboot script. The problem with this approach is that
     # when a script is run as "novaboot <options> <script>" then $ARGV[0]
     # contains the first option. Hence the -f check.
-    my $dir = abs_path($invocation_dir . (($ARGV[0] && -f $ARGV[0]) ? '/'.dirname($ARGV[0]) : ''));
-    while (-d $dir && $dir ne "/") {
+    my $dir = File::Spec->rel2abs($ARGV[0] && -f $ARGV[0] ? dirname($ARGV[0]) : '', $invocation_dir);
+    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;
+    $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 ($append, $bender, $builddir, @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, $man, $no_file_gen, $off_opt, $on_opt, $pulsar, $pulsar_root, $qemu, $qemu_append, $qemu_flags_cmd, $rom_prefix, $rsync_flags, @scriptmod, $scons, $serial, $server);
+my $explicit_target;
+GetOptions ("target|t=s" => \$explicit_target);
+
+my ($amt, @append, $bender, @chainloaders, $concat, $config_name_opt, $dhcp_tftp, $dump_opt, $dump_config, @exiton, @expect_raw, $gen_only, $grub_config, $grub_prefix, $grub_preamble, $grub2_prolog, $grub2_config, $help, $ider, $iprelay, $iso_image, $interactive, $kernel_opt, $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, $tftp, $tftp_port, $uboot, @uboot_init);
 
 $rsync_flags = '';
 $rom_prefix = 'rom://';
+$stty = 'raw -crtscts -onlcr 115200';
+
+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 = ();
+}
 
-Getopt::Long::Configure(qw/no_ignore_case no_pass_through/);
 my %opt_spec;
 %opt_spec = (
-    "append|a=s"     => \$append,
+    "amt=s"         => \$amt,
+    "append|a=s"     => \@append,
     "bender|b"       => \$bender,
-    "build-dir=s"    => \$builddir,
+    "build-dir=s"    => sub { my ($n, $v) = @_; $builddir = File::Spec->rel2abs($v); },
     "concat"        => \$concat,
     "chainloader=s"  => \@chainloaders,
     "dhcp-tftp|d"    => \$dhcp_tftp,
     "dump"          => \$dump_opt,
     "dump-config"    => \$dump_config,
+    "exiton=s"       => \@exiton,
+    "expect=s"      => \&handle_expect,
+    "expect-re=s"    => \&handle_expect,
+    "expect-raw=s"   => sub { my ($n, $v) = @_; unshift(@expect_raw, eval($v)); },
     "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,
     "iprelay=s"             => \$iprelay,
-    "iso|i:s"       => \$iso_image,
+    "iso:s"         => \$iso_image,
+    "kernel|k=s"     => \$kernel_opt,
+    "interactive|i"  => \$interactive,
     "name=s"        => \$config_name_opt,
+    "make|m:s"      => \$make,
     "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|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,
     "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,
     "strip-rom"             => sub { $rom_prefix = ''; },
-    "target|t=s"     => sub { my ($opt_name, $opt_value) = @_;
-                             exists $CFG::targets{$opt_value} or die("Unknown target '$opt_value' (valid targets are: ".join(", ", sort keys(%CFG::targets)).")");
-                             GetOptionsFromString($CFG::targets{$opt_value}, %opt_spec); },
+    "stty=s"        => \$stty,
+    "tftp"          => \$tftp,
+    "tftp-port=i"    => \$tftp_port,
+    "uboot:s"       => \$uboot,
+    "uboot-init=s"   => \@uboot_init,
     "h"             => \$help,
     "help"          => \$man,
     );
-GetOptions %opt_spec or pod2usage(2);
+
+# First process target options
+{
+    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;
+    }
+    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 or die("Error in command line arguments");
 pod2usage(1) if $help;
 pod2usage(-exitstatus => 0, -verbose => 2) if $man;
 
@@ -151,24 +228,36 @@ 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"; }
+}
+
 # Default options
-if (defined $serial) { $serial ||= "/dev/ttyUSB0"; }
+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"; }
 
 ## 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, $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 = [],
@@ -179,21 +268,20 @@ while (<>) {
     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;
@@ -202,29 +290,67 @@ 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");
+       next;
+    }
+    if (s/^load *//) {         # Load line
+       die("novaboot: '$last_fn' line $.: Missing file name\n") unless /^[^ <]+/;
+       if (/^([^ ]*)(.*?)[[:space:]]*<<([^ ]*)$/) { # Heredoc start
+           push @$modules, "$1$2";
+           $file = [];
+           push @$generated, {filename => $1, content => $file};
+           $EOF = $3;
+           next;
+       }
+       if (/^([^ ]*)(.*?)[[:space:]]*< ?(.*)$/) { # Command substitution
+           push @$modules, "$1$2";
+           push @$generated, {filename => $1, command => $3};
+           next;
+       }
+       push @$modules, $_;
        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 (/^run (.*)/) {         # run line
+       push @$generated, {command => $1};
        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
 
@@ -241,7 +367,11 @@ sub generate_configs($$$) {
        print "novaboot: Created $fn\n";
       } elsif (exists $$g{command} && ! $no_file_gen) {
        $ENV{SRCDIR} = dirname(File::Spec->rel2abs( $filename, $invocation_dir ));
-       system_verbose("( $$g{command} ) > $$g{filename}");
+       if (exists $$g{filename}) {
+           system_verbose("( $$g{command} ) > $$g{filename}");
+       } else {
+           system_verbose($$g{command});
+       }
       }
     }
 }
@@ -267,7 +397,40 @@ sub generate_grub_config($$$$;$)
        }
     }
     close($fg);
-    print("novaboot: Created $CFG::builddir/$filename\n");
+    print("novaboot: Created $builddir/$filename\n");
+    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('To 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;
 }
 
@@ -297,7 +460,7 @@ sub generate_grub2_config($$$$;$$)
     }
     print $fg "}\n";
     close($fg);
-    print("novaboot: Created $CFG::builddir/$filename\n");
+    print("novaboot: Created $builddir/$filename\n");
     return $filename;
 }
 
@@ -322,14 +485,20 @@ sub generate_pulsar_config($$)
     # 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 $CFG::builddir/$filename\n");
+    print("novaboot: Created $builddir/$filename\n");
     return $filename;
 }
 
+sub shell_cmd_string(@)
+{
+    return join(' ', map((/^[-_=a-zA-Z0-9\/\.\+]+$/ ? "$_" : "'$_'"), @_));
+}
+
 sub exec_verbose(@)
 {
-    print "novaboot: Running: ".join(' ', map("'$_'", @_))."\n";
+    print "novaboot: Running: ".shell_cmd_string(@_)."\n";
     exec(@_);
+    exit(1); # should not be reached
 }
 
 sub system_verbose($)
@@ -350,10 +519,21 @@ if (exists $variables->{WVDESC}) {
     print "Testing \"all\" in $last_fn:\n";
 }
 
-## Handle reset and power on/off - TODO: move after the file generation phase
+## 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
+# check whether the target is not used by somebody else before
+# deploying files. Otherwise, we may rewrite other user's files on a
+# boot server.
+
+my $exp; # Expect object to communicate with the target over serial line
+
+my ($target_reset, $target_power_on, $target_power_off);
+my ($amt_user, $amt_password, $amt_host, $amt_port);
 
-my $IPRELAY;
 if (defined $iprelay) {
+    my $IPRELAY;
     $iprelay =~ /([.0-9]+)(:([0-9]+))?/;
     my $addr = $1;
     my $port = $3 || 23;
@@ -363,23 +543,15 @@ if (defined $iprelay) {
     print "novaboot: Connecting to IP relay... ";
     connect($IPRELAY, $paddr)    || die "connect: $!";
     print "done\n";
-    $IPRELAY->autoflush(1);
+    $exp = Expect->init(\*$IPRELAY);
+    $exp->log_stdout(1);
 
     while (1) {
-       print $IPRELAY "\xFF\xF6";
-       alarm(20);
-       local $SIG{ALRM} = sub { die "Relay AYT timeout"; };
-       my $ayt_reponse = "";
-       my $read = sysread($IPRELAY, $ayt_reponse, 100);
-       alarm(0);
-
-       chomp($ayt_reponse);
-       print "$ayt_reponse\n";
-       if ($ayt_reponse =~ /<iprelayd: not connected/) {
-           sleep(10);
-           next;
-       }
-       last;
+       print $exp "\xFF\xF6";  # AYT
+       my $connected = $exp->expect(20, # Timeout in seconds
+                                    '<iprelayd: connected>',
+                                    '-re', '<WEB51 HW[^>]*>');
+       last if $connected;
     }
 
     sub relaycmd($$) {
@@ -400,67 +572,181 @@ if (defined $iprelay) {
     sub relay($$;$) {
        my ($relay, $onoff, $can_giveup) = @_;
        my $confirmation = '';
-       print $IPRELAY relaycmd($relay, $onoff);
-
-       # We use non-blocking I/O and polling here because for some
-       # reason read() on blocking FD returns only after all
-       # requested data is available. If we get during the first
-       # read() only a part of confirmation, we may get the rest
-       # after the system boots and print someting, which may be too
-       # long.
-       $IPRELAY->blocking(0);
-
-       alarm(20); # Timeout in seconds
-       my $giveup = 0;
-        local $SIG{ALRM} = sub {
-           if ($can_giveup) { print("Relay confirmation timeout - ignoring\n"); $giveup = 1;}
-           else {die "Relay confirmation timeout";}
-       };
-       my $index;
-       while (($index=index($confirmation, relayconf($relay, $onoff))) < 0 && !$giveup) {
-           my $read = read($IPRELAY, $confirmation, 70, length($confirmation));
-           if (!defined($read)) {
-               die("IP relay: $!") unless $! == EAGAIN;
-               usleep(10000);
-               next;
+       $exp->log_stdout(0);
+       print $exp relaycmd($relay, $onoff);
+       my $confirmed = $exp->expect(20, # Timeout in seconds
+                                    relayconf($relay, $onoff));
+       if (!$confirmed) {
+           if ($can_giveup) {
+               print("Relay confirmation timeout - ignoring\n");
+           } else {
+               die "Relay confirmation timeout";
            }
-           #use MIME::QuotedPrint;
-           #print "confirmation = ".encode_qp($confirmation)."\n";
        }
-       alarm(0);
-       $IPRELAY->blocking(1);
+       $exp->log_stdout(1);
     }
-}
 
-if ($iprelay && (defined $on_opt || defined $off_opt)) {
-     relay(1, 1); # Press power button
-    if (defined $on_opt) {
-       usleep(100000);         # Short press
-    } else {
-       print "novaboot: Switching the target off...\n";
+    $target_reset = sub {
+       relay(2, 1, 1); # Reset the machine
+       usleep(100000);
+       relay(2, 0);
+    };
+
+    $target_power_off = sub {
+       relay(1, 1);            # Press power button
        usleep(6000000);        # Long press to switch off
+       relay(1, 0);
+    };
+
+    $target_power_on = sub {
+       relay(1, 1);            # Press power button
+       usleep(100000);         # Short press
+       relay(1, 0);
+    };
+}
+elsif ($serial) {
+    my $CONN;
+    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 (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
     }
-    print $IPRELAY relay(1, 0);
+
+    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 "novaboot: Running: $cmd\n" =~ s/\Q$amt_password\E/???/r;
+    $exp = Expect->spawn($cmd);
+    $exp->expect(10, "RUN_SOL") || die "Expect for 'RUN_SOL' timed out";
+
+}
+
+
+if ($remote_expect) {
+    $exp->expect(10, $remote_expect) || die "Expect for '$remote_expect' timed out";
+}
+
+if (defined $reset_cmd) {
+    $target_reset = sub {
+       system_verbose($reset_cmd);
+    };
+}
+
+if (defined $on_opt && defined $target_power_on) {
+    &$target_power_on();
+    exit;
+}
+if (defined $off_opt && defined $target_power_off) {
+    print "novaboot: Switching the target off...\n";
+    &$target_power_off();
     exit;
 }
 
-## Figure out the location of builddir and chdir() there
-if ($builddir) {
-    $CFG::builddir = $builddir;
+$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";
 } else {
-    if (! defined $CFG::builddir) {
-       $CFG::builddir = ( $gittop || $ENV{'HOME'}."/nul" ) . "/build";
-       if (! -d $CFG::builddir) {
-           $CFG::builddir = $ENV{SRCDIR} = dirname(File::Spec->rel2abs( ${$scripts[0]}{filename}, $invocation_dir ));
-       }
-    }
+    $builddir = $invocation_dir;
 }
 
-chdir($CFG::builddir) or die "Can't change directory to $CFG::builddir: $!";
-print "novaboot: Entering directory `$CFG::builddir'\n";
-
 ## File generation phase
-my (%files_iso, $menu_iso, $config_name, $filename);
+my (%files_iso, $menu_iso, $filename);
+my $config_name = '';
 
 foreach my $script (@scripts) {
     $filename = $$script{filename};
@@ -471,26 +757,15 @@ foreach my $script (@scripts) {
     ($config_name = $filename) =~ s#.*/##;
     $config_name = $config_name_opt if (defined $config_name_opt);
 
-    my $kernel;
-    if (exists $variables->{KERNEL}) {
-       $kernel = $variables->{KERNEL};
-    } else {
-       if ($CFG::hypervisor) {
-           $kernel = $CFG::hypervisor . " ";
-           if (exists $variables->{HYPERVISOR_PARAMS}) {
-               $kernel .= $variables->{HYPERVISOR_PARAMS};
-           } else {
-               $kernel .= $CFG::hypervisor_params;
-           }
-       }
+    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";
     }
-    @$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 ||= $CFG::builddir;
+    $prefix ||= $builddir;
     # TODO: use $grub_prefix as first parameter if some switch is given
     generate_configs('', $generated, $filename);
 
@@ -500,12 +775,14 @@ foreach my $script (@scripts) {
     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);
 
-### Run scons
-    if (defined $scons) {
+### Run scons or make
+    {
        my @files = map({ ($file) = m/([^ ]*)/; $file; } @$modules);
        # Filter-out generated files
-       my @to_build = grep({ my $file = $_; !scalar(grep($file eq $$_{filename}, @$generated)) } @files);
-       system_verbose($scons || $CFG::scons." ".join(" ", @to_build));
+       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);
     }
 
 ### Copy files (using rsync)
@@ -532,7 +809,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;
     }
@@ -540,13 +817,36 @@ 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);
+    } else {
+       # Older ISOLINUX version
+       @files = qw(/usr/lib/syslinux/isolinux.bin /usr/lib/syslinux/mboot.c32 /usr/lib/syslinux/menu.c32);
+    }
+    system_verbose("cp @files 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");
-    print("ISO image created: $CFG::builddir/$iso_image\n");
+
+    # 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");
 }
 
 exit(0) if defined $gen_only;
@@ -567,9 +867,9 @@ sub trim($) {
     return $str
 }
 
-### Qemu
+### Start in Qemu
 
-if (!(defined $dhcp_tftp || defined $serial || defined $iprelay || defined $server || defined $iso_image)) {
+if (defined $qemu) {
     # Qemu
     $qemu ||= $variables->{QEMU} || $CFG::qemu;
     my @qemu_flags = split(" ", $qemu);
@@ -581,7 +881,7 @@ if (!(defined $dhcp_tftp || defined $serial || defined $iprelay || defined $serv
 
     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
 
@@ -589,24 +889,30 @@ if (!(defined $dhcp_tftp || defined $serial || defined $iprelay || defined $serv
        foreach (@$modules) {s/,/+/g;}
        generate_configs("", $generated, $filename);
 
-       my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);
-       $kcmd = '' if !defined $kcmd;
-       my $dtb;
-       @$modules = map { if (/\.dtb$/) { $dtb=$_; (); } else { $_ } } @$modules;
-       my $initrd = join ",", @$modules;
+       if (scalar @$modules) {
+           my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);
+           $kcmd = '' if !defined $kcmd;
+           my $dtb;
+           @$modules = map { if (/\.dtb$/) { $dtb=$_; (); } else { $_ } } @$modules;
+           my $initrd = join ",", @$modules;
 
-       push(@qemu_flags, ('-kernel', $kbin, '-append', $kcmd));
-       push(@qemu_flags, ('-initrd', $initrd)) if $initrd;
-       push(@qemu_flags, ('-dtb', $dtb)) if $dtb;
+           push(@qemu_flags, ('-kernel', $kbin, '-append', $kcmd));
+           push(@qemu_flags, ('-initrd', $initrd)) if $initrd;
+           push(@qemu_flags, ('-dtb', $dtb)) if $dtb;
+       }
     }
     push(@qemu_flags,  qw(-serial stdio)); # Redirect serial output (for collecting test restuls)
-    exec_verbose(($qemu,  '-name', $config_name, @qemu_flags));
+    unshift(@qemu_flags, ('-name', $config_name));
+    print "novaboot: Running: ".shell_cmd_string($qemu, @qemu_flags)."\n";
+    $exp = Expect->spawn(($qemu, @qemu_flags)) || die("exec() failed: $!");
 }
 
 ### Local DHCPD and TFTPD
 
 my ($dhcpd_pid, $tftpd_pid);
 
+$tftp=1 if $tftp_port;
+
 if (defined $dhcp_tftp)
 {
     generate_configs("(nd)", $generated, $filename);
@@ -629,51 +935,143 @@ host server {
            sudo ip l set dev eth0 up;
            sudo touch dhcpd.leases");
 
+    # We run servers by forking ourselves, because the servers end up
+    # in our process group and get killed by signals sent to the
+    # process group (e.g. Ctrl-C on terminal).
     $dhcpd_pid = fork();
-    if ($dhcpd_pid == 0) {
-       # This way, the spawned server are killed when this script is killed.
-       exec_verbose("sudo dhcpd -d -cf dhcpd.conf -lf dhcpd.leases -pf dhcpd.pid");
-    }
-    $tftpd_pid = fork();
-    if ($tftpd_pid == 0) {
-       exec_verbose("sudo in.tftpd --foreground --secure -v -v -v $CFG::builddir");
+    exec_verbose("sudo dhcpd -d -cf dhcpd.conf -lf dhcpd.leases -pf dhcpd.pid") if ($dhcpd_pid == 0);
+}
+
+if (defined $dhcp_tftp || defined $tftp) {
+    $tftp_port ||= 69;
+    # 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  --address :$tftp_port $builddir");
+
+    # Kill server when we die
+    $SIG{__DIE__} = sub { system_verbose('sudo pkill --pidfile=dhcpd.pid') if (defined $dhcp_tftp);
+                         system_verbose('sudo pkill --pidfile=tftpd.pid'); };
+
+    # We have to kill tftpd explicitely, because it is not in our process group
+    $SIG{INT} = sub { system_verbose('sudo pkill --pidfile=tftpd.pid'); exit(0); };
+}
+
+### AMT IDE-R
+if (defined $ider) {
+    my $ider_cmd= "amtider -c $iso_image -u $amt_user -p $amt_password $amt_host $amt_port"  ;
+    print "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";
     }
-    $SIG{TERM} = sub { print "CHILDS KILLED\n"; kill 15, $dhcpd_pid, $tftpd_pid; };
+    # FIXME: This collides with --tftp option. Hopefully, nobody needs
+    # to use both simultaneously.
+    $SIG{__DIE__} = sub { system_verbose('kill $ider_pid'); };
 }
 
-### Serial line or IP relay
+### Reset target (IP relay, AMT, ...)
 
-if ($serial || defined $iprelay) {
-    my $CONN;
-    if (defined $iprelay) {
-       print "novaboot: Reseting the test box... ";
-       relay(2, 1, 1); # Reset the machine
-       usleep(100000);
-       relay(2, 0);
-       print "done\n";
+if (defined $target_reset) {
+    print "novaboot: Reseting the test box... ";
+    &$target_reset();
+    print "done\n";
+}
+
+### U-boot conversation
+if (defined $uboot) {
+    my $uboot_prompt = $uboot || '=> ';
+    print "novaboot: Waiting for uBoot prompt...\n";
+    $exp->log_stdout(1);
+    #$exp->exp_internal(1);
+    $exp->expect(20,
+                [qr/Hit any key to stop autoboot:/, sub { $exp->send("\n"); exp_continue; }],
+                $uboot_prompt) || die "No uBoot prompt deteceted";
+    foreach my $cmd (@uboot_init) {
+       if ($cmd =~ /\$NB_MYIP/) {
+           my $ip = (grep /inet /, `ip addr show eth0`)[0] || die "Problem determining our IP address";
+           $ip =~ s/\s*inet ([0-9.]*).*/$1/;
+           $cmd =~ s/\$NB_MYIP/$ip/g;
+       }
+       chomp($cmd);
+       $exp->send("$cmd\n");
+       $exp->expect(10, $uboot_prompt) || die "uBoot prompt timeout";
+    }
 
-       $CONN = $IPRELAY;
-    } elsif ($serial) {
-       system("stty -F $serial raw -crtscts -onlcr 115200");
-       open($CONN, "+<", $serial) || die "open $serial: $!";
-       $CONN->autoflush(1);
+    # Boot the system if there are some load lines in the script
+    if (scalar(@$modules) > 0) {
+       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; }],
+                    $uboot_prompt) || die "No U-Boot prompt deteceted";
+       if (defined $dtb) {
+           $dtb_addr = '7f0000';
+           $exp->send("tftp $dtb_addr $dtb\n");
+           $exp->expect(10,
+                        [qr/#/, sub { exp_continue; }],
+                        $uboot_prompt) || die "Device tree load failed";
+       }
+       if (defined $initrd) {
+           $initrd_addr = 'b00000';
+           $exp->send("tftp $initrd_addr $initrd\n");
+           $exp->expect(10,
+                        [qr/#/, sub { exp_continue; }],
+                        $uboot_prompt) || die "Initrd load failed";
+       }
+       $exp->send("set bootargs '$kcmd'\n");
+       $exp->expect(5, $uboot_prompt)  || die "uBoot prompt timeout";
+       $exp->send("bootm $kern_addr $initrd_addr $dtb_addr\n");
+       $exp->expect(5, "\n")  || die "uBoot command timeout";
     }
+}
 
-    # Pass the NOVA output to stdout.
-    while (<$CONN>) {
-       print;
+### Serial line interaction
+if (defined $exp) {
+    # Serial line of the target is available
+    my $interrupt = 'Ctrl-C';
+    if ($interactive && !@exiton) {
+       $interrupt = '"~~."';
+    }
+    my $note = (-t STDIN) ? '' : '- only target->host ';
+    print "novaboot: Serial line interaction $note(press $interrupt to interrupt)...\n";
+    $exp->log_stdout(1);
+    if (@exiton) {
+       $exp->expect(undef, @expect_raw, @exiton);
+    } else {
+       my @inputs = ($exp);
+       if (-t STDIN) { # Set up bi-directional communication if we run on terminal
+           my $infile = new IO::File;
+           $infile->IO::File::fdopen(*STDIN,'r');
+           my $in_object = Expect->exp_init($infile);
+           $in_object->set_group($exp);
+
+           if ($interactive) {
+               $in_object->set_seq('~~\.', sub { print "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;
+       Expect::interconnect(@inputs) unless defined($exp->exitstatus);
     }
-    kill 15, $dhcpd_pid, $tftpd_pid if ($dhcp_tftp);
-    exit;
 }
 
-### Wait for dhcpc or tftpd
-if (defined $dhcp_tftp) {
-    my $pid = wait();
-    if ($pid == $dhcpd_pid) { print "dhcpd exited!\n"; }
-    elsif ($pid == $tftpd_pid) { print "tftpd exited!\n"; }
-    else { print "wait returned: $pid\n"; }
-    kill(15, 0); # Kill current process group i.e. all remaining children
+## Kill dhcpc or tftpd
+if (defined $dhcp_tftp || defined $tftp) {
+    die("novaboot: This should kill servers on background\n");
 }
 
 ## Documentation
@@ -684,29 +1082,33 @@ novaboot - A tool for booting various operating systems on various hardware or i
 
 =head1 SYNOPSIS
 
-B<novaboot> [ options ] [--] script...
+B<novaboot> --help
 
-B<./script> [ options ]
+B<novaboot> [option]... [--] script...
 
-B<novaboot> --help
+B<./script> [option]...
 
 =head1 DESCRIPTION
 
-This program makes it easier to boot NOVA or other operating system
-(OS) in different environments. It reads a so called novaboot script
-and uses it either to boot the OS in an emulator (e.g. in qemu) or to
-generate the configuration for a specific bootloader and optionally to
-copy the necessary binaries and other needed files to proper
-locations, perhaps on a remote server. In case the system is actually
-booted, its serial output is redirected to standard output if that is
-possible.
-
-A typical way of using novaboot is to make the novaboot script
+This program makes booting of an operating system (e.g. NOVA or Linux)
+as simple as running a local program. It facilitates booting on local
+or remote hosts or in emulators such as qemu. Novaboot operation is
+controlled by 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">). Based on this input,
+novaboot setups everything for the target host to boot the desired
+configuration, i.e. it generates the bootloader configuration file in
+the proper format, deploy the binaries and other needed files to
+required locations, perhaps on a remote boot server and reset the
+target host. Finally, target host's serial output is redirected to
+standard output if that is possible.
+
+Typical way of using novaboot is to make the novaboot script
 executable and set its first line to I<#!/usr/bin/env novaboot>. Then,
 booting a particular OS configuration becomes the same as executing a
 local program - the novaboot script.
 
-With C<novaboot> you can:
+For example, with C<novaboot> you can:
 
 =over 3
 
@@ -715,24 +1117,24 @@ With C<novaboot> you can:
 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>.
+configuration specified in the F<script>.
 
 =item 2.
 
 Create a bootloader configuration file (currently supported
-bootloaders are GRUB, GRUB2 and Pulsar) and copy it with all other
-files needed for booting to another, perhaps remote, location.
+bootloaders are GRUB, GRUB2, Pulsar and U-Boot) and copy it with all
+other files needed for booting to a remote boot server.
 
- ./script --server --iprelay=192.168.1.2
+ ./script --server=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.
+This command copies files to the TFTP server and uses
+TCP/IP-controlled relay to reset the target host and receive its
+serial output.
 
 =item 3.
 
-Run DHCP and TFTP server on developer's machine to PXE-boot NOVA from
-it. E.g.
+Run DHCP and TFTP server on developer's machine to PXE-boot the target
+host from it. E.g.
 
  ./script --dhcp-tftp
 
@@ -751,31 +1153,49 @@ configurations.
 
 =back
 
+Note that the options needed for a specific target can be stored in a
+L</"CONFIGURATION FILE">. Then it is sufficient to use only the B<-t>
+option to specify the name of the target.
+
 =head1 PHASES AND OPTIONS
 
-Novaboot perform its work in several phases. Each phase can be
+Novaboot performs its work in several phases. Each phase can be
 influenced by several options, certain phases can be skipped. The list
-of phases with the corresponding options follows.
+of phases (in the execution order) and the corresponding options
+follow.
 
 =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 configuration files. Their content is
+described in L</"CONFIGURATION FILE">. By default, configuration is
+read from two locations. First from the configuration directory and
+second from F<.novaboot> files along the path to the current
+directory. The later read files override settings from the former
+ones.
+
+Configuration directory is determined by the content of
+NOVABOOT_CONFIG_DIR environment variable defaulting to
+F</etc/novaboot.d>. Files in this directory with names consisting
+solely from English letters, numbers, dashes '-' and underscores '_'
+(note that dot '.' is not included) are read in alphabetical order.
+
+Then 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 order from the root directory
+downwards.
 
 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.
+for configuration file search instead of the novaboot script
+directory.
 
 =over 8
 
-=item -c, --config=<filename>
+=item -c, --config=I<filename>
 
 Use the specified configuration file instead of the default one(s).
 
@@ -794,7 +1214,7 @@ initial template for a configuration file.
 
 Print short (B<-h>) or long (B<--help>) help.
 
-=item -t, --target=<target>
+=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
@@ -810,11 +1230,11 @@ used in the later phases.
 
 =over 8
 
-=item -a, --append=<parameters>
+=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
+can be used to append parameters to the kernel's or root task's
+command line. Can appear multiple times.
 
 =item -b, --bender
 
@@ -822,16 +1242,21 @@ 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.
 
-=item --chainloader=<chainloader>
+=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'.
 
 =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>
 
@@ -845,36 +1270,29 @@ 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>.
+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, binaries can be generated in this phases by
+running C<scons> or C<make>.
 
 =over 8
 
-=item --build-dir=<directory>
+=item --build-dir=I<directory>
 
 Overrides the default build directory location.
 
-The default build directory location is determined as follows:
+The default build directory location is determined as follows: If the
+configuration file defines the C<$builddir> variable, its value is
+used. Otherwise, it is the directory that contains the first processed
+novaboot script.
 
-If there is a configuration file, the value specified in the
-I<$builddir> variable is used. Otherwise, if the current working
-directory is inside git work tree and there is F<build> directory at
-the top of that tree, it is used. Otherwise, if directory
-F<~/nul/build> exists, it is used. Otherwise, it is the directory that
-contains the first processed novaboot script.
+See also L</BUILDDIR> variable.
 
 =item -g, --grub[=I<filename>]
 
@@ -914,9 +1332,13 @@ 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
+Specifies text I<preable> that is put at the beginning of the entry
 GRUB2 entry.
 
+=item -m, --make[=make command]
+
+Runs C<make> to build files that are not generated by novaboot itself.
+
 =item --name=I<string>
 
 Use the name I<string> instead of the name of the novaboot script.
@@ -925,8 +1347,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 generation of files
+specified with "<<WORD" syntax.
 
 =item -p, --pulsar[=mac]
 
@@ -936,9 +1359,14 @@ I<novaboot>.
 
 =item --scons[=scons command]
 
-Runs I<scons> to build files that are not generated by 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.
@@ -951,6 +1379,60 @@ 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.
 
+=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
+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. Default I<user> is admin.
+
+=item --ider
+
+Use Intel AMT technology for IDE redirection. This allows the target
+machine to boot from nonvaboot created ISO image.
+
+The experimental I<amtider> utility needed by this option can be
+obtained from https://github.com/wentasah/amtterm.
+
+=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.
+
+Note: This option is supposed to work with HWG-ER02a IP relays.
+
+=item -s, --serial[=device]
+
+Target's serial line is connected to host's serial line (device). The
+default value for device is F</dev/ttyUSB0>.
+
+The value of this option is exported in NB_NOVABOOT environment
+variable to all subprocesses run by C<novaboot>.
+
+=item --stty=I<settings>
+
+Specifies settings passed to C<stty> invoked on the serial line
+specified with B<--serial> option. If this option is not given,
+C<stty> is called with C<raw -crtscts -onlcr 115200> settings.
+
+=item --remote-cmd=I<cmd>
+
+Command that mediates connection to the target's serial line. For
+example C<ssh server 'cu -l /dev/ttyS0'>.
+
+=item --remote-expect=I<string>
+
+Wait for reception of I<string> after establishing the the remote
+connection before continuing.
+
+
+=back
+
 =head2 File deployment phase
 
 In some setups, it is necessary to copy the files needed for booting
@@ -961,19 +1443,35 @@ F</boot> partition.
 
 =item -d, --dhcp-tftp
 
-Turns your workstation into a DHCP and TFTP server so that NOVA
-can be booted via PXE BIOS on a test machine directly connected by
-a plain Ethernet cable to your workstation.
+Turns your workstation into a DHCP and TFTP server so that the OS can
+be booted via PXE BIOS (or similar mechanism) on the test machine
+directly connected by a plain Ethernet cable to your workstation.
 
-The DHCP and TFTP servers require root privileges and C<novaboot>
+The DHCP and TFTP servers 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 password.
+I</etc/sudoers> to allow running the necessary commands without asking
+for 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 *, /usr/bin/touch dhcpd.leases
+ 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 -i, --iso[=filename]
+=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 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.
+
+ 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 --tftp-port=I<port>
+
+Port to run the TFTP server on. Implies B<--tftp>.
+
+=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
@@ -1007,21 +1505,16 @@ copying files as a result of I<--server> option.
 
 =over 8
 
-=item --iprelay=I<addr[:port]>
-
-Use IP relay to reset the machine and to get the serial output. The IP
-address of the relay is given by I<addr> parameter.
-
-Note: This option is expected to work with HWG-ER02a IP relays.
-
 =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 B<--iprelay> or
+B<--amt>.
 
-=item -Q, --qemu=I<qemu-binary>
+=item -Q, --qemu[=I<qemu-binary>]
 
-The name of qemu binary to use. The default is 'qemu'.
+Boot the configuration in qemu. Optionally, the name of qemu binary
+can be specified as a parameter.
 
 =item --qemu-append=I<flags>
 
@@ -1033,71 +1526,154 @@ C<-cpu coreduo -smp 2>).
 Replace the default qemu flags (QEMU_FLAGS variable or C<-cpu coreduo
 -smp 2>) with I<flags> specified here.
 
+=item --reset-cmd=I<cmd>
+
+Command that resets the target.
+
 =back
 
 =head2 Interaction with the bootloader on the target
 
-See B<--serial>. There will be new options soon.
+=over 8
 
-=head2 Target's output reception phase
+=item --uboot[=I<prompt>]
 
-=over 8
+Interact with uBoot 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# ").
+Implementation of this option is currently tied to a particular board
+that we use. It may be subject to changes in the future!
 
-=item -s, --serial[=device]
+=item --uboot-init
+
+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>.
 
-Use serial line to control GRUB bootloader and to see the output
-serial output of the machine. The default value is F</dev/ttyUSB0>.
+If the command contains string I<$NB_MYIP> then this string is
+replaced by IPv4 address of eth0 interface.
 
 =back
 
-See also B<--iprelay>.
+=head2 Target interaction phase
+
+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 I<string> is sent by the target, novaboot exits. This option can
+be specified multiple times.
+
+If I<string> is C<-re>, then the next B<--exiton>'s I<string> is
+treated as regular expression. For example:
+
+    --exiton -re --exiton 'error:.*failed'
+
+=item -i, --interactive
+
+Setup things for interactive use of target. Your terminal will be
+switched to raw mode. In raw mode, your system does not process input
+in any way (no echoing of entered characters, no interpretation
+special characters). This, among others, means that Ctrl-C is passed
+to the target and does no longer interrupt novaboot. Use "~~."
+sequence to exit novaboot.
+
+=item --expect=I<string>
+
+When 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>
 
-=head2 Termination phase
+When target's output matches regular expression I<regex>, send the
+string specified with the subsequent B<--send*> option to the target.
 
-Daemons that were spwned (F<dhcpd> and F<tftpd>) are killed here.
+=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'>
+
+=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
+with 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.
+Lines starting with C<load> keyword 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 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 shell's heredoc feature.
+
+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.
+
+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.
+
+Example (Linux):
 
-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.
+  #!/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"
 
-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.
+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>.
 
 =head2 VARIABLES
 
@@ -1105,70 +1681,87 @@ The following variables are interpreted in the novaboot script:
 
 =over 8
 
-=item WVDESC
+=item BUILDDIR
 
-Description of the wvtest-compliant program.
+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 WVTEST_TIMEOUT
+=item EXITON
 
-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.
+Assigning this variable has the same effect as specifying L</--exiton>
+option.
+
+=item HYPERVISOR_PARAMS
+
+Parameters passed to hypervisor. The default value is "serial", unless
+overridden in 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 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 WVDESC
 
-Parameters passed to hypervisor. The default value is "serial", unless
-overriden in configuration file.
+Description of the wvtest-compliant program.
 
-=item KERNEL
+=item WVTEST_TIMEOUT
 
-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.
+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
 
-Novaboot can read its configuration from a file. Configuration file
-was necessary in early days of novaboot. Nowadays, an attempt is made
-to not use the configuration file because it makes certain novaboot
-scripts unusable on systems without (or with different) configuration
-file. The only recommended use of the configuration file is to specify
-custom_options (see bellow).
-
-If you decide to use the configuration file, it is looked up, by
-default, in 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.
+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, configuration file
+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 overridden by environment variables
+(see below) or by command line switches.
 
-Documentation of some configuration variables follows:
+Supported configuration variables include:
 
 =over 8
 
+=item $builddir
+
+Build directory location relative to the location of the configuration
+file.
+
+=item $default_target
+
+Default target (see below) to use when no target is explicitly
+specified on command line with the B<--target> option.
+
 =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'} = '--server=boot:/tftproot --serial=/dev/ttyUSB0 --grub',
 
 then the following two commands are equivalent:
 
@@ -1191,6 +1784,11 @@ 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_BENDER
 
 Defining this variable has the same meaning as B<--bender> option.