]> rtime.felk.cvut.cz Git - novaboot.git/blobdiff - novaboot
Add an option to disable reseting of the target
[novaboot.git] / novaboot
index b66863e9e47c8b385a11790f4ed183c7de240c86..ec60ffe0fe2c9e2b8ee3e160e1eac56465a00dd3 100755 (executable)
--- a/novaboot
+++ b/novaboot
@@ -16,7 +16,7 @@
 use strict;
 use warnings;
 use warnings (exists $ENV{NOVABOOT_TEST} ? (FATAL => 'all') : ());
-use Getopt::Long qw(GetOptionsFromString);
+use Getopt::Long qw(GetOptionsFromString GetOptionsFromArray);
 use Pod::Usage;
 use File::Basename;
 use File::Spec;
@@ -28,8 +28,6 @@ use IPC::Open2;
 use POSIX qw(:errno_h);
 use Cwd qw(getcwd abs_path);
 use Expect;
-use LWP::UserAgent;
-use LWP::Authen::Digest;  
 
 # always flush
 $| = 1;
@@ -42,16 +40,16 @@ my $invocation_dir = $ENV{PWD} || getcwd();
 $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 -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}; sleep 1" --reset-cmd="if which dtrrts; then dtrrts $NB_SERIAL 0 1; sleep 0.1; dtrrts $NB_SERIAL 1 1; fi"',
-
+    "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 --server=pc-sojkam.felk.cvut.cz:/srv/tftp --remote-cmd="ssh -tt pc-sojkam.felk.cvut.cz \"sterm -d -s 115200 /dev/ttyUSB0\""',
+    "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";
@@ -90,22 +88,31 @@ my @cfgs;
        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 $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, $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, $uboot, $uboot_init);
+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, $reset_cmd, $rom_prefix, $rsync_flags, @scriptmod, $scons, $serial, $server, $stty, $tftp, $tftp_port, $uboot, %uboot_addr, @uboot_init);
 
 $rsync_flags = '';
 $rom_prefix = 'rom://';
 $stty = 'raw -crtscts -onlcr 115200';
+$reset = 1;                    # Reset target by default
 
 my @expect_seen = ();
 sub handle_expect
@@ -125,7 +132,6 @@ sub handle_send
     @expect_seen = ();
 }
 
-Getopt::Long::Configure(qw/no_ignore_case no_pass_through/);
 my %opt_spec;
 %opt_spec = (
     "amt=s"         => \$amt,
@@ -144,9 +150,10 @@ my %opt_spec;
     "gen-only"      => \$gen_only,
     "grub|g:s"              => \$grub_config,
     "grub-preamble=s"=> \$grub_preamble,
-    "grub-prefix=s"  => \$grub_prefix,
+    "prefix|grub-prefix=s" => \$grub_prefix,
     "grub2:s"       => \$grub2_config,
     "grub2-prolog=s" => \$grub2_prolog,
+    "ider"           => \$ider,
     "iprelay=s"             => \$iprelay,
     "iso:s"         => \$iso_image,
     "kernel|k=s"     => \$kernel_opt,
@@ -163,6 +170,7 @@ my %opt_spec;
     "qemu-flags|q=s" => \$qemu_flags_cmd,
     "remote-cmd=s"   => \$remote_cmd,
     "remote-expect=s"=> \$remote_expect,
+    "reset!"         => \$reset,
     "reset-cmd=s"    => \$reset_cmd,
     "rsync-flags=s"  => \$rsync_flags,
     "scons:s"       => \$scons,
@@ -173,8 +181,11 @@ my %opt_spec;
     "server:s"              => \$server,
     "strip-rom"             => sub { $rom_prefix = ''; },
     "stty=s"        => \$stty,
-    "uboot"         => \$uboot,
-    "uboot-init=s"   => \$uboot_init,
+    "tftp"          => \$tftp,
+    "tftp-port=i"    => \$tftp_port,
+    "uboot:s"       => \$uboot,
+    "uboot-addr=s"   => \%uboot_addr,
+    "uboot-init=s"   => \@uboot_init,
     "h"             => \$help,
     "help"          => \$man,
     );
