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