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