]> rtime.felk.cvut.cz Git - novaboot.git/blob - novaboot
5f4cfe3fdc299e911bcc49363702107f9821a6df
[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     } else {
1099         my @inputs = ($exp);
1100         if (-t STDIN) { # Set up bi-directional communication if we run on terminal
1101             my $infile = new IO::File;
1102             $infile->IO::File::fdopen(*STDIN,'r');
1103             my $in_object = Expect->exp_init($infile);
1104             $in_object->set_group($exp);
1105
1106             if ($interactive) {
1107                 $in_object->set_seq('~~\.', sub { print "novaboot: Escape sequence detected\r\n"; undef; });
1108                 $in_object->manual_stty(0);       # Use raw terminal mode
1109             } else {
1110                 $in_object->manual_stty(1);       # Do not modify terminal settings
1111             }
1112             push(@inputs, $in_object);
1113         }
1114         #use Data::Dumper;
1115         #print Dumper(\@expect_raw);
1116         $exp->expect(undef, @expect_raw) if @expect_raw;
1117         Expect::interconnect(@inputs) unless defined($exp->exitstatus);
1118     }
1119 }
1120
1121 ## Kill dhcpc or tftpd
1122 if (defined $dhcp_tftp || defined $tftp) {
1123     die("novaboot: This should kill servers on background\n");
1124 }
1125
1126 ## Documentation
1127
1128 =head1 NAME
1129
1130 novaboot - Boots a locally compiled operating system on a remote
1131 target or in qemu
1132
1133 =head1 SYNOPSIS
1134
1135 B<novaboot> --help
1136
1137 B<novaboot> [option]... [--] script...
1138
1139 B<./script> [option]...
1140
1141 =head1 DESCRIPTION
1142
1143 This program makes booting of a locally compiled operating system (OS)
1144 (e.g. NOVA or Linux) on remote targets as simple as running a program
1145 locally. It automates things like copying OS images to a TFTP server,
1146 generation of bootloader configuration files, resetting of target
1147 hardware or redirection of target's serial line to stdin/out. Novaboot
1148 is highly configurable and it makes it easy to boot a single image on
1149 different targets or different images on a single target.
1150
1151 Novaboot operation is controlled by command line options and by a so
1152 called novaboot script, which can be thought as a generalization of
1153 bootloader configuration files (see L</"NOVABOOT SCRIPT SYNTAX">).
1154 Typical way of using novaboot is to make the novaboot script
1155 executable and set its first line to I<#!/usr/bin/env novaboot>. Then,
1156 booting a particular OS configuration becomes the same as executing a
1157 local program - the novaboot script.
1158
1159 Novaboot uses configuration files to, among other things, define
1160 command line options needed for different targets. Users typically use
1161 only the B<-t>/B<--target> command line option to select the target.
1162 Internally, this option expands to the pre-configured options.
1163 Configuration files are searched at multiple places, which allows to
1164 have per-system, per-user or per-project configurations. Configuration
1165 file syntax is described in section L</"CONFIGURATION FILE">.
1166
1167 Simple examples of using C<novaboot>:
1168
1169 =over 3
1170
1171 =item 1.
1172
1173 Run an OS in Qemu. This is the default action when no other action is
1174 specified by command line switches. Thus running C<novaboot myos> (or
1175 C<./myos> as described above) will run Qemu and make it boot the
1176 configuration specified in the F<myos> script.
1177
1178 =item 2.
1179
1180 Create a bootloader configuration file (currently supported
1181 bootloaders are GRUB, GRUB2, ISOLINUX, Pulsar and U-Boot) and copy it
1182 with all other files needed for booting to a remote boot server. Then
1183 use a TCP/IP-controlled relay/serial-to-TCP converter to reset the
1184 target and receive its serial output.
1185
1186  ./myos --grub2 --server=192.168.1.1:/tftp --iprelay=192.168.1.2
1187
1188 =item 3.
1189
1190 Run DHCP and TFTP server on developer's machine to boot the target
1191 from it.
1192
1193  ./myos --dhcp-tftp
1194
1195 This is useful when no network infrastructure is in place and
1196 the target is connected directly to developer's box.
1197
1198 =item 4.
1199
1200 Create bootable ISO image.
1201
1202  novaboot --iso -- script1 script2
1203
1204 The created ISO image will have ISOLINUX bootloader installed on it
1205 and the boot menu will allow selecting between I<script1> and
1206 I<script2> configurations.
1207
1208 =back
1209
1210 =head1 PHASES AND OPTIONS
1211
1212 Novaboot performs its work in several phases. Each phase can be
1213 influenced by several command line options, certain phases can be
1214 skipped. The list of phases (in the execution order) and the
1215 corresponding options follows.
1216
1217 =head2 Configuration reading phase
1218
1219 After starting, novaboot reads configuration files. Their content is
1220 described in section L</"CONFIGURATION FILE">. By default,
1221 configuration is read from two locations. First from the configuration
1222 directory and second from F<.novaboot> files along the path to the
1223 current directory. The latter read files override settings from the
1224 former ones.
1225
1226 Configuration directory is determined by the content of
1227 NOVABOOT_CONFIG_DIR environment variable and defaults to
1228 F</etc/novaboot.d>. Files in this directory with names consisting
1229 solely of English letters, numbers, dashes '-' and underscores '_'
1230 (note that dot '.' is not included) are read in alphabetical order.
1231
1232 Then novaboot searches for files named F<.novaboot> starting from the
1233 directory of the novaboot script (or working directory, see bellow)
1234 and continuing upwards up to the root directory. The found
1235 configuration files are then read in the opposite order (i.e. from the
1236 root directory downwards). This allows to have, for example, user
1237 specific configuration in F<~/.novaboot> and project specific one in
1238 F<~/project/.novaboot>.
1239
1240 In certain cases, the location of the novaboot script cannot be
1241 determined in this early phase. This happens either when the script is
1242 read from the standard input or when novaboot is invoked explicitly as
1243 in the example L</"4."> above. In this case the current working
1244 directory is used as a starting point for configuration file search
1245 instead of the novaboot script directory.
1246
1247 =over 8
1248
1249 =item -c, --config=I<filename>
1250
1251 Use the specified configuration file instead of the default one(s).
1252
1253 =back
1254
1255 =head2 Command line processing phase
1256
1257 =over 8
1258
1259 =item --dump-config
1260
1261 Dump the current configuration to stdout end exit. Useful as an
1262 initial template for a configuration file.
1263
1264 =item -h, --help
1265
1266 Print short (B<-h>) or long (B<--help>) help.
1267
1268 =item -t, --target=I<target>
1269
1270 This option serves as a user configurable shortcut for other novaboot
1271 options. The effect of this option is the same as the options stored
1272 in the C<%targets> configuration variable under key I<target>. See
1273 also L</"CONFIGURATION FILE">.
1274
1275 =back
1276
1277 =head2 Script preprocessing phase
1278
1279 This phases allows to modify the parsed novaboot script before it is
1280 used in the later phases.
1281
1282 =over 8
1283
1284 =item -a, --append=I<parameters>
1285
1286 Append a string to the first C<load> line in the novaboot script. This
1287 can be used to append parameters to the kernel's or root task's
1288 command line. This option can appear multiple times.
1289
1290 =item -b, --bender
1291
1292 Use F<bender> chainloader. Bender scans the PCI bus for PCI serial
1293 ports and stores the information about them in the BIOS data area for
1294 use by the kernel.
1295
1296 =item --chainloader=I<chainloader>
1297
1298 Specifies a chainloader that is loaded before the kernel and other
1299 files specified in the novaboot script. E.g. 'bin/boot/bender
1300 promisc'.
1301
1302 =item --dump
1303
1304 Print the modules to boot and their parameters after this phase
1305 finishes. Then exit. This is useful for seeing the effect of other
1306 options in this section.
1307
1308 =item -k, --kernel=F<file>
1309
1310 Replace the first word on the first C<load> line in the novaboot
1311 script with F<file>.
1312
1313 =item --scriptmod=I<perl expression>
1314
1315 When novaboot script is read, I<perl expression> is executed for every
1316 line (in $_ variable). For example, C<novaboot
1317 --scriptmod=s/sigma0/omega6/g> replaces every occurrence of I<sigma0>
1318 in the script with I<omega6>.
1319
1320 When this option is present, it overrides I<$script_modifier> variable
1321 from the configuration file, which has the same effect. If this option
1322 is given multiple times all expressions are evaluated in the command
1323 line order.
1324
1325 =back
1326
1327 =head2 File generation phase
1328
1329 In this phase, files needed for booting are generated in a so called
1330 I<build directory> (see L</--build-dir>). In most cases configuration
1331 for a bootloader is generated automatically by novaboot. It is also
1332 possible to generate other files using I<heredoc> or I<"<"> syntax in
1333 novaboot scripts. Finally, binaries can be generated in this phases by
1334 running C<scons> or C<make>.
1335
1336 =over 8
1337
1338 =item --build-dir=I<directory>
1339
1340 Overrides the default build directory location.
1341
1342 The default build directory location is determined as follows: If the
1343 configuration file defines the C<$builddir> variable, its value is
1344 used. Otherwise, it is the directory that contains the first processed
1345 novaboot script.
1346
1347 See also L</BUILDDIR> variable.
1348
1349 =item -g, --grub[=I<filename>]
1350
1351 Generates grub bootloader menu file. If the I<filename> is not
1352 specified, F<menu.lst> is used. The I<filename> is relative to the
1353 build directory (see B<--build-dir>).
1354
1355 =item --grub-preamble=I<prefix>
1356
1357 Specifies the I<preable> that is at the beginning of the generated
1358 GRUB or GRUB2 config files. This is useful for specifying GRUB's
1359 timeout.
1360
1361 =item --prefix=I<prefix>
1362
1363 Specifies I<prefix> (e.g. F</srv/tftp>) that is put in front of every
1364 file name in generated bootloader configuration files (or in U-Boot
1365 commands).
1366
1367 If the I<prefix> contains string $NAME, it will be replaced with the
1368 name of the novaboot script (see also B<--name>).
1369
1370 If the I<prefix> contains string $BUILDDIR, it will be replaced with
1371 the build directory (see also B<--build-dir>).
1372
1373 =item --grub-prefix
1374
1375 Alias for B<--prefix>.
1376
1377 =item --grub2[=I<filename>]
1378
1379 Generate GRUB2 menu entry in I<filename>. If I<filename> is not
1380 specified F<grub.cfg> is used. The content of the menu entry can be
1381 customized with B<--grub-preamble>, B<--grub2-prolog> or
1382 B<--grub_prefix> options.
1383
1384 In order to use the the generated menu entry on your development
1385 machine that uses GRUB2, append the following snippet to
1386 F</etc/grub.d/40_custom> file and regenerate your grub configuration,
1387 i.e. run update-grub on Debian/Ubuntu.
1388
1389   if [ -f /path/to/nul/build/grub.cfg ]; then
1390     source /path/to/nul/build/grub.cfg
1391   fi
1392
1393 =item --grub2-prolog=I<prolog>
1394
1395 Specifies text that is put at the beginning of the GRUB2 menu entry.
1396
1397 =item -m, --make[=make command]
1398
1399 Runs C<make> to build files that are not generated by novaboot itself.
1400
1401 =item --name=I<string>
1402
1403 Use the name I<string> instead of the name of the novaboot script.
1404 This name is used for things like a title of grub menu or for the
1405 server directory where the boot files are copied to.
1406
1407 =item --no-file-gen
1408
1409 Do not run external commands to generate files (i.e. "<" syntax and
1410 C<run> keyword). This switch does not influence generation of files
1411 specified with "<<WORD" syntax.
1412
1413 =item -p, --pulsar[=mac]
1414
1415 Generates pulsar bootloader configuration file named F<config-I<mac>>
1416 The I<mac> string is typically a MAC address and defaults to
1417 I<novaboot>.
1418
1419 =item --scons[=scons command]
1420
1421 Runs C<scons> to build files that are not generated by novaboot
1422 itself.
1423
1424 =item --strip-rom
1425
1426 Strip I<rom://> prefix from command lines and generated config files.
1427 The I<rom://> prefix is used by NUL. For NRE, it has to be stripped.
1428
1429 =item --gen-only
1430
1431 Exit novaboot after file generation phase.
1432
1433 =back
1434
1435 =head2 Target connection check
1436
1437 If supported by the target, the connection to it is made and it is
1438 checked whether the target is not occupied by another novaboot
1439 user/instance.
1440
1441 =over 8
1442
1443 =item --amt=I<"[user[:password]@]host[:port]>
1444
1445 Use Intel AMT technology to control the target machine. WS management
1446 is used to powercycle it and Serial-Over-Lan (SOL) for input/output.
1447 The hostname or (IP address) is given by the I<host> parameter. If
1448 I<password> is not specified, environment variable AMT_PASSWORD is
1449 used. The I<port> specifies a TCP port for SOL. If not specified, the
1450 default is 16992. Default I<user> is admin.
1451
1452 =item --iprelay=I<addr[:port]>
1453
1454 Use TCP/IP relay and serial port to access the target's serial port
1455 and powercycle it. The IP address of the relay is given by I<addr>
1456 parameter. If I<port> is not specified, it default to 23.
1457
1458 Note: This option is supposed to work with HWG-ER02a IP relays.
1459
1460 =item -s, --serial[=device]
1461
1462 Target's serial line is connected to host's serial line (device). The
1463 default value for device is F</dev/ttyUSB0>.
1464
1465 The value of this option is exported in NB_NOVABOOT environment
1466 variable to all subprocesses run by C<novaboot>.
1467
1468 =item --stty=I<settings>
1469
1470 Specifies settings passed to C<stty> invoked on the serial line
1471 specified with B<--serial> option. If this option is not given,
1472 C<stty> is called with C<raw -crtscts -onlcr 115200> settings.
1473
1474 =item --remote-cmd=I<cmd>
1475
1476 Command that mediates connection to the target's serial line. For
1477 example C<ssh server 'cu -l /dev/ttyS0'>.
1478
1479 =item --remote-expect=I<string>
1480
1481 Wait for reception of I<string> after establishing the the remote
1482 connection before continuing.
1483
1484
1485 =back
1486
1487 =head2 File deployment phase
1488
1489 In some setups, it is necessary to copy the files needed for booting
1490 to a particular location, e.g. to a TFTP boot server or to the
1491 F</boot> partition.
1492
1493 =over 8
1494
1495 =item -d, --dhcp-tftp
1496
1497 Turns your workstation into a DHCP and TFTP server so that the OS can
1498 be booted via PXE BIOS (or similar mechanism) on the test machine
1499 directly connected by a plain Ethernet cable to your workstation.
1500
1501 The DHCP and TFTP servers requires root privileges and C<novaboot>
1502 uses C<sudo> command to obtain those. You can put the following to
1503 I</etc/sudoers> to allow running the necessary commands without asking
1504 for password.
1505
1506  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
1507  your_login ALL=NOPASSWD: NOVABOOT
1508
1509 =item --tftp
1510
1511 Starts a TFTP server on your workstation. This is similar to
1512 B<--dhcp-tftp> except that DHCP server is not started.
1513
1514 The TFTP server require root privileges and C<novaboot> uses C<sudo>
1515 command to obtain those. You can put the following to I</etc/sudoers>
1516 to allow running the necessary commands without asking for password.
1517
1518  Cmnd_Alias NOVABOOT =  /usr/sbin/in.tftpd --listen --secure -v -v -v --pidfile tftpd.pid *, /usr/bin/pkill --pidfile=tftpd.pid
1519  your_login ALL=NOPASSWD: NOVABOOT
1520
1521 =item --tftp-port=I<port>
1522
1523 Port to run the TFTP server on. Implies B<--tftp>.
1524
1525 =item --iso[=filename]
1526
1527 Generates the ISO image that boots NOVA system via GRUB. If no filename
1528 is given, the image is stored under I<NAME>.iso, where I<NAME> is the name
1529 of the novaboot script (see also B<--name>).
1530
1531 =item --server[=[[user@]server:]path]
1532
1533 Copy all files needed for booting to another location. The files will
1534 be copied (by B<rsync> tool) to the directory I<path>. If the I<path>
1535 contains string $NAME, it will be replaced with the name of the
1536 novaboot script (see also B<--name>).
1537
1538 =item --rsync-flags=I<flags>
1539
1540 Specifies which I<flags> are appended to F<rsync> command line when
1541 copying files as a result of I<--server> option.
1542
1543 =item --concat
1544
1545 If B<--server> is used and its value ends with $NAME, then after
1546 copying the files, a new bootloader configuration file (e.g. menu.lst)
1547 is created at I<path-wo-name>, i.e. the path specified by B<--server>
1548 with $NAME part removed. The content of the file is created by
1549 concatenating all files of the same name from all subdirectories of
1550 I<path-wo-name> found on the "server".
1551
1552 =item --ider
1553
1554 Use Intel AMT technology for IDE redirection. This allows the target
1555 machine to boot from novaboot created ISO image. Implies B<--iso>.
1556
1557 The experimental C<amtider> utility needed by this option can be
1558 obtained from https://github.com/wentasah/amtterm.
1559
1560 =back
1561
1562 =head2 Target power-on and reset phase
1563
1564 At this point, the target is reset (or switched on/off). There is
1565 several ways how this can be accomplished. Resetting a physical target
1566 can currently be accomplished by the following options: B<--amt>,
1567 B<--iprelay>, B<--reset-cmd>.
1568
1569 =over 8
1570
1571 =item --on, --off
1572
1573 Switch on/off the target machine and exit. The script (if any) is
1574 completely ignored. Currently it works only with B<--iprelay> or
1575 B<--amt>.
1576
1577 =item -Q, --qemu[=I<qemu-binary>]
1578
1579 Boot the configuration in qemu. Optionally, the name of qemu binary
1580 can be specified as a parameter.
1581
1582 =item --qemu-append=I<flags>
1583
1584 Append I<flags> to the default qemu flags (QEMU_FLAGS variable or
1585 C<-cpu coreduo -smp 2>).
1586
1587 =item -q, --qemu-flags=I<flags>
1588
1589 Replace the default qemu flags (QEMU_FLAGS variable or C<-cpu coreduo
1590 -smp 2>) with I<flags> specified here.
1591
1592 =item --reset-cmd=I<cmd>
1593
1594 Command that resets the target.
1595
1596 =item --no-reset, --reset
1597
1598 Disable/enable resetting of the target.
1599
1600 =back
1601
1602 =head2 Interaction with the bootloader on the target
1603
1604 =over 8
1605
1606 =item --uboot[=I<prompt>]
1607
1608 Interact with U-Boot bootloader to boot the thing described in the
1609 novaboot script. I<prompt> specifies the U-Boot's prompt (default is
1610 "=> ", other common prompts are "U-Boot> " or "U-Boot# ").
1611 Implementation of this option is currently tied to a particular board
1612 that we use. It may be subject to changes in the future!
1613
1614 =item --uboot-init
1615
1616 Command(s) to send the U-Boot bootloader before loading the images and
1617 booting them. This option can be given multiple times. After sending
1618 commands from each option novaboot waits for U-Boot I<prompt>.
1619
1620 If the command contains string I<$NB_MYIP> then this string is
1621 replaced by IPv4 address of eth0 interface. Similarly I<$NB_PREFIX> is
1622 replaced with prefix given by B<--prefix>.
1623
1624 See also C<uboot> keyword in L</"NOVABOOT SCRIPT SYNTAX">).
1625
1626 =item --uboot-addr I<name>=I<address>
1627
1628 Load address of U-Boot's C<tftpboot> command for loading I<name>,
1629 where name is one of I<kernel>, I<ramdisk> or I<fdt> (flattened device
1630 tree).
1631
1632 The default addresses are ${I<name>_addr_r}, i.e. U-Boot environment
1633 variables used by convention for this purpose.
1634
1635 =item --uboot-cmd=I<command>
1636
1637 Specifies U-Boot command used to execute the OS. If the command
1638 contains strings C<$kernel_addr>, C<$ramdisk_addr>, C<$fdt_addr>,
1639 these are replaced with the addresses configured with B<--uboot-addr>.
1640
1641 The default value is
1642
1643     bootm $kernel_addr $ramdisk_addr $fdt_addr
1644
1645 or the C<UBOOT_CMD> variable if defined in the novaboot script.
1646
1647 =back
1648
1649 =head2 Target interaction phase
1650
1651 In this phase, target's serial output is redirected to stdout and if
1652 stdin is a TTY, it is redirected to the target's serial input allowing
1653 interactive work with the target.
1654
1655 =over 8
1656
1657 =item --exiton=I<string>
1658
1659 When I<string> is sent by the target, novaboot exits. This option can
1660 be specified multiple times, in which case novaboot exits whenever
1661 either of the specified strings is sent.
1662
1663 If I<string> is C<-re>, then the next B<--exiton>'s I<string> is
1664 treated as regular expression. For example:
1665
1666     --exiton -re --exiton 'error:.*failed'
1667
1668 =item --exiton-re=I<regex>
1669
1670 The same as --exiton -re --exiton I<regex>.
1671
1672 =item --exiton-timeout=I<seconds>
1673
1674 By default B<--exiton> waits for the string match forever. When this
1675 option is specified, "exiton" timeouts after the specifies number of
1676 seconds and novaboot returns non-zero exit code.
1677
1678 =item -i, --interactive
1679
1680 Setup things for interactive use of target. Your terminal will be
1681 switched to raw mode. In raw mode, your system does not process input
1682 in any way (no echoing of entered characters, no interpretation
1683 special characters). This, among others, means that Ctrl-C is passed
1684 to the target and does no longer interrupt novaboot. Use "~~."
1685 sequence to exit novaboot.
1686
1687 =item --expect=I<string>
1688
1689 When I<string> is received from the target, send the string specified
1690 with the subsequent B<--send*> option to the target.
1691
1692 =item --expect-re=I<regex>
1693
1694 When target's output matches regular expression I<regex>, send the
1695 string specified with the subsequent B<--send*> option to the target.
1696
1697 =item --expect-raw=I<perl-code>
1698
1699 Provides direct control over Perl's Expect module.
1700
1701 =item --send=I<string>
1702
1703 Send I<string> to the target after the previously specified
1704 B<--expect*> was matched in the target's output. The I<string> may
1705 contain escape sequences such as "\n".
1706
1707 Note that I<string> is actually interpreted by Perl, so it can contain
1708 much more that escape sequences. This behavior may change in the
1709 future.
1710
1711 Example: C<--expect='login: ' --send='root\n'>
1712
1713 =item --sendcont=I<string>
1714
1715 Similar to B<--send> but continue expecting more input.
1716
1717 Example: C<--expect='Continue?' --sendcont='yes\n'>
1718
1719 =back
1720
1721 =head1 NOVABOOT SCRIPT SYNTAX
1722
1723 The syntax tries to mimic POSIX shell syntax. The syntax is defined
1724 with the following rules.
1725
1726 Lines starting with "#" and empty lines are ignored.
1727
1728 Lines that end with "\" are concatenated with the following line after
1729 removal of the final "\" and leading whitespace of the following line.
1730
1731 Lines of the form I<VARIABLE=...> (i.e. matching '^[A-Z_]+=' regular
1732 expression) assign values to internal variables. See L</VARIABLES>
1733 section.
1734
1735 Lines starting with C<load> keyword represent modules to boot. The
1736 word after C<load> is a file name (relative to the build directory
1737 (see B<--build-dir>) of the module to load and the remaining words are
1738 passed to it as the command line parameters.
1739
1740 When the C<load> line ends with "<<WORD" then the subsequent lines
1741 until the line containing solely WORD are copied literally to the file
1742 named on that line. This is similar to shell's heredoc feature.
1743
1744 When the C<load> line ends with "< CMD" then command CMD is executed
1745 with F</bin/sh> and its standard output is stored in the file named on
1746 that line. The SRCDIR variable in CMD's environment is set to the
1747 absolute path of the directory containing the interpreted novaboot
1748 script.
1749
1750 Lines starting with C<run> keyword contain shell commands that are run
1751 during file generation phase. This is the same as the "< CMD" syntax
1752 for C<load> keyboard except that the command's output is not
1753 redirected to a file. The ordering of commands is the same as they
1754 appear in the novaboot script.
1755
1756 Lines starting with C<uboot> represent U-Boot commands that are sent
1757 to the target if B<--uboot> option is given. Having a U-Boot line in
1758 the novaboot script is the same as passing an equivalent
1759 B<--uboot-init> option to novaboot. The C<uboot> keyword can be
1760 suffixed with timeout specification. The syntax is C<uboot:Ns>, where
1761 C<N> is the whole number of seconds. If the U-Boot command prompt does
1762 not appear before the timeout, novaboot fails. The default timeout is
1763 10 seconds.
1764
1765 Example (Linux):
1766
1767   #!/usr/bin/env novaboot
1768   load bzImage console=ttyS0,115200
1769   run  make -C buildroot
1770   load rootfs.cpio < gen_cpio buildroot/images/rootfs.cpio "myapp->/etc/init.d/S99myapp"
1771
1772 Example (NOVA User Land - NUL):
1773
1774   #!/usr/bin/env novaboot
1775   WVDESC=Example program
1776   load bin/apps/sigma0.nul S0_DEFAULT script_start:1,1 \
1777                            verbose hostkeyb:0,0x60,1,12,2
1778   load bin/apps/hello.nul
1779   load hello.nulconfig <<EOF
1780   sigma0::mem:16 name::/s0/log name::/s0/timer name::/s0/fs/rom ||
1781   rom://bin/apps/hello.nul
1782   EOF
1783
1784 This example will load three modules: F<sigma0.nul>, F<hello.nul> and
1785 F<hello.nulconfig>. sigma0 receives some command line parameters and
1786 F<hello.nulconfig> file is generated on the fly from the lines between
1787 C<<<EOF> and C<EOF>.
1788
1789 =head2 VARIABLES
1790
1791 The following variables are interpreted in the novaboot script:
1792
1793 =over 8
1794
1795 =item BUILDDIR
1796
1797 Novaboot chdir()s to this directory before file generation phase. The
1798 directory name specified here is relative to the build directory
1799 specified by other means (see L</--build-dir>).
1800
1801 =item EXITON
1802
1803 Assigning this variable has the same effect as specifying L</--exiton>
1804 option.
1805
1806 =item HYPERVISOR_PARAMS
1807
1808 Parameters passed to hypervisor. The default value is "serial", unless
1809 overridden in configuration file.
1810
1811 =item KERNEL
1812
1813 The kernel to use instead of the hypervisor specified in the
1814 configuration file with the C<$hypervisor> variable. The value should
1815 contain the name of the kernel image as well as its command line
1816 parameters. If this variable is defined and non-empty, the variable
1817 HYPERVISOR_PARAMS is not used.
1818
1819 =item NO_BOOT
1820
1821 If this variable is 1, the system is not booted. This is currently
1822 only implemented for U-Boot bootloader where it is useful for
1823 interacting with the bootloader without booting the system - e.g. for
1824 flashing.
1825
1826 =item QEMU
1827
1828 Use a specific qemu binary (can be overridden with B<-Q>) and flags
1829 when booting this script under qemu. If QEMU_FLAGS variable is also
1830 specified flags specified in QEMU variable are replaced by those in
1831 QEMU_FLAGS.
1832
1833 =item QEMU_FLAGS
1834
1835 Use specific qemu flags (can be overridden with B<-q>).
1836
1837 =item UBOOT_CMD
1838
1839 See L</--uboot-cmd>.
1840
1841 =item WVDESC
1842
1843 Description of the WvTest-compliant program.
1844
1845 =item WVTEST_TIMEOUT
1846
1847 The timeout in seconds for WvTest harness. If no complete line appears
1848 in the test output within the time specified here, the test fails. It
1849 is necessary to specify this for long running tests that produce no
1850 intermediate output.
1851
1852 =back
1853
1854 =head1 CONFIGURATION FILE
1855
1856 Novaboot can read its configuration from one or more files. By
1857 default, novaboot looks for files in F</etc/novaboot.d> and files
1858 named F<.novaboot> as described in L</Configuration reading phase>.
1859 Alternatively, configuration file location can be specified with the
1860 B<-c> switch or with the NOVABOOT_CONFIG environment variable. The
1861 configuration file has Perl syntax (i.e. it is better to put C<1;> as
1862 a last line) and should set values of certain Perl variables. The
1863 current configuration can be dumped with the B<--dump-config> switch.
1864 Some configuration variables can be overridden by environment
1865 variables (see below) or by command line switches.
1866
1867 Supported configuration variables include:
1868
1869 =over 8
1870
1871 =item $builddir
1872
1873 Build directory location relative to the location of the configuration
1874 file.
1875
1876 =item $default_target
1877
1878 Default target (see below) to use when no target is explicitly
1879 specified on command line with the B<--target> option.
1880
1881 =item %targets
1882
1883 Hash of target definitions to be used with the B<--target> option. The
1884 key is the identifier of the target, the value is the string with
1885 command line options. For instance, if the configuration file contains:
1886
1887  $targets{'mybox'} = '--server=boot:/tftproot --serial=/dev/ttyUSB0 --grub',
1888
1889 then the following two commands are equivalent:
1890
1891  ./myos --server=boot:/tftproot --serial=/dev/ttyUSB0 --grub
1892  ./myos -t mybox
1893
1894 =back
1895
1896 =head1 ENVIRONMENT VARIABLES
1897
1898 Some options can be specified not only via config file or command line
1899 but also through environment variables. Environment variables override
1900 the values from configuration file and command line parameters
1901 override the environment variables.
1902
1903 =over 8
1904
1905 =item NOVABOOT_CONFIG
1906
1907 Name of the novaboot configuration file to use instead of the default
1908 one(s).
1909
1910 =item NOVABOOT_CONFIG_DIR
1911
1912 Name of the novaboot configuration directory. When not specified
1913 F</etc/novaboot.d> is used.
1914
1915 =item NOVABOOT_BENDER
1916
1917 Defining this variable has the same meaning as B<--bender> option.
1918
1919 =back
1920
1921 =head1 AUTHORS
1922
1923 Michal Sojka <sojka@os.inf.tu-dresden.de>
1924
1925 =cut
1926
1927 # LocalWords:  novaboot Novaboot NOVABOOT TFTP PXE DHCP filename stty
1928 # LocalWords:  chainloader stdout Qemu qemu preprocessing ISOLINUX bootable
1929 # LocalWords:  config subprocesses sudo sudoers tftp dhcp IDE stdin
1930 # LocalWords:  subdirectories TTY whitespace heredoc POSIX WvTest