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