@@ -182,10 +193,19 @@ my %opt_spec;
 # First process target options
 {
     my $t = defined($explicit_target) ? $explicit_target : $CFG::default_target;
-    if ($t) {
+    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)).")");
-       GetOptionsFromString($CFG::targets{$t}, %opt_spec);
+
+       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
@@ -217,6 +237,11 @@ if ($interactive && !-t STDIN) {
 
 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";
@@ -230,8 +255,9 @@ my @scripts;
 my $file;
 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 $continuation;
@@ -289,6 +315,10 @@ while (<>) {
        push @$modules, $_;
        next;
     }
+    if (/^run (.*)/) {         # run line
+       push @$generated, {command => $1};
+       next;
+    }
 
     die("novaboot: Cannot parse script '$last_fn' line $.. Didn't you forget 'load' keyword?\n");
 }
@@ -340,7 +370,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});
+       }
       }
     }
 }
@@ -370,6 +404,39 @@ 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('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;
+}
+
 sub generate_grub2_config($$$$;$$)
 {
     my ($filename, $title, $base, $modules_ref, $preamble, $prolog) = @_;
@@ -434,6 +501,7 @@ sub exec_verbose(@)
 {
     print "novaboot: Running: ".shell_cmd_string(@_)."\n";
     exec(@_);
+    exit(1); # should not be reached
 }
 
 sub system_verbose($)
@@ -446,7 +514,7 @@ sub system_verbose($)
     if ($ret) { die("Command failure $ret"); }
 }
 
