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