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