-## WvTest handline
+## WvTest headline
 
 if (exists $variables->{WVDESC}) {
     print "Testing \"$variables->{WVDESC}\" in $last_fn:\n";
@@ -454,7 +522,7 @@ if (exists $variables->{WVDESC}) {
     print "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
@@ -465,6 +533,7 @@ 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);
+my ($amt_user, $amt_password, $amt_host, $amt_port);
 
 if (defined $iprelay) {
     my $IPRELAY;
@@ -543,21 +612,25 @@ elsif ($serial) {
     system_verbose("stty -F $serial $stty");
     open($CONN, "+<", $serial) || die "open $serial: $!";
     $exp = Expect->init(\*$CONN);
-} elsif ($remote_cmd) {
+}
+elsif ($remote_cmd) {
     print "novaboot: Running: $remote_cmd\n";
     $exp = Expect->spawn($remote_cmd);
-} elsif (defined $amt){    
-    sub genXML { #HOST, username, password, schema, className, pstate
+}
+elsif (defined $amt) {
+    require LWP::UserAgent;
+    require LWP::Authen::Digest;
+
+    sub genXML {
         my ($host, $username, $password, $schema, $className, $pstate) = @_;
-        #AMT numbers for PowerStateChange: 2 on, 4 standby, 7 hibernate, 8 off, 
-        #10 reset, 11 MNI interupt(on windows->bluescreen;-)) 
-        my %pstates = ("on", 2,
-                    "standby", 4,
-                    "hibernate", 7,
-                    "off", 8,
-                    "reset", 10,
-                    "MNI", 11);
-        my $text = <<END;
+       #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>
@@ -578,70 +651,74 @@ elsif ($serial) {
                </p:RequestPowerStateChange_INPUT>
                </s:Body></s:Envelope>
 END
-        return $text;
     }
-  
-    sub sendPOST{ #ip, username, password, content
-        my ($host, $username, $password, $content)=@_;+
-        my $req = HTTP::Request->new(POST => "http://$host:16992/wsman");
+
+    sub sendPOST {
+        my ($host, $username, $password, $content) = @_;
+
         my $ua = LWP::UserAgent->new();
-        my $res = $ua->request($req);
-        die ($res->status_line) unless $res->code==401;
-        my $header = $res->header("WWW-Authenticate");
-        my $digRealm = "Digest realm=\"";
-        my $posRealm = index($header,$digRealm)+length($digRealm);
-        my $realm = substr($header,$posRealm,index($header,"\"",$posRealm)-$posRealm);
-        
-        $ua = LWP::UserAgent->new();
         $ua->agent("novaboot");
-        $ua->credentials("$host:16992",$realm,  $username => $password); 
+
+       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 ($res->status_line) unless $res->is_success;
+        die ("AMT power change request failed: " . $res->status_line) unless $res->is_success;
         $res->content() =~ /<g:ReturnValue>(\d+)<\/g:ReturnValue>/;
         return $1;
     }
 
-    sub powerChange  {#IP, username, password, pstate
+    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);
-    }  
-
-       my (@amt_info, $len, $amt_password, $result, $host, $port, $user);
-       ($user,$amt_password,$host,$port) = ($amt =~ /(?:(.*?)(?::(.*))?@)?([^:]*)(?::([0-9]*))?/);;
-       $len=@amt_info;
-       die "AMT host not specified" unless $host;
-       $user ||= "admin";
-       $port ||= 16994;
+    }
+
+    ($amt_user,$amt_password,$amt_host,$amt_port) = ($amt =~ /(?:(.*?)(?::(.*))?@)?([^:]*)(?::([0-9]*))?/);;
+    $amt_user ||= "admin";
     $amt_password ||= $ENV{'AMT_PASSWORD'} || die "AMT password not specified";
-       $target_power_off = sub {
-               $result = powerChange($host,$user,$amt_password, "off");
-               die "Cannot turn off the computer, somebody is connected or computer is already off" unless $result==0;
-       };
-       
-       $target_power_on = sub {
-               $result = powerChange($host,$user,$amt_password, "on");
-       };
-       
-       $target_reset = sub {
-               $result = powerChange($host,$user,$amt_password, "reset");
-               if ($result != 0) {
-                   print STDERR "Warning: Cannot reset $host, trying power on. ";
-                   $result = powerChange($host,$user,$amt_password, "on");
-               }
-               die "AMT reset failed (code $result)" if $result;
-               sleep(2); # Without pause, connection to AMT machine is initiated before reset and fails after restart
-               $exp = Expect->spawn("amtterm -u $user -p $amt_password $host $port"); 
-               #expect must be here because AMT doesnt allow to reset/on/off computer when somebody is connected
-       };      
+    $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";
 }
@@ -666,11 +743,14 @@ $builddir ||= dirname(File::Spec->rel2abs( ${$scripts[0]}{filename})) if scalar
 if (defined $builddir) {
     chdir($builddir) or die "Can't change directory to $builddir: $!";
     print "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};
@@ -687,9 +767,11 @@ foreach my $script (@scripts) {
        print "novaboot: Entering directory `$builddir'\n";
     }
 
-    my $prefix;
-    ($prefix = $grub_prefix) =~ s/\$NAME/$config_name/ if defined $grub_prefix;
-    $prefix ||= $builddir;
+    if ($grub_prefix) {
+       $prefix = $grub_prefix;
+       $prefix =~ s/\$NAME/$config_name/;
+       $prefix =~ s/\$BUILDDIR/$builddir/;
+    }
     # TODO: use $grub_prefix as first parameter if some switch is given
     generate_configs('', $generated, $filename);
 
@@ -703,7 +785,7 @@ foreach my $script (@scripts) {
     {
        my @files = map({ ($file) = m/([^ ]*)/; $file; } @$modules);
        # 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);
@@ -733,7 +815,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;
     }
@@ -741,12 +823,35 @@ 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");
+
+    # 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");
 }
 
@@ -768,7 +873,7 @@ sub trim($) {
     return $str
 }
 
-### Qemu
+### Start in Qemu
 
 if (defined $qemu) {
     # Qemu
@@ -782,7 +887,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
 
@@ -812,6 +917,8 @@ if (defined $qemu) {
 
 my ($dhcpd_pid, $tftpd_pid);
 
+$tftp=1 if $tftp_port;
+
 if (defined $dhcp_tftp)
 {
     generate_configs("(nd)", $generated, $filename);
@@ -839,72 +946,117 @@ 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;
+    # 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');
+    $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";
+    }
+    # 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 (defined $target_reset) {
+if (defined $target_reset && $reset) {
     print "novaboot: Reseting the test box... ";
     &$target_reset();
     print "done\n";
 }
 
+### U-boot conversation
 if (defined $uboot) {
-    print "novaboot: Waiting for uBoot prompt...\n";
+    my $uboot_prompt = $uboot || '=> ';
+    print "novaboot: Waiting for U-Boot 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; }],
-                '=> ') || 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";
+                $uboot_prompt) || die "No U-Boot 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 "U-Boot prompt timeout";
     }
-    if (defined $initrd) {
-       $initrd_addr = 'b00000';
-       $exp->send("tftp $initrd_addr $initrd\n");
+
+    # 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 ($ramdisk_addr, $fdt_addr) = ('-', '');
+
+       die "No '--uboot-addr kernel' given" unless $uboot_addr{kernel};
+       $exp->send("tftpboot $uboot_addr{kernel} $prefix$kbin\n");
        $exp->expect(10,
-                    [qr/#/, sub { exp_continue; }],
-                    '=> ') || die "Initrd load failed";
+                    [qr/##/, sub { exp_continue; }],
+                    $uboot_prompt) || die "Kernel load timeout";
+       if (defined $dtb) {
+           die "No '--uboot-addr fdt' given" unless $uboot_addr{fdt};
+           $fdt_addr = $uboot_addr{fdt};
+           $exp->send("tftpboot $fdt_addr $prefix$dtb\n");
+           $exp->expect(10,
+                        [qr/##/, sub { exp_continue; }],
+                        $uboot_prompt) || die "Device tree load timeout";
+       }
+       if (defined $initrd) {
+           die "No '--uboot-addr ramdisk' given" unless $uboot_addr{ramdisk};
+           $ramdisk_addr = $uboot_addr{ramdisk};
+           $exp->send("tftpboot $ramdisk_addr $prefix$initrd\n");
+           $exp->expect(10,
+                        [qr/#/, sub { exp_continue; }],
+                        $uboot_prompt) || die "Initrd load timeout";
+       }
+       $exp->send("echo $kcmd\n");
+       $exp->expect(1, '-re', qr{echo .*\n(.*)\n$uboot_prompt})  || die "Command line test timeout";
+       my $args = ($exp->matchlist)[0];
+       if ($args =~ /^setenv\s+bootargs/) {
+           $exp->send("$args\n");
+       } else {
+           $exp->send("setenv bootargs $kcmd\n");
+       }
+       $exp->expect(5, $uboot_prompt)  || die "U-Boot prompt timeout";
+       $exp->send("bootm $uboot_addr{kernel} $ramdisk_addr $fdt_addr\n");
+       $exp->expect(5, "\n")  || die "U-Boot command timeout";
     }
-    $exp->send("set bootargs '$kcmd'\n");
-    $exp->expect(5, '=> ')  || die "uBoot prompt timeout";
-    $exp->send("bootm $kern_addr $initrd_addr $dtb_addr\n");
-    $exp->expect(5, "\n")  || die "uBoot command timeout";
 }
 
+### Serial line interaction
 if (defined $exp) {
     # Serial line of the target is available
     my $interrupt = 'Ctrl-C';
     if ($interactive && !@exiton) {
        $interrupt = '"~~."';
     }
-    print "novaboot: Serial line interaction (press $interrupt to interrupt)...\n";
+    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);
@@ -932,7 +1084,7 @@ if (defined $exp) {
 }
 
 ## Kill dhcpc or tftpd
-if (defined $dhcp_tftp) {
+if (defined $dhcp_tftp || defined $tftp) {
     die("novaboot: This should kill servers on background\n");
 }
 
@@ -984,8 +1136,8 @@ configuration specified in the F<script>.
 =item 2.
 
 Create a bootloader configuration file (currently supported
-bootloaders are GRUB, GRUB2, Pulsar and U-Boot) and copy it with all
-other files needed for booting to a remote boot server.
+bootloaders are GRUB, GRUB2, ISOLINUX, Pulsar and U-Boot) and copy it
+with all other files needed for booting to a remote boot server.
 
  ./script --server=192.168.1.1:/tftp --iprelay=192.168.1.2
 
@@ -996,12 +1148,12 @@ serial output.
 =item 3.
 
 Run DHCP and TFTP server on developer's machine to PXE-boot the target
-host from it. E.g.
+machine from it. E.g.
 
  ./script --dhcp-tftp
 
 When a PXE-bootable machine is connected via Ethernet to developer's
-machine, it will boot the configuration described in I<script>.
+machine, it will boot the configuration described in the I<script>.
 
 =item 4.
 
@@ -1009,7 +1161,7 @@ Create bootable ISO images. E.g.
 
  novaboot --iso -- script1 script2
 
-The created ISO image will have GRUB bootloader installed on it and
+The created ISO image will use ISOLINUX bootloader installed on it and
 the boot menu will allow selecting between I<script1> and I<script2>
 configurations.
 
@@ -1022,25 +1174,39 @@ option to specify the name of the target.
 =head1 PHASES AND OPTIONS
 
 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
-follow.
+influenced by several command line options, certain phases can be
+skipped. The list 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 section 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 the opposite order (i.e. from the
+root directory downwards). This allows to have, for example, user
+specific configuration in F<~/.novaboot> and project specific one in
+F<~/project/.novaboot>.
 
 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.
+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
 
@@ -1083,7 +1249,7 @@ used in the later phases.
 
 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.
+command line. This option can appear multiple times.
 
 =item -b, --bender
 
@@ -1093,8 +1259,9 @@ 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
 
@@ -1155,22 +1322,30 @@ Specifies the I<preable> 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
+file name 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<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
+In order to use the 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.
@@ -1181,8 +1356,7 @@ i.e. run update-grub on Debian/Ubuntu.
 
 =item --grub2-prolog=I<prolog>
 
-Specifies text I<preable> that is put at the beginning of the entry
-GRUB2 entry.
+Specifies text that is put at the beginning of the GRUB2 menu entry.
 
 =item -m, --make[=make command]
 
@@ -1196,8 +1370,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]
 
@@ -1231,11 +1406,12 @@ user/instance.
 
 =item --amt=I<"[user[:password]@]host[:port]>
 
-Use Intel AMT to connect to target. Using SOL redirection and WS management 
-to powercycle it. The IP address or FQDN of the PC is given by I<host>
-parameter. If I<password> is not specified, environment variable 
-AMT_PASSWORD is used. I<port> is defining port number for SOL, 
-if not specified, default is 16992. Default I<user> is admin.
+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 --iprelay=I<addr[:port]>
 
@@ -1286,14 +1462,30 @@ 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 --pidfile tftpd.pid *, /usr/bin/touch dhcpd.leases, /usr/bin/pkill --pidfile=dhcpd.pid, /usr/bin/pkill --pidfile=tftpd.pid
+ 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 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
@@ -1302,11 +1494,15 @@ 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>).
+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 which I<flags> are appended to F<rsync> command line when
+copying files as a result of I<--server> option.
 
 =item --concat
 
@@ -1317,21 +1513,30 @@ 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
 
-Specifies which I<flags> are appended to F<rsync> command line when
-copying files as a result of I<--server> option.
+Use Intel AMT technology for IDE redirection. This allows the target
+machine to boot from novaboot created ISO image. Implies B<--iso>.
+
+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 is
+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>.
+
 =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 B<--iprelay> or
+B<--amt>.
 
 =item -Q, --qemu[=I<qemu-binary>]
 
@@ -1352,23 +1557,38 @@ Replace the default qemu flags (QEMU_FLAGS variable or C<-cpu coreduo
 
 Command that resets the target.
 
+=item --no-reset, --reset
+
+Disable/enable reseting of the target.
+
 =back
 
 =head2 Interaction with the bootloader on the target
 
 =over 8
 
-=item --uboot
+=item --uboot[=I<prompt>]
 
-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!
+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# ").
+Implementation of this option is currently tied to a particular board
+that we use. It may be subject to changes in the future!
 
 =item --uboot-init
 
 Command(s) to send the U-Boot bootloader before loading the images and
-booting them.
+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.
+
+=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).
 
 =back
 
@@ -1462,10 +1682,17 @@ 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):
 
   #!/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):
@@ -1528,7 +1755,7 @@ Use specific qemu flags (can be overridden with B<-q>).
 
 =item WVDESC
 
-Description of the wvtest-compliant program.
+Description of the WvTest-compliant program.
 
 =item WVTEST_TIMEOUT
 
@@ -1543,13 +1770,13 @@ intermediate output.
 
 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 overridden by environment variables (see below) or by
-command line switches.
+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.
 
 Supported configuration variables include:
 
@@ -1567,10 +1794,11 @@ 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:
 
@@ -1593,6 +1821,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.
@@ -1602,3 +1835,10 @@ Defining this variable has the same meaning as B<--bender> option.
 =head1 AUTHORS
 
 Michal Sojka <sojka@os.inf.tu-dresden.de>
+
+=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