]> rtime.felk.cvut.cz Git - novaboot.git/blob - novaboot
Use wvtool instead of wvtestrun for testing
[novaboot.git] / novaboot
1 #!/usr/bin/perl -w
2
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation, either version 2 of the License, or
6 # (at your option) any later version.
7
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 # GNU General Public License for more details.
12
13 # You should have received a copy of the GNU General Public License
14 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
15
16 use strict;
17 use warnings;
18 use warnings (exists $ENV{NOVABOOT_TEST} ? (FATAL => 'all') : ());
19 use Getopt::Long qw(GetOptionsFromString GetOptionsFromArray);
20 use Pod::Usage;
21 use File::Basename;
22 use File::Spec;
23 use IO::Handle;
24 use Time::HiRes("usleep");
25 use Socket;
26 use FileHandle;
27 use IPC::Open2;
28 use POSIX qw(:errno_h);
29 use Cwd qw(getcwd abs_path);
30 use Expect;
31
32 # always flush
33 $| = 1;
34
35 my $invocation_dir = $ENV{PWD} || getcwd();
36
37 ## Configuration file handling
38
39 # Default configuration
40 $CFG::hypervisor = "";
41 $CFG::hypervisor_params = "serial";
42 $CFG::genisoimage = "genisoimage";
43 $CFG::qemu = 'qemu-system-i386 -cpu coreduo -smp 2';
44 $CFG::default_target = 'qemu';
45 %CFG::targets = (
46     'qemu' => '--qemu',
47     "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',
48     "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',
49     "localhost" => '--scriptmod=s/console=tty[A-Z0-9,]+// --server=/boot/novaboot/$NAME --grub2 --grub-prefix=/boot/novaboot/$NAME --grub2-prolog="  set root=\'(hd0,msdos1)\'"',
50     "ryu" =>  '--uboot --uboot-init="mw f0000b00 \${psc_cfg}; sleep 1" --uboot-addr kernel=800000 --uboot-addr ramdisk=b00000 --uboot-addr fdt=7f0000',
51     "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\""',
52     "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"',
53     );
54 chomp(my $nproc = `nproc`);
55 $CFG::scons = "scons -j$nproc";
56 $CFG::make = "make -j$nproc";
57
58 my $builddir;
59
60 sub read_config($) {
61     my ($cfg) = @_;
62     {
63         package CFG; # Put config data into a separate namespace
64
65         my $rc = do($cfg);
66
67         # Check for errors
68         if ($@) {
69             die("ERROR: Failure compiling '$cfg' - $@");
70         } elsif (! defined($rc)) {
71             die("ERROR: Failure reading '$cfg' - $!");
72         } elsif (! $rc) {
73             die("ERROR: Failure processing '$cfg'");
74         }
75     }
76     $builddir = File::Spec->rel2abs($CFG::builddir, dirname($cfg)) if defined $CFG::builddir;
77     print STDERR "novaboot: Read $cfg\n";
78 }
79
80 my @cfgs;
81 {
82     # We don't use $0 here, because it points to the novaboot itself and
83     # not to the novaboot script. The problem with this approach is that
84     # when a script is run as "novaboot <options> <script>" then $ARGV[0]
85     # contains the first option. Hence the -f check.
86     my $dir = File::Spec->rel2abs($ARGV[0] && -f $ARGV[0] ? dirname($ARGV[0]) : '', $invocation_dir);
87     while ((-d $dir || -l $dir ) && $dir ne "/") {
88         push @cfgs, "$dir/.novaboot" if -r "$dir/.novaboot";
89         my @dirs = File::Spec->splitdir($dir);
90         $dir = File::Spec->catdir(@dirs[0..$#dirs-1]);
91     }
92     @cfgs = reverse @cfgs;
93     $dir = $ENV{'NOVABOOT_CONFIG_DIR'} || '/etc/novaboot.d';
94     if (opendir(my $dh, $dir)) {
95         my @etccfg = map { "$dir/$_" } grep { /^[-_a-zA-Z0-9]+$/ && -f "$dir/$_" } readdir($dh);
96         closedir $dh;
97         @etccfg = sort(@etccfg);
98         @cfgs = ( @etccfg, @cfgs );
99     }
100 }
101 my $cfg = $ENV{'NOVABOOT_CONFIG'};
102 Getopt::Long::Configure(qw/no_ignore_case pass_through/);
103 GetOptions ("config|c=s" => \$cfg);
104 read_config($_) foreach $cfg or @cfgs;
105
106 ## Command line handling
107
108 my $explicit_target;
109 GetOptions ("target|t=s" => \$explicit_target);
110
111 my ($amt, @append, $bender, @chainloaders, $concat, $config_name_opt, $dhcp_tftp, $dump_opt, $dump_config, @exiton, $exiton_timeout, @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_cmd, @uboot_init);
112
113 %uboot_addr = (
114     'kernel'  => '${kernel_addr_r}',
115     'ramdisk' => '${ramdisk_addr_r}',
116     'fdt'     => '${fdt_addr_r}',
117     );
118
119 $rsync_flags = '';
120 $rom_prefix = 'rom://';
121 $stty = 'raw -crtscts -onlcr 115200';
122 $reset = 1;                     # Reset target by default
123
124 my @expect_seen = ();
125 sub handle_expect
126 {
127     my ($n, $v) = @_;
128     push(@expect_seen, '-re') if $n eq "expect-re";
129     push(@expect_seen, $v);
130 }
131
132 sub handle_send
133 {
134     my ($n, $v) = @_;
135     unless (@expect_seen) { die("No --expect before --send"); }
136     my $ret = ($n eq "sendcont") ? exp_continue : 0;
137     unshift(@expect_raw, sub { shift->send(eval("\"$v\"")); $ret; });
138     unshift(@expect_raw, @expect_seen);
139     @expect_seen = ();
140 }
141
142 my %opt_spec;
143 %opt_spec = (
144     "amt=s"          => \$amt,
145     "append|a=s"     => \@append,
146     "bender|b"       => \$bender,
147     "build-dir=s"    => sub { my ($n, $v) = @_; $builddir = File::Spec->rel2abs($v); },
148     "concat"         => \$concat,
149     "chainloader=s"  => \@chainloaders,
150     "dhcp-tftp|d"    => \$dhcp_tftp,
151     "dump"           => \$dump_opt,
152     "dump-config"    => \$dump_config,
153     "exiton=s"       => \@exiton,
154     "exiton-timeout=i"=> \$exiton_timeout,
155     "exiton-re=s"    => sub { my ($n, $v) = @_; push(@exiton, '-re', $v); },
156     "expect=s"       => \&handle_expect,
157     "expect-re=s"    => \&handle_expect,
158     "expect-raw=s"   => sub { my ($n, $v) = @_; unshift(@expect_raw, eval($v)); },
159     "gen-only"       => \$gen_only,
160     "grub|g:s"       => \$grub_config,
161     "grub-preamble=s"=> \$grub_preamble,
162     "prefix|grub-prefix=s" => \$grub_prefix,
163     "grub2:s"        => \$grub2_config,
164     "grub2-prolog=s" => \$grub2_prolog,
165     "ider"           => \$ider,
166     "iprelay=s"      => \$iprelay,
167     "iso:s"          => \$iso_image,
168     "kernel|k=s"     => \$kernel_opt,
169     "interactive|i"  => \$interactive,
170     "name=s"         => \$config_name_opt,
171     "make|m:s"       => \$make,
172     "no-file-gen"    => \$no_file_gen,
173     "off"            => \$off_opt,
174     "on"             => \$on_opt,
175     "pulsar|p:s"     => \$pulsar,
176     "pulsar-root=s"  => \$pulsar_root,
177     "qemu|Q:s"       => \$qemu,
178     "qemu-append=s"  => \$qemu_append,
179     "qemu-flags|q=s" => \$qemu_flags_cmd,
180     "remote-cmd=s"   => \$remote_cmd,
181     "remote-expect=s"=> \$remote_expect,
182     "reset!"         => \$reset,
183     "reset-cmd=s"    => \$reset_cmd,
184     "rsync-flags=s"  => \$rsync_flags,
185     "scons:s"        => \$scons,
186     "scriptmod=s"    => \@scriptmod,
187     "send=s"         => \&handle_send,
188     "sendcont=s"     => \&handle_send,
189     "serial|s:s"     => \$serial,
190     "server:s"       => \$server,
191     "strip-rom"      => sub { $rom_prefix = ''; },
192     "stty=s"         => \$stty,
193     "tftp"           => \$tftp,
194     "tftp-port=i"    => \$tftp_port,
195     "uboot:s"        => \$uboot,
196     "uboot-addr=s"   => \%uboot_addr,
197     "uboot-cmd=s"    => \$uboot_cmd,
198     "uboot-init=s"   => \@uboot_init,
199     "h"              => \$help,
200     "help"           => \$man,
201     );
202
203 # First process target options
204 {
205     my $t = defined($explicit_target) ? $explicit_target : $CFG::default_target;
206     my @target_expanded;
207     Getopt::Long::Configure(qw/no_ignore_case pass_through/);
208     while ($t) {
209         exists $CFG::targets{$t} or die("Unknown target '$t' (valid targets are: ".join(", ", sort keys(%CFG::targets)).")");
210
211         undef $explicit_target;
212         my ($ret, $remaining_args) = GetOptionsFromString ($CFG::targets{$t}, ("target|t=s" => \$explicit_target));
213         if (!$ret) { die "Error parsing target $t option"; }
214         push(@target_expanded, @$remaining_args);
215         $t = $explicit_target;
216     }
217     Getopt::Long::Configure(qw/no_ignore_case no_pass_through/);
218     GetOptionsFromArray(\@target_expanded, %opt_spec) or die ("Error in target definition");
219 }
220
221 # Then process other command line options - some of them may override
222 # what was specified by the target
223 GetOptions %opt_spec or die("Error in command line arguments");
224 pod2usage(1) if $help;
225 pod2usage(-exitstatus => 0, -verbose => 2) if $man;
226
227 ### Dump sanitized configuration (if requested)
228
229 if ($dump_config) {
230     use Data::Dumper;
231     $Data::Dumper::Indent=1;
232     print "# This file is in perl syntax.\n";
233     foreach my $key(sort(keys(%CFG::))) { # See "Symbol Tables" in perlmod(1)
234         if (defined ${$CFG::{$key}}) { print Data::Dumper->Dump([${$CFG::{$key}}], ["*$key"]); }
235         if (        @{$CFG::{$key}}) { print Data::Dumper->Dump([\@{$CFG::{$key}}], ["*$key"]); }
236         if (        %{$CFG::{$key}}) { print Data::Dumper->Dump([\%{$CFG::{$key}}], ["*$key"]); }
237     }
238     print "1;\n";
239     exit;
240 }
241
242 ### Sanitize configuration
243
244 if ($interactive && !-t STDIN) {
245     die("novaboot: Interactive mode not supported when not on terminal");
246 }
247
248 if (defined $config_name_opt && scalar(@ARGV) > 1) { die "You cannot use --name with multiple scripts"; }
249
250 if ($ider) {
251     $iso_image //= ''; # IDE-R needs an ISO image
252     if (!defined $amt) { die "Error: --ider requires --amt"; }
253 }
254
255 # Default options
256 if (defined $serial) {
257     $serial ||= "/dev/ttyUSB0";
258     $ENV{NB_SERIAL} = $serial;
259 }
260 if (defined $grub_config) { $grub_config ||= "menu.lst"; }
261 if (defined $grub2_config) { $grub2_config ||= "grub.cfg"; }
262
263 ## Parse the novaboot script(s)
264 my @scripts;
265 my $file;
266 my $EOF;
267 my $last_fn = '';
268 my ($modules, $variables, $generated, $continuation) = ([], {}, []);
269 my $skip_reading = defined($on_opt) || defined($off_opt);
270 while (!$skip_reading && ($_ = <>)) {
271     if ($ARGV ne $last_fn) { # New script
272         die "Missing EOF in $last_fn" if $file;
273         die "Unfinished line in $last_fn" if $continuation;
274         $last_fn = $ARGV;
275         push @scripts, { 'filename' => $ARGV,
276                          'modules' => $modules = [],
277                          'variables' => $variables = {},
278                          'generated' => $generated = []};
279
280     }
281     chomp();
282     next if /^#/ || /^\s*$/;    # Skip comments and empty lines
283
284     $_ =~ s/^[[:space:]]*// if ($continuation);
285
286     if (/\\$/) {                # Line continuation
287         $continuation .= substr($_, 0, length($_)-1);
288         next;
289     }
290
291     if ($continuation) {        # Last continuation line
292         $_ = $continuation . $_;
293         $continuation = '';
294     }
295
296     foreach my $mod(@scriptmod) { eval $mod; }
297
298     if ($file && $_ eq $EOF) {  # Heredoc end
299         undef $file;
300         next;
301     }
302     if ($file) {                # Heredoc content
303         push @{$file}, "$_\n";
304         next;
305     }
306     if (/^([A-Z_]+)=(.*)$/) {   # Internal variable
307         $$variables{$1} = $2;
308         push(@exiton, $2) if ($1 eq "EXITON");
309         next;
310     }
311     if (s/^load *//) {          # Load line
312         die("novaboot: '$last_fn' line $.: Missing file name\n") unless /^[^ <]+/;
313         if (/^([^ ]*)(.*?)[[:space:]]*<<([^ ]*)$/) { # Heredoc start
314             push @$modules, "$1$2";
315             $file = [];
316             push @$generated, {filename => $1, content => $file};
317             $EOF = $3;
318             next;
319         }
320         if (/^([^ ]*)(.*?)[[:space:]]*< ?(.*)$/) { # Command substitution
321             push @$modules, "$1$2";
322             push @$generated, {filename => $1, command => $3};
323             next;
324         }
325         push @$modules, $_;
326         next;
327     }
328     if (/^run (.*)/) {          # run line
329         push @$generated, {command => $1};
330         next;
331     }
332     if (/^uboot(?::([0-9]+)s)? (.*)/) { # uboot line
333         # TODO: If U-Boot supports some interactive menu, it might
334         # make sense to store uboot lines per novaboot script.
335         if ($1) {               # Command with explicit timeout
336             push @uboot_init, { command => $2,
337                                 timeout => $1 };
338         } else {                # Command without explicit timeout
339             push @uboot_init, $2;
340         }
341         next;
342     }
343
344     die("novaboot: Cannot parse script '$last_fn' line $.. Didn't you forget 'load' keyword?\n");
345 }
346 # use Data::Dumper;
347 # print Dumper(\@scripts);
348
349 foreach my $script (@scripts) {
350     $modules = $$script{modules};
351     @$modules[0] =~ s/^[^ ]*/$kernel_opt/ if $kernel_opt;
352     @$modules[0] .= ' ' . join(' ', @append) if @append;
353
354     my $kernel;
355     if (exists $variables->{KERNEL}) {
356         $kernel = $variables->{KERNEL};
357     } else {
358         if ($CFG::hypervisor) {
359             $kernel = $CFG::hypervisor . " ";
360             if (exists $variables->{HYPERVISOR_PARAMS}) {
361                 $kernel .= $variables->{HYPERVISOR_PARAMS};
362             } else {
363                 $kernel .= $CFG::hypervisor_params;
364             }
365         }
366     }
367     @$modules = ($kernel, @$modules) if $kernel;
368     @$modules = (@chainloaders, @$modules);
369     @$modules = ("bin/boot/bender", @$modules) if ($bender || defined $ENV{'NOVABOOT_BENDER'});
370 }
371
372 if ($dump_opt) {
373     foreach my $script (@scripts) {
374         print join("\n", @{$$script{modules}})."\n";
375     }
376     exit(0);
377 }
378
379 ## Helper functions
380
381 sub generate_configs($$$) {
382     my ($base, $generated, $filename) = @_;
383     if ($base) { $base = "$base/"; };
384     foreach my $g(@$generated) {
385       if (exists $$g{content}) {
386         my $config = $$g{content};
387         my $fn = $$g{filename};
388         open(my $f, '>', $fn) || die("$fn: $!");
389         map { s|\brom://([^ ]*)|$rom_prefix$base$1|g; print $f "$_"; } @{$config};
390         close($f);
391         print "novaboot: Created $fn\n";
392       } elsif (exists $$g{command} && ! $no_file_gen) {
393         $ENV{SRCDIR} = dirname(File::Spec->rel2abs( $filename, $invocation_dir ));
394         if (exists $$g{filename}) {
395             system_verbose("( $$g{command} ) > $$g{filename}");
396         } else {
397             system_verbose($$g{command});
398         }
399       }
400     }
401 }
402
403 sub generate_grub_config($$$$;$)
404 {
405     my ($filename, $title, $base, $modules_ref, $preamble) = @_;
406     if ($base) { $base = "$base/"; };
407     open(my $fg, '>', $filename) or die "$filename: $!";
408     print $fg "$preamble\n" if $preamble;
409     print $fg "title $title\n" if $title;
410     #print $fg "root $base\n"; # root doesn't really work for (nd)
411     my $first = 1;
412     foreach (@$modules_ref) {
413         if ($first) {
414             $first = 0;
415             my ($kbin, $kcmd) = split(' ', $_, 2);
416             $kcmd = '' if !defined $kcmd;
417             print $fg "kernel ${base}$kbin $kcmd\n";
418         } else {
419             s|\brom://([^ ]*)|$rom_prefix$base$1|g; # Translate rom:// files - needed for vdisk parameter of sigma0
420             print $fg "module $base$_\n";
421         }
422     }
423     close($fg);
424     print("novaboot: Created $builddir/$filename\n");
425     return $filename;
426 }
427
428 sub generate_syslinux_config($$$$)
429 {
430     my ($filename, $title, $base, $modules_ref) = @_;
431     if ($base && $base !~ /\/$/) { $base = "$base/"; };
432     open(my $fg, '>', $filename) or die "$filename: $!";
433     print $fg "LABEL $title\n";
434     #TODO print $fg "MENU LABEL $human_readable_title\n";
435
436     my ($kbin, $kcmd) = split(' ', @$modules_ref[0], 2);
437
438     if (system("file $kbin|grep 'Linux kernel'") == 0) {
439         my $initrd = @$modules_ref[1];
440         die('To many "load" lines for Linux kernel') if (scalar @$modules_ref > 2);
441         print $fg "LINUX $base$kbin\n";
442         print $fg "APPEND $kcmd\n";
443         print $fg "INITRD $base$initrd\n";
444     } else {
445         print $fg "KERNEL mboot.c32\n";
446         my @append;
447         foreach (@$modules_ref) {
448             s|\brom://([^ ]*)|$rom_prefix$base$1|g; # Translate rom:// files - needed for vdisk parameter of sigma0
449             push @append, "$base$_";
450             print $fg "APPEND ".join(' --- ', @append)."\n";
451         }
452     }
453     #TODO print $fg "TEXT HELP\n";
454     #TODO print $fg "some help here\n";
455     #TODO print $fg "ENDTEXT\n";
456     close($fg);
457     print("novaboot: Created $builddir/$filename\n");
458     return $filename;
459 }
460
461 sub generate_grub2_config($$$$;$$)
462 {
463     my ($filename, $title, $base, $modules_ref, $preamble, $prolog) = @_;
464     if ($base && substr($base,-1,1) ne '/') { $base = "$base/"; };
465     open(my $fg, '>', $filename) or die "$filename: $!";
466     print $fg "$preamble\n" if $preamble;
467     $title ||= 'novaboot';
468     print $fg "menuentry $title {\n";
469     print $fg "$prolog\n" if $prolog;
470     my $first = 1;
471     foreach (@$modules_ref) {
472         if ($first) {
473             $first = 0;
474             my ($kbin, $kcmd) = split(' ', $_, 2);
475             $kcmd = '' if !defined $kcmd;
476             print $fg "  multiboot ${base}$kbin $kcmd\n";
477         } else {
478             my @args = split;
479             # GRUB2 doesn't pass filename in multiboot info so we have to duplicate it here
480             $_ = join(' ', ($args[0], @args));
481             s|\brom://|$rom_prefix|g; # We do not need to translate path for GRUB2
482             print $fg "  module $base$_\n";
483         }
484     }
485     print $fg "}\n";
486     close($fg);
487     print("novaboot: Created $builddir/$filename\n");
488     return $filename;
489 }
490
491 sub generate_pulsar_config($$)
492 {
493     my ($filename, $modules_ref) = @_;
494     open(my $fg, '>', $filename) or die "$filename: $!";
495     print $fg "root $pulsar_root\n" if defined $pulsar_root;
496     my $first = 1;
497     my ($kbin, $kcmd);
498     foreach (@$modules_ref) {
499         if ($first) {
500             $first = 0;
501             ($kbin, $kcmd) = split(' ', $_, 2);
502             $kcmd = '' if !defined $kcmd;
503         } else {
504             my @args = split;
505             s|\brom://|$rom_prefix|g;
506             print $fg "load $_\n";
507         }
508     }
509     # Put kernel as last - this is needed for booting Linux and has no influence on non-Linux OSes
510     print $fg "exec $kbin $kcmd\n";
511     close($fg);
512     print("novaboot: Created $builddir/$filename\n");
513     return $filename;
514 }
515
516 sub shell_cmd_string(@)
517 {
518     return join(' ', map((/^[-_=a-zA-Z0-9\/\.\+]+$/ ? "$_" : "'$_'"), @_));
519 }
520
521 sub exec_verbose(@)
522 {
523     print "novaboot: Running: ".shell_cmd_string(@_)."\n";
524     exec(@_);
525     exit(1); # should not be reached
526 }
527
528 sub system_verbose($)
529 {
530     my $cmd = shift;
531     print "novaboot: Running: $cmd\n";
532     my $ret = system($cmd);
533     if ($ret & 0x007f) { die("Command terminated by a signal"); }
534     if ($ret & 0xff00) {die("Command exit with non-zero exit code"); }
535     if ($ret) { die("Command failure $ret"); }
536 }
537
538 ## WvTest headline
539
540 if (exists $variables->{WVDESC}) {
541     print "Testing \"$variables->{WVDESC}\" in $last_fn:\n";
542 } elsif ($last_fn =~ /\.wv$/) {
543     print "Testing \"all\" in $last_fn:\n";
544 }
545
546 ## Connect to the target and check whether it is not occupied
547
548 # We have to do this before file generation phase, because file
549 # generation is intermixed with file deployment phase and we want to
550 # check whether the target is not used by somebody else before
551 # deploying files. Otherwise, we may rewrite other user's files on a
552 # boot server.
553
554 my $exp; # Expect object to communicate with the target over serial line
555
556 my ($target_reset, $target_power_on, $target_power_off);
557 my ($amt_user, $amt_password, $amt_host, $amt_port);
558
559 if (defined $iprelay) {
560     my $IPRELAY;
561     $iprelay =~ /([.0-9]+)(:([0-9]+))?/;
562     my $addr = $1;
563     my $port = $3 || 23;
564     my $paddr   = sockaddr_in($port, inet_aton($addr));
565     my $proto   = getprotobyname('tcp');
566     socket($IPRELAY, PF_INET, SOCK_STREAM, $proto)  || die "socket: $!";
567     print "novaboot: Connecting to IP relay... ";
568     connect($IPRELAY, $paddr)    || die "connect: $!";
569     print "done\n";
570     $exp = Expect->init(\*$IPRELAY);
571     $exp->log_stdout(1);
572
573     while (1) {
574         print $exp "\xFF\xF6";  # AYT
575         my $connected = $exp->expect(20, # Timeout in seconds
576                                      '<iprelayd: connected>',
577                                      '-re', '<WEB51 HW[^>]*>');
578         last if $connected;
579     }
580
581     sub relaycmd($$) {
582         my ($relay, $onoff) = @_;
583         die unless ($relay == 1 || $relay == 2);
584
585         my $cmd = ($relay == 1 ? 0x5 : 0x6) | ($onoff ? 0x20 : 0x10);
586         return "\xFF\xFA\x2C\x32".chr($cmd)."\xFF\xF0";
587     }
588
589     sub relayconf($$) {
590         my ($relay, $onoff) = @_;
591         die unless ($relay == 1 || $relay == 2);
592         my $cmd = ($relay == 1 ? 0xdf : 0xbf) | ($onoff ? 0x00 : 0xff);
593         return "\xFF\xFA\x2C\x97".chr($cmd)."\xFF\xF0";
594     }
595
596     sub relay($$;$) {
597         my ($relay, $onoff, $can_giveup) = @_;
598         my $confirmation = '';
599         $exp->log_stdout(0);
600         print $exp relaycmd($relay, $onoff);
601         my $confirmed = $exp->expect(20, # Timeout in seconds
602                                      relayconf($relay, $onoff));
603         if (!$confirmed) {
604             if ($can_giveup) {
605                 print("Relay confirmation timeout - ignoring\n");
606             } else {
607                 die "Relay confirmation timeout";
608             }
609         }
610         $exp->log_stdout(1);
611     }
612
613     $target_reset = sub {
614         relay(2, 1, 1); # Reset the machine
615         usleep(100000);
616         relay(2, 0);
617     };
618
619     $target_power_off = sub {
620         relay(1, 1);            # Press power button
621         usleep(6000000);        # Long press to switch off
622         relay(1, 0);
623     };
624
625     $target_power_on = sub {
626         relay(1, 1);            # Press power button
627         usleep(100000);         # Short press
628         relay(1, 0);
629     };
630 }
631 elsif ($serial) {
632     my $CONN;
633     system_verbose("stty -F $serial $stty");
634     open($CONN, "+<", $serial) || die "open $serial: $!";
635     $exp = Expect->init(\*$CONN);
636 }
637 elsif ($remote_cmd) {
638     print "novaboot: Running: $remote_cmd\n";
639     $exp = Expect->spawn($remote_cmd);
640 }
641 elsif (defined $amt) {
642     require LWP::UserAgent;
643     require LWP::Authen::Digest;
644
645     sub genXML {
646         my ($host, $username, $password, $schema, $className, $pstate) = @_;
647         #AMT numbers for PowerStateChange (MNI => bluescreen on windows;-)
648         my %pstates = ("on"        => 2,
649                        "standby"   => 4,
650                        "hibernate" => 7,
651                        "off"       => 8,
652                        "reset"     => 10,
653                        "MNI"       => 11);
654         return <<END;
655                 <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">
656                 <s:Header><a:To>http://$host:16992/wsman</a:To>
657                 <w:ResourceURI s:mustUnderstand="true">$schema</w:ResourceURI>
658                 <a:ReplyTo><a:Address s:mustUnderstand="true">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address></a:ReplyTo>
659                 <a:Action s:mustUnderstand="true">$schema$className</a:Action>
660                 <w:MaxEnvelopeSize s:mustUnderstand="true">153600</w:MaxEnvelopeSize>
661                 <a:MessageID>uuid:709072C9-609C-4B43-B301-075004043C7C</a:MessageID>
662                 <w:Locale xml:lang="en-US" s:mustUnderstand="false" />
663                 <w:OperationTimeout>PT60.000S</w:OperationTimeout>
664                 <w:SelectorSet><w:Selector Name="Name">Intel(r) AMT Power Management Service</w:Selector></w:SelectorSet>
665                 </s:Header><s:Body>
666                 <p:RequestPowerStateChange_INPUT xmlns:p="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_PowerManagementService">
667                 <p:PowerState>$pstates{$pstate}</p:PowerState>
668                 <p:ManagedElement><a:Address>http://$host:16992/wsman</a:Address>
669                 <a:ReferenceParameters><w:ResourceURI>http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ComputerSystem</w:ResourceURI>
670                 <w:SelectorSet><w:Selector Name="Name">ManagedSystem</w:Selector></w:SelectorSet>
671                 </a:ReferenceParameters></p:ManagedElement>
672                 </p:RequestPowerStateChange_INPUT>
673                 </s:Body></s:Envelope>
674 END
675     }
676
677     sub sendPOST {
678         my ($host, $username, $password, $content) = @_;
679
680         my $ua = LWP::UserAgent->new();
681         $ua->agent("novaboot");
682
683         my $req = HTTP::Request->new(POST => "http://$host:16992/wsman");
684         my $res = $ua->request($req);
685         die ("Unexpected AMT response: " . $res->status_line) unless $res->code == 401;
686
687         my ($realm) = $res->header("WWW-Authenticate") =~ /Digest realm="(.*?)"/;
688         $ua->credentials("$host:16992", $realm, $username => $password);
689
690         # Create a request
691         $req = HTTP::Request->new(POST => "http://$host:16992/wsman");
692         $req->content_type('application/x-www-form-urlencoded');
693         $req->content($content);
694         $res = $ua->request($req);
695         die ("AMT power change request failed: " . $res->status_line) unless $res->is_success;
696         $res->content() =~ /<g:ReturnValue>(\d+)<\/g:ReturnValue>/;
697         return $1;
698     }
699
700     sub powerChange  {
701         my ($host, $username, $password, $pstate)=@_;
702         my $schema="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_PowerManagementService";
703         my $className="/RequestPowerStateChange";
704         my $content = genXML($host, $username, $password ,$schema, $className, $pstate);
705         return sendPOST($host, $username, $password, $content);
706     }
707
708     ($amt_user,$amt_password,$amt_host,$amt_port) = ($amt =~ /(?:(.*?)(?::(.*))?@)?([^:]*)(?::([0-9]*))?/);;
709     $amt_user ||= "admin";
710     $amt_password ||= $ENV{'AMT_PASSWORD'} || die "AMT password not specified";
711     $amt_host || die "AMT host not specified";
712     $amt_port ||= 16994;
713
714
715     $target_power_off = sub {
716         $exp->close();
717         my $result = powerChange($amt_host,$amt_user,$amt_password, "off");
718         die "AMT power off failed (ReturnValue $result)" if $result != 0;
719     };
720
721     $target_power_on = sub {
722         my $result = powerChange($amt_host,$amt_user,$amt_password, "on");
723         die "AMT power on failed (ReturnValue $result)" if $result != 0;
724     };
725
726     $target_reset = sub {
727         my $result = powerChange($amt_host,$amt_user,$amt_password, "reset");
728         if ($result != 0) {
729             print STDERR "Warning: Cannot reset $amt_host, trying power on. ";
730             $result = powerChange($amt_host,$amt_user,$amt_password, "on");
731         }
732         die "AMT reset failed (ReturnValue $result)" if $result != 0;
733     };
734
735     my $cmd = "amtterm -u $amt_user -p $amt_password $amt_host $amt_port";
736     print "novaboot: Running: $cmd\n" =~ s/\Q$amt_password\E/???/r;
737     $exp = Expect->spawn($cmd);
738     $exp->expect(10, "RUN_SOL") || die "Expect for 'RUN_SOL' timed out";
739
740 }
741
742
743 if ($remote_expect) {
744     $exp || die("No serial line connection");
745     $exp->expect(180, $remote_expect) || die "Expect for '$remote_expect' timed out";
746 }
747
748 if (defined $reset_cmd) {
749     $target_reset = sub {
750         system_verbose($reset_cmd);
751     };
752 }
753
754 if (defined $on_opt && defined $target_power_on) {
755     &$target_power_on();
756     exit;
757 }
758 if (defined $off_opt && defined $target_power_off) {
759     print "novaboot: Switching the target off...\n";
760     &$target_power_off();
761     exit;
762 }
763
764 $builddir ||= dirname(File::Spec->rel2abs( ${$scripts[0]}{filename})) if scalar @scripts;
765 if (defined $builddir) {
766     chdir($builddir) or die "Can't change directory to $builddir: $!";
767     print "novaboot: Entering directory `$builddir'\n";
768 } else {
769     $builddir = $invocation_dir;
770 }
771
772 ## File generation phase
773 my (%files_iso, $menu_iso, $filename);
774 my $config_name = '';
775 my $prefix = '';
776
777 foreach my $script (@scripts) {
778     $filename = $$script{filename};
779     $modules = $$script{modules};
780     $generated = $$script{generated};
781     $variables = $$script{variables};
782
783     ($config_name = $filename) =~ s#.*/##;
784     $config_name = $config_name_opt if (defined $config_name_opt);
785
786     if (exists $variables->{BUILDDIR}) {
787         $builddir = File::Spec->rel2abs($variables->{BUILDDIR});
788         chdir($builddir) or die "Can't change directory to $builddir: $!";
789         print "novaboot: Entering directory `$builddir'\n";
790     }
791
792     if ($grub_prefix) {
793         $prefix = $grub_prefix;
794         $prefix =~ s/\$NAME/$config_name/;
795         $prefix =~ s/\$BUILDDIR/$builddir/;
796     }
797     # TODO: use $grub_prefix as first parameter if some switch is given
798     generate_configs('', $generated, $filename);
799
800 ### Generate bootloader configuration files
801     my @bootloader_configs;
802     push @bootloader_configs, generate_grub_config($grub_config, $config_name, $prefix, $modules, $grub_preamble) if (defined $grub_config);
803     push @bootloader_configs, generate_grub2_config($grub2_config, $config_name, $prefix, $modules, $grub_preamble, $grub2_prolog) if (defined $grub2_config);
804     push @bootloader_configs, generate_pulsar_config('config-'.($pulsar||'novaboot'), $modules) if (defined $pulsar);
805
806 ### Run scons or make
807     {
808         my @files = map({ ($file) = m/([^ ]*)/; $file; } @$modules);
809         # Filter-out generated files
810         my @to_build = grep({ my $file = $_; !scalar(grep($file eq ($$_{filename} || ''), @$generated)) } @files);
811
812         system_verbose($scons || $CFG::scons." ".join(" ", @to_build)) if (defined $scons);
813         system_verbose($make  || $CFG::make ." ".join(" ", @to_build)) if (defined $make);
814     }
815
816 ### Copy files (using rsync)
817     if (defined $server && !defined($gen_only)) {
818         (my $real_server = $server) =~ s/\$NAME/$config_name/;
819
820         my ($hostname, $path) = split(":", $real_server, 2);
821         if (! defined $path) {
822             $path = $hostname;
823             $hostname = "";
824         }
825         my $files = join(" ", map({ ($file) = m/([^ ]*)/; $file; } ( @$modules, @bootloader_configs)));
826         map({ my $file = (split)[0]; die "$file: $!" if ! -f $file; } @$modules);
827         my $istty = -t STDOUT && ($ENV{'TERM'} || 'dumb') ne 'dumb';
828         my $progress = $istty ? "--progress" : "";
829         system_verbose("rsync $progress -RLp $rsync_flags $files $real_server");
830         if ($server =~ m|/\$NAME$| && $concat) {
831             my $cmd = join("; ", map { "( cd $path/.. && cat */$_ > $_ )" } @bootloader_configs);
832             system_verbose($hostname ? "ssh $hostname '$cmd'" : $cmd);
833         }
834     }
835
836 ### Prepare ISO image generation
837     if (defined $iso_image) {
838         generate_configs("(cd)", $generated, $filename);
839         my $menu;
840         generate_syslinux_config(\$menu, $config_name, "/", $modules);
841         $menu_iso .= "$menu\n";
842         map { ($file,undef) = split; $files_iso{$file} = 1; } @$modules;
843     }
844 }
845
846 ## Generate ISO image
847 if (defined $iso_image) {
848     system_verbose("mkdir -p isolinux");
849
850     my @files;
851     if (-f '/usr/lib/ISOLINUX/isolinux.bin') {
852         # Newer ISOLINUX version
853         @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);
854     } else {
855         # Older ISOLINUX version
856         @files = qw(/usr/lib/syslinux/isolinux.bin /usr/lib/syslinux/mboot.c32 /usr/lib/syslinux/menu.c32);
857     }
858     system_verbose("cp @files isolinux");
859     open(my $fh, ">isolinux/isolinux.cfg");
860     if ($#scripts) {
861         print $fh "TIMEOUT 50\n";
862         print $fh "DEFAULT menu\n";
863     } else {
864         print $fh "DEFAULT $config_name\n";
865     }
866     print $fh "$menu_iso";
867     close($fh);
868
869     my $files = join(" ", map("$_=$_", (keys(%files_iso), 'isolinux/isolinux.cfg', map(s|.*/|isolinux/|r, @files))));
870     $iso_image ||= "$config_name.iso";
871
872     # Note: We use -U flag below to "Allow 'untranslated' filenames,
873     # completely violating the ISO9660 standards". Without this
874     # option, isolinux is not able to read files names for example
875     # bzImage-3.0.
876     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");
877     print("ISO image created: $builddir/$iso_image\n");
878 }
879
880 exit(0) if defined $gen_only;
881
882 ## Boot the system using various methods and send serial output to stdout
883
884 if (scalar(@scripts) > 1 && ( defined $dhcp_tftp || defined $serial || defined $iprelay)) {
885     die "You cannot do this with multiple scripts simultaneously";
886 }
887
888 if ($variables->{WVTEST_TIMEOUT}) {
889     print "wvtest: timeout ", $variables->{WVTEST_TIMEOUT}, "\n";
890 }
891
892 sub trim($) {
893     my ($str) = @_;
894     $str =~ s/^\s+|\s+$//g;
895     return $str
896 }
897
898 ### Start in Qemu
899
900 if (defined $qemu) {
901     # Qemu
902     $qemu ||= $variables->{QEMU} || $CFG::qemu;
903     my @qemu_flags = split(" ", $qemu);
904     $qemu = shift(@qemu_flags);
905
906     @qemu_flags = split(/ +/, trim($variables->{QEMU_FLAGS})) if exists $variables->{QEMU_FLAGS};
907     @qemu_flags = split(/ +/, trim($qemu_flags_cmd)) if $qemu_flags_cmd;
908     push(@qemu_flags, split(/ +/, trim($qemu_append || '')));
909
910     if (defined $iso_image) {
911         # Boot NOVA with grub (and test the iso image)
912         push(@qemu_flags, ('-cdrom', $iso_image));
913     } else {
914         # Boot NOVA without GRUB
915
916         # Non-patched qemu doesn't like commas, but NUL can live with pluses instead of commans
917         foreach (@$modules) {s/,/+/g;}
918         generate_configs("", $generated, $filename);
919
920         if (scalar @$modules) {
921             my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);
922             $kcmd = '' if !defined $kcmd;
923             my $dtb;
924             @$modules = map { if (/\.dtb$/) { $dtb=$_; (); } else { $_ } } @$modules;
925             my $initrd = join ",", @$modules;
926
927             push(@qemu_flags, ('-kernel', $kbin, '-append', $kcmd));
928             push(@qemu_flags, ('-initrd', $initrd)) if $initrd;
929             push(@qemu_flags, ('-dtb', $dtb)) if $dtb;
930         }
931     }
932     push(@qemu_flags,  qw(-serial stdio)); # Redirect serial output (for collecting test restuls)
933     unshift(@qemu_flags, ('-name', $config_name));
934     print "novaboot: Running: ".shell_cmd_string($qemu, @qemu_flags)."\n";
935     $exp = Expect->spawn(($qemu, @qemu_flags)) || die("exec() failed: $!");
936 }
937
938 ### Local DHCPD and TFTPD
939
940 my ($dhcpd_pid, $tftpd_pid);
941
942 $tftp=1 if $tftp_port;
943
944 if (defined $dhcp_tftp)
945 {
946     generate_configs("(nd)", $generated, $filename);
947     system_verbose('mkdir -p tftpboot');
948     generate_grub_config("tftpboot/os-menu.lst", $config_name, "(nd)", \@$modules, "timeout 0");
949     open(my $fh, '>', 'dhcpd.conf');
950     my $mac = `cat /sys/class/net/eth0/address`;
951     chomp $mac;
952     print $fh "subnet 10.23.23.0 netmask 255.255.255.0 {
953                       range 10.23.23.10 10.23.23.100;
954                       filename \"bin/boot/grub/pxegrub.pxe\";
955                       next-server 10.23.23.1;
956 }
957 host server {
958         hardware ethernet $mac;
959         fixed-address 10.23.23.1;
960 }";
961     close($fh);
962     system_verbose("sudo ip a add 10.23.23.1/24 dev eth0;
963             sudo ip l set dev eth0 up;
964             sudo touch dhcpd.leases");
965
966     # We run servers by forking ourselves, because the servers end up
967     # in our process group and get killed by signals sent to the
968     # process group (e.g. Ctrl-C on terminal).
969     $dhcpd_pid = fork();
970     exec_verbose("sudo dhcpd -d -cf dhcpd.conf -lf dhcpd.leases -pf dhcpd.pid") if ($dhcpd_pid == 0);
971 }
972
973 if (defined $dhcp_tftp || defined $tftp) {
974     $tftp_port ||= 69;
975     # Unfortunately, tftpd requires root privileges even with
976     # non-privileged (>1023) port due to initgroups().
977     system_verbose("sudo in.tftpd --listen --secure -v -v -v --pidfile tftpd.pid  --address :$tftp_port $builddir");
978
979     # Kill server when we die
980     $SIG{__DIE__} = sub { system_verbose('sudo pkill --pidfile=dhcpd.pid') if (defined $dhcp_tftp);
981                           system_verbose('sudo pkill --pidfile=tftpd.pid'); };
982
983     # We have to kill tftpd explicitely, because it is not in our process group
984     $SIG{INT} = sub { system_verbose('sudo pkill --pidfile=tftpd.pid'); exit(0); };
985 }
986
987 ### AMT IDE-R
988 if (defined $ider) {
989     my $ider_cmd= "amtider -c $iso_image -u $amt_user -p $amt_password $amt_host $amt_port"  ;
990     print "novaboot: Running: $ider_cmd\n" =~ s/\Q$amt_password\E/???/r;
991     my $ider_pid = fork();
992     if ($ider_pid == 0) {
993         exec($ider_cmd);
994         die "IDE redirection failed";
995     }
996     # FIXME: This collides with --tftp option. Hopefully, nobody needs
997     # to use both simultaneously.
998     $SIG{__DIE__} = sub { system_verbose('kill $ider_pid'); };
999 }
1000
1001 ### Reset target (IP relay, AMT, ...)
1002
1003 if (defined $target_reset && $reset) {
1004     print "novaboot: Reseting the test box... ";
1005     &$target_reset();
1006     print "done\n";
1007 }
1008
1009 ### U-boot conversation
1010 if (defined $uboot) {
1011     my $uboot_prompt = $uboot || '=> ';
1012     print "novaboot: Waiting for U-Boot prompt...\n";
1013     $exp || die("No serial line connection");
1014     $exp->log_stdout(1);
1015     #$exp->exp_internal(1);
1016     $exp->expect(20,
1017                  [qr/Hit any key to stop autoboot:/, sub { $exp->send("\n"); exp_continue; }],
1018                  $uboot_prompt) || die "No U-Boot prompt deteceted";
1019     foreach my $cmdspec (@uboot_init) {
1020         my ($cmd, $timeout);
1021         if (ref($cmdspec) eq "HASH") {
1022             $cmd = $cmdspec->{command};
1023             $timeout = $cmdspec->{timeout};
1024         } else {
1025             $cmd = $cmdspec;
1026             $timeout = 10;
1027         }
1028         if ($cmd =~ /\$NB_MYIP/) {
1029             my $ip = (grep /inet /, `ip addr show eth0`)[0] || die "Problem determining our IP address";
1030             $ip =~ s/\s*inet ([0-9.]*).*/$1/;
1031             $cmd =~ s/\$NB_MYIP/$ip/g;
1032         }
1033         if ($cmd =~ /\$NB_PREFIX/) {
1034             my $p = $prefix;
1035             $p =~ s|/*$||;
1036             $cmd =~ s/\$NB_PREFIX/$p/g;
1037         }
1038         chomp($cmd);
1039         $exp->send("$cmd\n");
1040         $exp->expect($timeout, $uboot_prompt) || die "U-Boot prompt timeout";
1041     }
1042
1043     # Boot the system if there are some load lines in the script
1044     if (scalar(@$modules) > 0 && !$variables->{NO_BOOT}) {
1045         my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);
1046         my $dtb;
1047         @$modules = map { if (/\.dtb$/) { $dtb=$_; (); } else { $_ } } @$modules;
1048         my $initrd = shift @$modules;
1049
1050         die "No '--uboot-addr kernel' given" unless $uboot_addr{kernel};
1051         $exp->send("tftpboot $uboot_addr{kernel} $prefix$kbin\n");
1052         $exp->expect(10,
1053                      [qr/##/, sub { exp_continue; }],
1054                      $uboot_prompt) || die "Kernel load timeout";
1055         if (defined $dtb) {
1056             die "No '--uboot-addr fdt' given" unless $uboot_addr{fdt};
1057             $exp->send("tftpboot $uboot_addr{fdt} $prefix$dtb\n");
1058             $exp->expect(10,
1059                          [qr/##/, sub { exp_continue; }],
1060                          $uboot_prompt) || die "Device tree load timeout";
1061         }
1062         if (defined $initrd) {
1063             die "No '--uboot-addr ramdisk' given" unless $uboot_addr{ramdisk};
1064             $exp->send("tftpboot $uboot_addr{ramdisk} $prefix$initrd\n");
1065             $exp->expect(10,
1066                          [qr/##/, sub { exp_continue; }],
1067                          $uboot_prompt) || die "Initrd load timeout";
1068         } else {
1069             $uboot_addr{ramdisk} = '-';
1070         }
1071
1072         $kcmd //= '';
1073         $exp->send("setenv bootargs $kcmd\n");
1074         $exp->expect(5, $uboot_prompt)  || die "U-Boot prompt timeout";
1075
1076         $uboot_cmd //= $variables->{UBOOT_CMD} // 'bootm $kernel_addr $ramdisk_addr $fdt_addr';
1077         $uboot_cmd =~ s/\$kernel_addr/$uboot_addr{kernel}/g;
1078         $uboot_cmd =~ s/\$ramdisk_addr/$uboot_addr{ramdisk}/g;
1079         $uboot_cmd =~ s/\$fdt_addr/$uboot_addr{fdt}/g;
1080
1081         $exp->send($uboot_cmd . "\n");
1082         $exp->expect(5, "\n")  || die "U-Boot command timeout";
1083     }
1084 }
1085
1086 ### Serial line interaction
1087 if (defined $exp) {
1088     # Serial line of the target is available
1089     my $interrupt = 'Ctrl-C';
1090     if ($interactive && !@exiton) {
1091         $interrupt = '"~~."';
1092     }
1093     my $note = (-t STDIN) ? '' : '- only target->host ';
1094     print "novaboot: Serial line interaction $note(press $interrupt to interrupt)...\n";
1095     $exp->log_stdout(1);
1096     if (@exiton) {
1097         $exp->expect($exiton_timeout, @expect_raw, @exiton) || die("exiton timeout");
1098         print "\n";
1099     } else {
1100         my @inputs = ($exp);
1101         if (-t STDIN) { # Set up bi-directional communication if we run on terminal
1102             my $infile = new IO::File;
1103             $infile->IO::File::fdopen(*STDIN,'r');
1104             my $in_object = Expect->exp_init($infile);
1105             $in_object->set_group($exp);
1106
1107             if ($interactive) {
1108                 $in_object->set_seq('~~\.', sub { print "novaboot: Escape sequence detected\r\n"; undef; });
1109                 $in_object->manual_stty(0);       # Use raw terminal mode
1110             } else {
1111                 $in_object->manual_stty(1);       # Do not modify terminal settings
1112             }
1113             push(@inputs, $in_object);
1114         }
1115         #use Data::Dumper;
1116         #print Dumper(\@expect_raw);
1117         $exp->expect(undef, @expect_raw) if @expect_raw;
1118         Expect::interconnect(@inputs) unless defined($exp->exitstatus);
1119     }
1120 }
1121
1122 ## Kill dhcpc or tftpd
1123 if (defined $dhcp_tftp || defined $tftp) {
1124     die("novaboot: This should kill servers on background\n");
1125 }
1126
1127 ## Documentation
1128
1129 =head1 NAME
1130
1131 novaboot - Boots a locally compiled operating system on a remote
1132 target or in qemu
1133
1134 =head1 SYNOPSIS
1135
1136 B<novaboot> --help
1137
1138 B<novaboot> [option]... [--] script...
1139
1140 B<./script> [option]...
1141
1142 =head1 DESCRIPTION
1143
1144 This program makes booting of a locally compiled operating system (OS)
1145 (e.g. NOVA or Linux) on remote targets as simple as running a program
1146 locally. It automates things like copying OS images to a TFTP server,
1147 generation of bootloader configuration files, resetting of target
1148 hardware or redirection of target's serial line to stdin/out. Novaboot
1149 is highly configurable and it makes it easy to boot a single image on
1150 different targets or different images on a single target.
1151
1152 Novaboot operation is controlled by command line options and by a so
1153 called novaboot script, which can be thought as a generalization of
1154 bootloader configuration files (see L</"NOVABOOT SCRIPT SYNTAX">).
1155 Typical way of using novaboot is to make the novaboot script
1156 executable and set its first line to I<#!/usr/bin/env novaboot>. Then,
1157 booting a particular OS configuration becomes the same as executing a
1158 local program - the novaboot script.
1159
1160 Novaboot uses configuration files to, among other things, define
1161 command line options needed for different targets. Users typically use
1162 only the B<-t>/B<--target> command line option to select the target.
1163 Internally, this option expands to the pre-configured options.
1164 Configuration files are searched at multiple places, which allows to
1165 have per-system, per-user or per-project configurations. Configuration
1166 file syntax is described in section L</"CONFIGURATION FILE">.
1167
1168 Simple examples of using C<novaboot>:
1169
1170 =over 3
1171
1172 =item 1.
1173
1174 Run an OS in Qemu. This is the default action when no other action is
1175 specified by command line switches. Thus running C<novaboot myos> (or
1176 C<./myos> as described above) will run Qemu and make it boot the
1177 configuration specified in the F<myos> script.
1178
1179 =item 2.
1180
1181 Create a bootloader configuration file (currently supported
1182 bootloaders are GRUB, GRUB2, ISOLINUX, Pulsar and U-Boot) and copy it
1183 with all other files needed for booting to a remote boot server. Then
1184 use a TCP/IP-controlled relay/serial-to-TCP converter to reset the
1185 target and receive its serial output.
1186
1187  ./myos --grub2 --server=192.168.1.1:/tftp --iprelay=192.168.1.2
1188
1189 =item 3.
1190
1191 Run DHCP and TFTP server on developer's machine to boot the target
1192 from it.
1193
1194  ./myos --dhcp-tftp
1195
1196 This is useful when no network infrastructure is in place and
1197 the target is connected directly to developer's box.
1198
1199 =item 4.
1200
1201 Create bootable ISO image.
1202
1203  novaboot --iso -- script1 script2
1204
1205 The created ISO image will have ISOLINUX bootloader installed on it
1206 and the boot menu will allow selecting between I<script1> and
1207 I<script2> configurations.
1208
1209 =back
1210
1211 =head1 PHASES AND OPTIONS
1212
1213 Novaboot performs its work in several phases. Each phase can be
1214 influenced by several command line options, certain phases can be
1215 skipped. The list of phases (in the execution order) and the
1216 corresponding options follows.
1217
1218 =head2 Configuration reading phase
1219
1220 After starting, novaboot reads configuration files. Their content is
1221 described in section L</"CONFIGURATION FILE">. By default,
1222 configuration is read from two locations. First from the configuration
1223 directory and second from F<.novaboot> files along the path to the
1224 current directory. The latter read files override settings from the
1225 former ones.
1226
1227 Configuration directory is determined by the content of
1228 NOVABOOT_CONFIG_DIR environment variable and defaults to
1229 F</etc/novaboot.d>. Files in this directory with names consisting
1230 solely of English letters, numbers, dashes '-' and underscores '_'
1231 (note that dot '.' is not included) are read in alphabetical order.
1232
1233 Then novaboot searches for files named F<.novaboot> starting from the
1234 directory of the novaboot script (or working directory, see bellow)
1235 and continuing upwards up to the root directory. The found
1236 configuration files are then read in the opposite order (i.e. from the
1237 root directory downwards). This allows to have, for example, user
1238 specific configuration in F<~/.novaboot> and project specific one in
1239 F<~/project/.novaboot>.
1240
1241 In certain cases, the location of the novaboot script cannot be
1242 determined in this early phase. This happens either when the script is
1243 read from the standard input or when novaboot is invoked explicitly as
1244 in the example L</"4."> above. In this case the current working
1245 directory is used as a starting point for configuration file search
1246 instead of the novaboot script directory.
1247
1248 =over 8
1249
1250 =item -c, --config=I<filename>
1251
1252 Use the specified configuration file instead of the default one(s).
1253
1254 =back
1255
1256 =head2 Command line processing phase
1257
1258 =over 8
1259
1260 =item --dump-config
1261
1262 Dump the current configuration to stdout end exit. Useful as an
1263 initial template for a configuration file.
1264
1265 =item -h, --help
1266
1267 Print short (B<-h>) or long (B<--help>) help.
1268
1269 =item -t, --target=I<target>
1270
1271 This option serves as a user configurable shortcut for other novaboot
1272 options. The effect of this option is the same as the options stored
1273 in the C<%targets> configuration variable under key I<target>. See
1274 also L</"CONFIGURATION FILE">.
1275
1276 =back
1277
1278 =head2 Script preprocessing phase
1279
1280 This phases allows to modify the parsed novaboot script before it is
1281 used in the later phases.
1282
1283 =over 8
1284
1285 =item -a, --append=I<parameters>
1286
1287 Append a string to the first C<load> line in the novaboot script. This
1288 can be used to append parameters to the kernel's or root task's
1289 command line. This option can appear multiple times.
1290
1291 =item -b, --bender
1292
1293 Use F<bender> chainloader. Bender scans the PCI bus for PCI serial
1294 ports and stores the information about them in the BIOS data area for
1295 use by the kernel.
1296
1297 =item --chainloader=I<chainloader>
1298
1299 Specifies a chainloader that is loaded before the kernel and other
1300 files specified in the novaboot script. E.g. 'bin/boot/bender
1301 promisc'.
1302
1303 =item --dump
1304
1305 Print the modules to boot and their parameters after this phase
1306 finishes. Then exit. This is useful for seeing the effect of other
1307 options in this section.
1308
1309 =item -k, --kernel=F<file>
1310
1311 Replace the first word on the first C<load> line in the novaboot
1312 script with F<file>.
1313
1314 =item --scriptmod=I<perl expression>
1315
1316 When novaboot script is read, I<perl expression> is executed for every
1317 line (in $_ variable). For example, C<novaboot
1318 --scriptmod=s/sigma0/omega6/g> replaces every occurrence of I<sigma0>
1319 in the script with I<omega6>.
1320
1321 When this option is present, it overrides I<$script_modifier> variable
1322 from the configuration file, which has the same effect. If this option
1323 is given multiple times all expressions are evaluated in the command
1324 line order.
1325
1326 =back
1327
1328 =head2 File generation phase
1329
1330 In this phase, files needed for booting are generated in a so called
1331 I<build directory> (see L</--build-dir>). In most cases configuration
1332 for a bootloader is generated automatically by novaboot. It is also
1333 possible to generate other files using I<heredoc> or I<"<"> syntax in
1334 novaboot scripts. Finally, binaries can be generated in this phases by
1335 running C<scons> or C<make>.
1336
1337 =over 8
1338
1339 =item --build-dir=I<directory>
1340
1341 Overrides the default build directory location.
1342
1343 The default build directory location is determined as follows: If the
1344 configuration file defines the C<$builddir> variable, its value is
1345 used. Otherwise, it is the directory that contains the first processed
1346 novaboot script.
1347
1348 See also L</BUILDDIR> variable.
1349
1350 =item -g, --grub[=I<filename>]
1351
1352 Generates grub bootloader menu file. If the I<filename> is not
1353 specified, F<menu.lst> is used. The I<filename> is relative to the
1354 build directory (see B<--build-dir>).
1355
1356 =item --grub-preamble=I<prefix>
1357
1358 Specifies the I<preable> that is at the beginning of the generated
1359 GRUB or GRUB2 config files. This is useful for specifying GRUB's
1360 timeout.
1361
1362 =item --prefix=I<prefix>
1363
1364 Specifies I<prefix> (e.g. F</srv/tftp>) that is put in front of every
1365 file name in generated bootloader configuration files (or in U-Boot
1366 commands).
1367
1368 If the I<prefix> contains string $NAME, it will be replaced with the
1369 name of the novaboot script (see also B<--name>).
1370
1371 If the I<prefix> contains string $BUILDDIR, it will be replaced with
1372 the build directory (see also B<--build-dir>).
1373
1374 =item --grub-prefix
1375
1376 Alias for B<--prefix>.
1377
1378 =item --grub2[=I<filename>]
1379
1380 Generate GRUB2 menu entry in I<filename>. If I<filename> is not
1381 specified F<grub.cfg> is used. The content of the menu entry can be
1382 customized with B<--grub-preamble>, B<--grub2-prolog> or
1383 B<--grub_prefix> options.
1384
1385 In order to use the the generated menu entry on your development
1386 machine that uses GRUB2, append the following snippet to
1387 F</etc/grub.d/40_custom> file and regenerate your grub configuration,
1388 i.e. run update-grub on Debian/Ubuntu.
1389
1390   if [ -f /path/to/nul/build/grub.cfg ]; then
1391     source /path/to/nul/build/grub.cfg
1392   fi
1393
1394 =item --grub2-prolog=I<prolog>
1395
1396 Specifies text that is put at the beginning of the GRUB2 menu entry.
1397
1398 =item -m, --make[=make command]
1399
1400 Runs C<make> to build files that are not generated by novaboot itself.
1401
1402 =item --name=I<string>
1403
1404 Use the name I<string> instead of the name of the novaboot script.
1405 This name is used for things like a title of grub menu or for the
1406 server directory where the boot files are copied to.
1407
1408 =item --no-file-gen
1409
1410 Do not run external commands to generate files (i.e. "<" syntax and
1411 C<run> keyword). This switch does not influence generation of files
1412 specified with "<<WORD" syntax.
1413
1414 =item -p, --pulsar[=mac]
1415
1416 Generates pulsar bootloader configuration file named F<config-I<mac>>
1417 The I<mac> string is typically a MAC address and defaults to
1418 I<novaboot>.
1419
1420 =item --scons[=scons command]
1421
1422 Runs C<scons> to build files that are not generated by novaboot
1423 itself.
1424
1425 =item --strip-rom
1426
1427 Strip I<rom://> prefix from command lines and generated config files.
1428 The I<rom://> prefix is used by NUL. For NRE, it has to be stripped.
1429
1430 =item --gen-only
1431
1432 Exit novaboot after file generation phase.
1433
1434 =back
1435
1436 =head2 Target connection check
1437
1438 If supported by the target, the connection to it is made and it is
1439 checked whether the target is not occupied by another novaboot
1440 user/instance.
1441
1442 =over 8
1443
1444 =item --amt=I<"[user[:password]@]host[:port]>
1445
1446 Use Intel AMT technology to control the target machine. WS management
1447 is used to powercycle it and Serial-Over-Lan (SOL) for input/output.
1448 The hostname or (IP address) is given by the I<host> parameter. If
1449 I<password> is not specified, environment variable AMT_PASSWORD is
1450 used. The I<port> specifies a TCP port for SOL. If not specified, the
1451 default is 16992. Default I<user> is admin.
1452
1453 =item --iprelay=I<addr[:port]>
1454
1455 Use TCP/IP relay and serial port to access the target's serial port
1456 and powercycle it. The IP address of the relay is given by I<addr>
1457 parameter. If I<port> is not specified, it default to 23.
1458
1459 Note: This option is supposed to work with HWG-ER02a IP relays.
1460
1461 =item -s, --serial[=device]
1462
1463 Target's serial line is connected to host's serial line (device). The
1464 default value for device is F</dev/ttyUSB0>.
1465
1466 The value of this option is exported in NB_NOVABOOT environment
1467 variable to all subprocesses run by C<novaboot>.
1468
1469 =item --stty=I<settings>
1470
1471 Specifies settings passed to C<stty> invoked on the serial line
1472 specified with B<--serial> option. If this option is not given,
1473 C<stty> is called with C<raw -crtscts -onlcr 115200> settings.
1474
1475 =item --remote-cmd=I<cmd>
1476
1477 Command that mediates connection to the target's serial line. For
1478 example C<ssh server 'cu -l /dev/ttyS0'>.
1479
1480 =item --remote-expect=I<string>
1481
1482 Wait for reception of I<string> after establishing the the remote
1483 connection before continuing.
1484
1485
1486 =back
1487
1488 =head2 File deployment phase
1489
1490 In some setups, it is necessary to copy the files needed for booting
1491 to a particular location, e.g. to a TFTP boot server or to the
1492 F</boot> partition.
1493
1494 =over 8
1495
1496 =item -d, --dhcp-tftp
1497
1498 Turns your workstation into a DHCP and TFTP server so that the OS can
1499 be booted via PXE BIOS (or similar mechanism) on the test machine
1500 directly connected by a plain Ethernet cable to your workstation.
1501
1502 The DHCP and TFTP servers requires root privileges and C<novaboot>
1503 uses C<sudo> command to obtain those. You can put the following to
1504 I</etc/sudoers> to allow running the necessary commands without asking
1505 for password.
1506
1507  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
1508  your_login ALL=NOPASSWD: NOVABOOT
1509
1510 =item --tftp
1511
1512 Starts a TFTP server on your workstation. This is similar to
1513 B<--dhcp-tftp> except that DHCP server is not started.
1514
1515 The TFTP server require root privileges and C<novaboot> uses C<sudo>
1516 command to obtain those. You can put the following to I</etc/sudoers>
1517 to allow running the necessary commands without asking for password.
1518
1519  Cmnd_Alias NOVABOOT =  /usr/sbin/in.tftpd --listen --secure -v -v -v --pidfile tftpd.pid *, /usr/bin/pkill --pidfile=tftpd.pid
1520  your_login ALL=NOPASSWD: NOVABOOT
1521
1522 =item --tftp-port=I<port>
1523
1524 Port to run the TFTP server on. Implies B<--tftp>.
1525
1526 =item --iso[=filename]
1527
1528 Generates the ISO image that boots NOVA system via GRUB. If no filename
1529 is given, the image is stored under I<NAME>.iso, where I<NAME> is the name
1530 of the novaboot script (see also B<--name>).
1531
1532 =item --server[=[[user@]server:]path]
1533
1534 Copy all files needed for booting to another location. The files will
1535 be copied (by B<rsync> tool) to the directory I<path>. If the I<path>
1536 contains string $NAME, it will be replaced with the name of the
1537 novaboot script (see also B<--name>).
1538
1539 =item --rsync-flags=I<flags>
1540
1541 Specifies which I<flags> are appended to F<rsync> command line when
1542 copying files as a result of I<--server> option.
1543
1544 =item --concat
1545
1546 If B<--server> is used and its value ends with $NAME, then after
1547 copying the files, a new bootloader configuration file (e.g. menu.lst)
1548 is created at I<path-wo-name>, i.e. the path specified by B<--server>
1549 with $NAME part removed. The content of the file is created by
1550 concatenating all files of the same name from all subdirectories of
1551 I<path-wo-name> found on the "server".
1552
1553 =item --ider
1554
1555 Use Intel AMT technology for IDE redirection. This allows the target
1556 machine to boot from novaboot created ISO image. Implies B<--iso>.
1557
1558 The experimental C<amtider> utility needed by this option can be
1559 obtained from https://github.com/wentasah/amtterm.
1560
1561 =back
1562
1563 =head2 Target power-on and reset phase
1564
1565 At this point, the target is reset (or switched on/off). There is
1566 several ways how this can be accomplished. Resetting a physical target
1567 can currently be accomplished by the following options: B<--amt>,
1568 B<--iprelay>, B<--reset-cmd>.
1569
1570 =over 8
1571
1572 =item --on, --off
1573
1574 Switch on/off the target machine and exit. The script (if any) is
1575 completely ignored. Currently it works only with B<--iprelay> or
1576 B<--amt>.
1577
1578 =item -Q, --qemu[=I<qemu-binary>]
1579
1580 Boot the configuration in qemu. Optionally, the name of qemu binary
1581 can be specified as a parameter.
1582
1583 =item --qemu-append=I<flags>
1584
1585 Append I<flags> to the default qemu flags (QEMU_FLAGS variable or
1586 C<-cpu coreduo -smp 2>).
1587
1588 =item -q, --qemu-flags=I<flags>
1589
1590 Replace the default qemu flags (QEMU_FLAGS variable or C<-cpu coreduo
1591 -smp 2>) with I<flags> specified here.
1592
1593 =item --reset-cmd=I<cmd>
1594
1595 Command that resets the target.
1596
1597 =item --no-reset, --reset
1598
1599 Disable/enable resetting of the target.
1600
1601 =back
1602
1603 =head2 Interaction with the bootloader on the target
1604
1605 =over 8
1606
1607 =item --uboot[=I<prompt>]
1608
1609 Interact with U-Boot bootloader to boot the thing described in the
1610 novaboot script. I<prompt> specifies the U-Boot's prompt (default is
1611 "=> ", other common prompts are "U-Boot> " or "U-Boot# ").
1612 Implementation of this option is currently tied to a particular board
1613 that we use. It may be subject to changes in the future!
1614
1615 =item --uboot-init
1616
1617 Command(s) to send the U-Boot bootloader before loading the images and
1618 booting them. This option can be given multiple times. After sending
1619 commands from each option novaboot waits for U-Boot I<prompt>.
1620
1621 If the command contains string I<$NB_MYIP> then this string is
1622 replaced by IPv4 address of eth0 interface. Similarly I<$NB_PREFIX> is
1623 replaced with prefix given by B<--prefix>.
1624
1625 See also C<uboot> keyword in L</"NOVABOOT SCRIPT SYNTAX">).
1626
1627 =item --uboot-addr I<name>=I<address>
1628
1629 Load address of U-Boot's C<tftpboot> command for loading I<name>,
1630 where name is one of I<kernel>, I<ramdisk> or I<fdt> (flattened device
1631 tree).
1632
1633 The default addresses are ${I<name>_addr_r}, i.e. U-Boot environment
1634 variables used by convention for this purpose.
1635
1636 =item --uboot-cmd=I<command>
1637
1638 Specifies U-Boot command used to execute the OS. If the command
1639 contains strings C<$kernel_addr>, C<$ramdisk_addr>, C<$fdt_addr>,
1640 these are replaced with the addresses configured with B<--uboot-addr>.
1641
1642 The default value is
1643
1644     bootm $kernel_addr $ramdisk_addr $fdt_addr
1645
1646 or the C<UBOOT_CMD> variable if defined in the novaboot script.
1647
1648 =back
1649
1650 =head2 Target interaction phase
1651
1652 In this phase, target's serial output is redirected to stdout and if
1653 stdin is a TTY, it is redirected to the target's serial input allowing
1654 interactive work with the target.
1655
1656 =over 8
1657
1658 =item --exiton=I<string>
1659
1660 When I<string> is sent by the target, novaboot exits. This option can
1661 be specified multiple times, in which case novaboot exits whenever
1662 either of the specified strings is sent.
1663
1664 If I<string> is C<-re>, then the next B<--exiton>'s I<string> is
1665 treated as regular expression. For example:
1666
1667     --exiton -re --exiton 'error:.*failed'
1668
1669 =item --exiton-re=I<regex>
1670
1671 The same as --exiton -re --exiton I<regex>.
1672
1673 =item --exiton-timeout=I<seconds>
1674
1675 By default B<--exiton> waits for the string match forever. When this
1676 option is specified, "exiton" timeouts after the specifies number of
1677 seconds and novaboot returns non-zero exit code.
1678
1679 =item -i, --interactive
1680
1681 Setup things for interactive use of target. Your terminal will be
1682 switched to raw mode. In raw mode, your system does not process input
1683 in any way (no echoing of entered characters, no interpretation
1684 special characters). This, among others, means that Ctrl-C is passed
1685 to the target and does no longer interrupt novaboot. Use "~~."
1686 sequence to exit novaboot.
1687
1688 =item --expect=I<string>
1689
1690 When I<string> is received from the target, send the string specified
1691 with the subsequent B<--send*> option to the target.
1692
1693 =item --expect-re=I<regex>
1694
1695 When target's output matches regular expression I<regex>, send the
1696 string specified with the subsequent B<--send*> option to the target.
1697
1698 =item --expect-raw=I<perl-code>
1699
1700 Provides direct control over Perl's Expect module.
1701
1702 =item --send=I<string>
1703
1704 Send I<string> to the target after the previously specified
1705 B<--expect*> was matched in the target's output. The I<string> may
1706 contain escape sequences such as "\n".
1707
1708 Note that I<string> is actually interpreted by Perl, so it can contain
1709 much more that escape sequences. This behavior may change in the
1710 future.
1711
1712 Example: C<--expect='login: ' --send='root\n'>
1713
1714 =item --sendcont=I<string>
1715
1716 Similar to B<--send> but continue expecting more input.
1717
1718 Example: C<--expect='Continue?' --sendcont='yes\n'>
1719
1720 =back
1721
1722 =head1 NOVABOOT SCRIPT SYNTAX
1723
1724 The syntax tries to mimic POSIX shell syntax. The syntax is defined
1725 with the following rules.
1726
1727 Lines starting with "#" and empty lines are ignored.
1728
1729 Lines that end with "\" are concatenated with the following line after
1730 removal of the final "\" and leading whitespace of the following line.
1731
1732 Lines of the form I<VARIABLE=...> (i.e. matching '^[A-Z_]+=' regular
1733 expression) assign values to internal variables. See L</VARIABLES>
1734 section.
1735
1736 Lines starting with C<load> keyword represent modules to boot. The
1737 word after C<load> is a file name (relative to the build directory
1738 (see B<--build-dir>) of the module to load and the remaining words are
1739 passed to it as the command line parameters.
1740
1741 When the C<load> line ends with "<<WORD" then the subsequent lines
1742 until the line containing solely WORD are copied literally to the file
1743 named on that line. This is similar to shell's heredoc feature.
1744
1745 When the C<load> line ends with "< CMD" then command CMD is executed
1746 with F</bin/sh> and its standard output is stored in the file named on
1747 that line. The SRCDIR variable in CMD's environment is set to the
1748 absolute path of the directory containing the interpreted novaboot
1749 script.
1750
1751 Lines starting with C<run> keyword contain shell commands that are run
1752 during file generation phase. This is the same as the "< CMD" syntax
1753 for C<load> keyboard except that the command's output is not
1754 redirected to a file. The ordering of commands is the same as they
1755 appear in the novaboot script.
1756
1757 Lines starting with C<uboot> represent U-Boot commands that are sent
1758 to the target if B<--uboot> option is given. Having a U-Boot line in
1759 the novaboot script is the same as passing an equivalent
1760 B<--uboot-init> option to novaboot. The C<uboot> keyword can be
1761 suffixed with timeout specification. The syntax is C<uboot:Ns>, where
1762 C<N> is the whole number of seconds. If the U-Boot command prompt does
1763 not appear before the timeout, novaboot fails. The default timeout is
1764 10 seconds.
1765
1766 Example (Linux):
1767
1768   #!/usr/bin/env novaboot
1769   load bzImage console=ttyS0,115200
1770   run  make -C buildroot
1771   load rootfs.cpio < gen_cpio buildroot/images/rootfs.cpio "myapp->/etc/init.d/S99myapp"
1772
1773 Example (NOVA User Land - NUL):
1774
1775   #!/usr/bin/env novaboot
1776   WVDESC=Example program
1777   load bin/apps/sigma0.nul S0_DEFAULT script_start:1,1 \
1778                            verbose hostkeyb:0,0x60,1,12,2
1779   load bin/apps/hello.nul
1780   load hello.nulconfig <<EOF
1781   sigma0::mem:16 name::/s0/log name::/s0/timer name::/s0/fs/rom ||
1782   rom://bin/apps/hello.nul
1783   EOF
1784
1785 This example will load three modules: F<sigma0.nul>, F<hello.nul> and
1786 F<hello.nulconfig>. sigma0 receives some command line parameters and
1787 F<hello.nulconfig> file is generated on the fly from the lines between
1788 C<<<EOF> and C<EOF>.
1789
1790 =head2 VARIABLES
1791
1792 The following variables are interpreted in the novaboot script:
1793
1794 =over 8
1795
1796 =item BUILDDIR
1797
1798 Novaboot chdir()s to this directory before file generation phase. The
1799 directory name specified here is relative to the build directory
1800 specified by other means (see L</--build-dir>).
1801
1802 =item EXITON
1803
1804 Assigning this variable has the same effect as specifying L</--exiton>
1805 option.
1806
1807 =item HYPERVISOR_PARAMS
1808
1809 Parameters passed to hypervisor. The default value is "serial", unless
1810 overridden in configuration file.
1811
1812 =item KERNEL
1813
1814 The kernel to use instead of the hypervisor specified in the
1815 configuration file with the C<$hypervisor> variable. The value should
1816 contain the name of the kernel image as well as its command line
1817 parameters. If this variable is defined and non-empty, the variable
1818 HYPERVISOR_PARAMS is not used.
1819
1820 =item NO_BOOT
1821
1822 If this variable is 1, the system is not booted. This is currently
1823 only implemented for U-Boot bootloader where it is useful for
1824 interacting with the bootloader without booting the system - e.g. for
1825 flashing.
1826
1827 =item QEMU
1828
1829 Use a specific qemu binary (can be overridden with B<-Q>) and flags
1830 when booting this script under qemu. If QEMU_FLAGS variable is also
1831 specified flags specified in QEMU variable are replaced by those in
1832 QEMU_FLAGS.
1833
1834 =item QEMU_FLAGS
1835
1836 Use specific qemu flags (can be overridden with B<-q>).
1837
1838 =item UBOOT_CMD
1839
1840 See L</--uboot-cmd>.
1841
1842 =item WVDESC
1843
1844 Description of the WvTest-compliant program.
1845
1846 =item WVTEST_TIMEOUT
1847
1848 The timeout in seconds for WvTest harness. If no complete line appears
1849 in the test output within the time specified here, the test fails. It
1850 is necessary to specify this for long running tests that produce no
1851 intermediate output.
1852
1853 =back
1854
1855 =head1 CONFIGURATION FILE
1856
1857 Novaboot can read its configuration from one or more files. By
1858 default, novaboot looks for files in F</etc/novaboot.d> and files
1859 named F<.novaboot> as described in L</Configuration reading phase>.
1860 Alternatively, configuration file location can be specified with the
1861 B<-c> switch or with the NOVABOOT_CONFIG environment variable. The
1862 configuration file has Perl syntax (i.e. it is better to put C<1;> as
1863 a last line) and should set values of certain Perl variables. The
1864 current configuration can be dumped with the B<--dump-config> switch.
1865 Some configuration variables can be overridden by environment
1866 variables (see below) or by command line switches.
1867
1868 Supported configuration variables include:
1869
1870 =over 8
1871
1872 =item $builddir
1873
1874 Build directory location relative to the location of the configuration
1875 file.
1876
1877 =item $default_target
1878
1879 Default target (see below) to use when no target is explicitly
1880 specified on command line with the B<--target> option.
1881
1882 =item %targets
1883
1884 Hash of target definitions to be used with the B<--target> option. The
1885 key is the identifier of the target, the value is the string with
1886 command line options. For instance, if the configuration file contains:
1887
1888  $targets{'mybox'} = '--server=boot:/tftproot --serial=/dev/ttyUSB0 --grub',
1889
1890 then the following two commands are equivalent:
1891
1892  ./myos --server=boot:/tftproot --serial=/dev/ttyUSB0 --grub
1893  ./myos -t mybox
1894
1895 =back
1896
1897 =head1 ENVIRONMENT VARIABLES
1898
1899 Some options can be specified not only via config file or command line
1900 but also through environment variables. Environment variables override
1901 the values from configuration file and command line parameters
1902 override the environment variables.
1903
1904 =over 8
1905
1906 =item NOVABOOT_CONFIG
1907
1908 Name of the novaboot configuration file to use instead of the default
1909 one(s).
1910
1911 =item NOVABOOT_CONFIG_DIR
1912
1913 Name of the novaboot configuration directory. When not specified
1914 F</etc/novaboot.d> is used.
1915
1916 =item NOVABOOT_BENDER
1917
1918 Defining this variable has the same meaning as B<--bender> option.
1919
1920 =back
1921
1922 =head1 AUTHORS
1923
1924 Michal Sojka <sojka@os.inf.tu-dresden.de>
1925
1926 =cut
1927
1928 # LocalWords:  novaboot Novaboot NOVABOOT TFTP PXE DHCP filename stty
1929 # LocalWords:  chainloader stdout Qemu qemu preprocessing ISOLINUX bootable
1930 # LocalWords:  config subprocesses sudo sudoers tftp dhcp IDE stdin
1931 # LocalWords:  subdirectories TTY whitespace heredoc POSIX WvTest