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.
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.
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/>.
20 use warnings (exists $ENV{NOVABOOT_TEST} ? (FATAL => 'all') : ());
21 use Getopt::Long qw(GetOptionsFromString GetOptionsFromArray);
26 use Time::HiRes("usleep");
30 use POSIX qw(:errno_h sysconf);
31 use Cwd qw(getcwd abs_path);
37 my $invocation_dir = $ENV{PWD} || getcwd();
39 ## Configuration file handling
41 # Default configuration
42 $CFG::hypervisor = "";
43 $CFG::hypervisor_params = "serial";
44 $CFG::genisoimage = "genisoimage";
45 $CFG::qemu = 'qemu-system-i386 -cpu coreduo -smp 2';
46 $CFG::default_target = '';
49 "tud" => '--server=erwin.inf.tu-dresden.de:~sojka/boot/novaboot --rsync-flags="--chmod=Dg+s,ug+w,o-w,+rX --rsync-path=\"umask 002 && rsync\"" --grub --grub-prefix=(nd)/tftpboot/sojka/novaboot --grub-preamble="timeout 0" --concat --iprelay=141.76.48.80:2324 --scriptmod=s/\\\\bhostserial\\\\b/hostserialpci/g',
50 "novabox" => '--server=novabox@rtime.felk.cvut.cz:/srv/tftp/novaboot --rsync-flags="--chmod=Dg+s,ug+w,o-w,+rX --rsync-path=\"umask 002 && rsync\"" --pulsar --iprelay-cmd="ssh -tt novabox@rtime.felk.cvut.cz nc localhost 2324"',
51 "localhost" => '--scriptmod=s/console=tty[A-Z0-9,]+// --server=/boot/novaboot/$NAME --grub2 --grub-prefix=/boot/novaboot/$NAME --grub2-prolog=" set root=\'(hd0,msdos1)\'"',
52 "ryu" => '--uboot --uboot-init="mw f0000b00 \${psc_cfg}; sleep 1" --uboot-addr kernel=800000 --uboot-addr ramdisk=b00000 --uboot-addr fdt=7f0000',
53 "ryuglab" => '--target ryu --server=pc-sojkam.felk.cvut.cz:/srv/tftp --remote-cmd="ssh -tt pc-sojkam.felk.cvut.cz \"sterm -d -s 115200 /dev/ttyUSB0\""',
54 "ryulocal" => '--target ryu --dhcp-tftp --serial --reset-cmd="if which dtrrts; then dtrrts $NB_SERIAL 0 1; sleep 0.1; dtrrts $NB_SERIAL 1 1; fi"',
59 $const{linux}->{_SC_NPROCESSORS_CONF} = 83;
60 my $nproc = sysconf($const{$^O}->{_SC_NPROCESSORS_CONF});
62 $CFG::scons = "scons -j$nproc";
63 $CFG::make = "make -j$nproc";
71 package CFG; # Put config data into a separate namespace
77 die("ERROR: Failure compiling '$cfg' - $@");
78 } elsif (! defined($rc)) {
79 die("ERROR: Failure reading '$cfg' - $!");
81 die("ERROR: Failure processing '$cfg'");
84 $builddir = File::Spec->rel2abs($CFG::builddir, dirname($cfg)) if defined $CFG::builddir;
85 print STDERR "novaboot: Read $cfg\n";
90 # We don't use $0 here, because it points to the novaboot itself and
91 # not to the novaboot script. The problem with this approach is that
92 # when a script is run as "novaboot <options> <script>" then $ARGV[0]
93 # contains the first option. Hence the -f check.
94 my $dir = File::Spec->rel2abs($ARGV[0] && -f $ARGV[0] ? dirname($ARGV[0]) : '', $invocation_dir);
95 while ((-d $dir || -l $dir ) && $dir ne "/") {
96 push @cfgs, "$dir/.novaboot" if -r "$dir/.novaboot";
97 my @dirs = File::Spec->splitdir($dir);
98 $dir = File::Spec->catdir(@dirs[0..$#dirs-1]);
100 @cfgs = reverse @cfgs;
102 my $xdg_config_home = $ENV{'XDG_CONFIG_HOME'} || $ENV{'HOME'}.'/.config';
103 unshift(@cfgs, "$xdg_config_home/novaboot") if -r "$xdg_config_home/novaboot";
105 $dir = $ENV{'NOVABOOT_CONFIG_DIR'} || '/etc/novaboot.d';
106 if (opendir(my $dh, $dir)) {
107 my @etccfg = map { "$dir/$_" } grep { /^[-_a-zA-Z0-9]+$/ && -f "$dir/$_" } readdir($dh);
109 @etccfg = sort(@etccfg);
110 @cfgs = ( @etccfg, @cfgs );
113 my $cfg = $ENV{'NOVABOOT_CONFIG'};
114 Getopt::Long::Configure(qw/no_ignore_case pass_through/);
115 GetOptions ("config|c=s" => \$cfg);
116 read_config($_) foreach $cfg or @cfgs;
118 ## Command line handling
120 my $explicit_target = $ENV{'NOVABOOT_TARGET'};
121 GetOptions ("target|t=s" => \$explicit_target);
123 # Variables for command line options
124 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);
126 my ($target_reset, $target_power_on, $target_power_off);
128 # Default values of certain command line options
130 'kernel' => '${kernel_addr_r}',
131 'ramdisk' => '${ramdisk_addr_r}',
132 'fdt' => '${fdt_addr_r}',
135 $rom_prefix = 'rom://';
136 $stty = 'raw -crtscts -onlcr 115200';
137 $reset = 1; # Reset target by default
138 $interaction = 1; # Perform target interaction by default
141 $remote_expect_timeout = 180;
143 my @expect_seen = ();
147 push(@expect_seen, '-re') if $n eq "expect-re";
148 push(@expect_seen, $v);
154 unless (@expect_seen) { die("No --expect before --send"); }
155 my $ret = ($n eq "sendcont") ? exp_continue : 0;
156 unshift(@expect_raw, sub { shift->send(eval("\"$v\"")); $ret; });
157 unshift(@expect_raw, @expect_seen);
161 # Options which can be safely specified on the server (via --ssh),
162 # i.e. which cannot cause unwanted local code execution etc.
163 my %opt_spec_safe = (
164 "grub|g:s" => \$grub_config,
165 "grub-preamble=s"=> \$grub_preamble,
166 "grub2-prolog=s" => \$grub2_prolog,
167 "grub2:s" => \$grub2_config,
168 "prefix|grub-prefix=s" => \$grub_prefix,
169 "pulsar-root=s" => \$pulsar_root,
170 "pulsar|p:s" => \$pulsar,
171 "uboot-addr=s" => \%uboot_addr,
172 "uboot-cmd=s" => \$uboot_cmd,
173 "uboot-init=s" => sub { push @uboot_init, { command => $_[1] }; },
174 "uboot:s" => \$uboot,
180 "append|a=s" => \@append,
181 "bender|b" => \$bender,
182 "build-dir=s" => sub { my ($n, $v) = @_; $builddir = File::Spec->rel2abs($v); },
183 "concat" => \$concat,
184 "chainloader=s" => \@chainloaders,
185 "dhcp-tftp|d" => \$dhcp_tftp,
186 "dump" => \$dump_opt,
187 "dump-config" => \$dump_config,
188 "exiton=s" => \@exiton,
189 "exiton-timeout=i"=> \$exiton_timeout,
190 "exiton-re=s" => sub { my ($n, $v) = @_; push(@exiton, '-re', $v); },
191 "expect=s" => \&handle_expect,
192 "expect-re=s" => \&handle_expect,
193 "expect-raw=s" => sub { my ($n, $v) = @_; unshift(@expect_raw, eval($v)); },
194 "final-eol!" => \$final_eol,
195 "gen-only" => \$gen_only,
197 "interaction!" => \$interaction,
198 "iprelay=s" => \$iprelay,
199 "iprelay-cmd=s" => \$iprelay_cmd,
200 "iso:s" => \$iso_image,
201 "kernel|k=s" => \$kernel_opt,
202 "interactive|i" => \$interactive,
203 "name=s" => \$config_name_opt,
204 "make|m:s" => \$make,
205 "netif=s" => \$netif,
206 "no-file-gen" => \$no_file_gen,
209 "qemu|Q:s" => \$qemu,
210 "qemu-append=s" => \$qemu_append,
211 "qemu-flags|q=s" => \$qemu_flags_cmd,
212 "remote-cmd=s" => \$remote_cmd,
213 "remote-expect=s"=> \$remote_expect,
214 "remote-expect-silent=s"=> sub { $remote_expect=$_[1]; $remote_expect_silent=1; },
215 "remote-expect-timeout=i"=> \$remote_expect_timeout,
217 "reset-cmd=s" => \$reset_cmd,
218 "reset-send=s" => \$reset_send,
219 "rsync-flags=s" => \$rsync_flags,
220 "scons:s" => \$scons,
221 "scriptmod=s" => \@scriptmod,
222 "send=s" => \&handle_send,
223 "sendcont=s" => \&handle_send,
224 "serial|s:s" => \$serial,
225 "server:s" => \$server,
226 "ssh:s" => \&handle_novaboot_server,
227 "strip-rom" => sub { $rom_prefix = ''; },
230 "tftp-port=i" => \$tftp_port,
231 "no-uboot" => sub { undef $uboot; },
236 sub handle_novaboot_server
239 my $xdg_runtime_dir = $ENV{XDG_RUNTIME_DIR} || '/var/run';
240 my $ssh_ctl_path = "${xdg_runtime_dir}/novaboot-%r@%h%p";
242 $remote_cmd = "ssh -tt -M -S '${ssh_ctl_path}' '${val}' console";
243 $remote_expect = "novaboot-shell: Connected";
245 $rsync_flags = "--rsh='ssh -S \'${ssh_ctl_path}\''";
246 ($grub_prefix = $val) =~ s|(.*)@.*|\/$1\/| if index($val, '@') != -1;
247 $reset_cmd = "ssh -tt -S '${ssh_ctl_path}' '${val}' reset";
249 $target_power_off = sub { system_verbose("ssh -tt -S '${ssh_ctl_path}' '${val}' off"); };
250 $target_power_on = sub { system_verbose("ssh -tt -S '${ssh_ctl_path}' '${val}' on"); };
252 my $cmd = "ssh '${val}' get-config";
253 print STDERR "novaboot: Running: $cmd\n";
254 my @target_config = qx($cmd < /dev/null);
255 if ($?) { die("Cannot get target configuration from the server"); }
256 printf "novaboot: Received configuration from the server:%s\n", (!@target_config) ? " empty" : "";
257 foreach (@target_config) { chomp; print " $_\n"; }
259 GetOptionsFromArray(\@target_config, %opt_spec_safe) or die("Error processing configuration from the server");
260 if (scalar @target_config) { die "Unsuported configuration received from the server: ".join(", ", @target_config); }
263 # First process target options
265 my $t = defined($explicit_target) ? $explicit_target : $CFG::default_target;
267 Getopt::Long::Configure(qw/no_ignore_case pass_through/);
269 exists $CFG::targets{$t} or die("Unknown target '$t' (valid targets are: ".join(", ", sort keys(%CFG::targets)).")");
271 undef $explicit_target;
272 my ($ret, $remaining_args) = GetOptionsFromString ($CFG::targets{$t}, ("target|t=s" => \$explicit_target));
273 if (!$ret) { die "Error parsing target $t option"; }
274 push(@target_expanded, @$remaining_args);
275 $t = $explicit_target;
278 my @args = (@target_expanded, @ARGV);
279 print STDERR "novaboot: Effective options: @args\n";
281 Getopt::Long::Configure(qw/no_ignore_case no_pass_through/);
282 GetOptionsFromArray(\@target_expanded, %opt_spec) or die ("Error in target definition");
285 # Then process other command line options - some of them may override
286 # what was specified by the target
287 GetOptions %opt_spec or die("Error in command line arguments");
288 pod2usage(1) if $help;
289 pod2usage(-exitstatus => 0, -verbose => 2) if $man;
291 ### Dump sanitized configuration (if requested)
295 $Data::Dumper::Indent=1;
296 print "# This file is in perl syntax.\n";
297 foreach my $key(sort(keys(%CFG::))) { # See "Symbol Tables" in perlmod(1)
298 if (defined ${$CFG::{$key}}) { print Data::Dumper->Dump([${$CFG::{$key}}], ["*$key"]); }
299 if ( @{$CFG::{$key}}) { print Data::Dumper->Dump([\@{$CFG::{$key}}], ["*$key"]); }
300 if ( %{$CFG::{$key}}) { print Data::Dumper->Dump([\%{$CFG::{$key}}], ["*$key"]); }
306 ### Sanitize configuration
308 if ($interactive && !-t STDIN) {
309 die("novaboot: Interactive mode not supported when not on terminal");
312 if (defined $config_name_opt && scalar(@ARGV) > 1) { die "You cannot use --name with multiple scripts"; }
315 $iso_image //= ''; # IDE-R needs an ISO image
316 if (!defined $amt) { die "Error: --ider requires --amt"; }
320 my %input_opts = ('--iprelay' => \$iprelay,
321 '--iprelay-cmd'=> \$iprelay_cmd,
322 '--serial' => \$serial,
323 '--remote-cmd' => \$remote_cmd,
325 my @opts = grep(defined(${$input_opts{$_}}) , keys %input_opts);
327 die("novaboot: More than one target connection option: ".join(', ', @opts)) if scalar @opts > 1;
331 if (defined $serial) {
332 $serial ||= "/dev/ttyUSB0";
333 $ENV{NB_SERIAL} = $serial;
335 if (defined $grub_config) { $grub_config ||= "menu.lst"; }
336 if (defined $grub2_config) { $grub2_config ||= "grub.cfg"; }
338 ## Parse the novaboot script(s)
343 my ($modules, $variables, $generated, $copy, $chainload, $continuation) = ([], {}, [], []);
344 my $skip_reading = defined($on_opt) || defined($off_opt);
345 while (!$skip_reading && ($_ = <>)) {
346 if ($ARGV ne $last_fn) { # New script
347 die "Missing EOF in $last_fn" if $file;
348 die "Unfinished line in $last_fn" if $continuation;
350 push @scripts, { 'filename' => $ARGV,
351 'modules' => $modules = [],
352 'variables' => $variables = {},
353 'generated' => $generated = [],
354 'copy' => $copy = [],
355 'chainload' => $chainload = [],
360 next if /^#/ || /^\s*$/; # Skip comments and empty lines
362 $_ =~ s/^[[:space:]]*// if ($continuation);
364 if (/\\$/) { # Line continuation
365 $continuation .= substr($_, 0, length($_)-1);
369 if ($continuation) { # Last continuation line
370 $_ = $continuation . $_;
374 foreach my $mod(@scriptmod) { eval $mod; }
376 if ($file && $_ eq $EOF) { # Heredoc end
380 if ($file) { # Heredoc content
381 push @{$file}, "$_\n";
384 if (/^([A-Z_]+)=(.*)$/) { # Internal variable
385 $$variables{$1} = $2;
386 push(@exiton, $2) if ($1 eq "EXITON");
389 sub process_load_copy($) {
390 die("novaboot: '$last_fn' line $.: Missing file name\n") unless /^[^ <]+/;
391 if (/^([^ ]*)(.*?)[[:space:]]*<<([^ ]*)$/) { # Heredoc start
393 push @$generated, {filename => $1, content => $file};
397 if (/^([^ ]*)(.*?)[[:space:]]*< ?(.*)$/) { # Command substitution
398 push @$generated, {filename => $1, command => $3};
403 if (s/^load *//) { # Load line
404 push @$modules, process_load_copy($_);
407 if (s/^copy *//) { # Copy line
408 push @$copy, process_load_copy($_);
411 if (s/^chld *//) { # Chainload line
412 push @$chainload, process_load_copy($_);
415 if (/^run (.*)/) { # run line
416 push @$generated, {command => $1};
419 if (/^uboot(?::([0-9]+)s)? +(< *)?(.*)/) { # uboot line
420 # TODO: If U-Boot supports some interactive menu, it might
421 # make sense to store uboot lines per novaboot script.
422 my ($timeout, $redir, $string, $dest) = ($1, $2, $3);
423 if ($string =~ /(.*) *> *(.*)/) {
427 push @uboot_init, { command => $redir ? "" : $string,
428 system => $redir ? $string : "",
435 die("novaboot: Cannot parse script '$last_fn' line $.. Didn't you forget 'load' keyword?\n");
438 # print Dumper(\@scripts);
440 foreach my $script (@scripts) {
441 $modules = $$script{modules};
442 @$modules[0] =~ s/^[^ ]*/$kernel_opt/ if $kernel_opt;
443 @$modules[0] .= ' ' . join(' ', @append) if @append;
446 if (exists $variables->{KERNEL}) {
447 $kernel = $variables->{KERNEL};
449 if ($CFG::hypervisor) {
450 $kernel = $CFG::hypervisor . " ";
451 if (exists $variables->{HYPERVISOR_PARAMS}) {
452 $kernel .= $variables->{HYPERVISOR_PARAMS};
454 $kernel .= $CFG::hypervisor_params;
458 @$modules = ($kernel, @$modules) if $kernel;
459 @$modules = (@chainloaders, @$modules);
460 @$modules = ("bin/boot/bender", @$modules) if ($bender || defined $ENV{'NOVABOOT_BENDER'});
464 foreach my $script (@scripts) {
465 print join("\n", @{$$script{modules}})."\n";
472 sub generate_configs($$$) {
473 my ($base, $generated, $filename) = @_;
474 if ($base) { $base = "$base/"; };
475 foreach my $g(@$generated) {
476 if (exists $$g{content}) {
477 my $config = $$g{content};
478 my $fn = $$g{filename};
479 open(my $f, '>', $fn) || die("$fn: $!");
480 map { s|\brom://([^ ]*)|$rom_prefix$base$1|g; print $f "$_"; } @{$config};
482 print STDERR "novaboot: Created $fn\n";
483 } elsif (exists $$g{command} && ! $no_file_gen) {
484 $ENV{SRCDIR} = dirname(File::Spec->rel2abs( $filename, $invocation_dir ));
485 if (exists $$g{filename}) {
486 system_verbose("( $$g{command} ) > $$g{filename}");
488 system_verbose($$g{command});
494 sub generate_grub_config($$$$;$)
496 my ($filename, $title, $base, $modules_ref, $preamble) = @_;
497 if ($base) { $base = "$base/"; };
498 open(my $fg, '>', $filename) or die "$filename: $!";
499 print $fg "$preamble\n" if $preamble;
500 print $fg "title $title\n" if $title;
501 #print $fg "root $base\n"; # root doesn't really work for (nd)
503 foreach (@$modules_ref) {
506 my ($kbin, $kcmd) = split(' ', $_, 2);
507 $kcmd = '' if !defined $kcmd;
508 print $fg "kernel ${base}$kbin $kcmd\n";
510 s|\brom://([^ ]*)|$rom_prefix$base$1|g; # Translate rom:// files - needed for vdisk parameter of sigma0
511 print $fg "module $base$_\n";
515 print("novaboot: Created $builddir/$filename\n");
519 sub generate_syslinux_config($$$$)
521 my ($filename, $title, $base, $modules_ref) = @_;
522 if ($base && $base !~ /\/$/) { $base = "$base/"; };
523 open(my $fg, '>', $filename) or die "$filename: $!";
524 print $fg "LABEL $title\n";
525 #TODO print $fg "MENU LABEL $human_readable_title\n";
527 my ($kbin, $kcmd) = split(' ', @$modules_ref[0], 2);
529 if (system("file $kbin|grep 'Linux kernel'") == 0) {
530 my $initrd = @$modules_ref[1];
531 die('To many "load" lines for Linux kernel') if (scalar @$modules_ref > 2);
532 print $fg "LINUX $base$kbin\n";
533 print $fg "APPEND $kcmd\n";
534 print $fg "INITRD $base$initrd\n";
536 print $fg "KERNEL mboot.c32\n";
538 foreach (@$modules_ref) {
539 s|\brom://([^ ]*)|$rom_prefix$base$1|g; # Translate rom:// files - needed for vdisk parameter of sigma0
540 push @append, "$base$_";
541 print $fg "APPEND ".join(' --- ', @append)."\n";
544 #TODO print $fg "TEXT HELP\n";
545 #TODO print $fg "some help here\n";
546 #TODO print $fg "ENDTEXT\n";
548 print("novaboot: Created $builddir/$filename\n");
552 sub generate_grub2_config($$$$;$$)
554 my ($filename, $title, $base, $modules_ref, $preamble, $prolog) = @_;
555 if ($base && substr($base,-1,1) ne '/') { $base = "$base/"; };
556 open(my $fg, '>', $filename) or die "$filename: $!";
557 print $fg "$preamble\n" if $preamble;
558 $title ||= 'novaboot';
559 print $fg "menuentry $title {\n";
560 print $fg "$prolog\n" if $prolog;
562 foreach (@$modules_ref) {
565 my ($kbin, $kcmd) = split(' ', $_, 2);
566 $kcmd = '' if !defined $kcmd;
567 print $fg " multiboot ${base}$kbin $kcmd\n";
570 # GRUB2 doesn't pass filename in multiboot info so we have to duplicate it here
571 $_ = join(' ', ($args[0], @args));
572 s|\brom://|$rom_prefix|g; # We do not need to translate path for GRUB2
573 print $fg " module $base$_\n";
578 print("novaboot: Created $builddir/$filename\n");
582 sub generate_pulsar_config($$$)
584 my ($filename, $modules_ref, $chainload_ref) = @_;
585 open(my $fg, '>', $filename) or die "$filename: $!";
586 print $fg "root $pulsar_root\n" if defined $pulsar_root;
587 if (scalar(@$chainload_ref) > 0) {
588 print $fg "chld $$chainload_ref[0]\n";
592 foreach (@$modules_ref) {
595 ($kbin, $kcmd) = split(' ', $_, 2);
596 $kcmd = '' if !defined $kcmd;
599 s|\brom://|$rom_prefix|g;
600 print $fg "load $_\n";
603 # Put kernel as last - this is needed for booting Linux and has no influence on non-Linux OSes
604 print $fg "exec $kbin $kcmd\n";
607 print("novaboot: Created $builddir/$filename\n");
611 sub shell_cmd_string(@)
613 return join(' ', map((/^[-_=a-zA-Z0-9\/\.\+]+$/ ? "$_" : "'$_'"), @_));
618 print STDERR "novaboot: Running: ".shell_cmd_string(@_)."\n";
620 exit(1); # should not be reached
623 sub system_verbose($)
626 print STDERR "novaboot: Running: $cmd\n";
627 my $ret = system($cmd);
628 if ($ret & 0x007f) { die("Command terminated by a signal"); }
629 if ($ret & 0xff00) {die("Command exit with non-zero exit code"); }
630 if ($ret) { die("Command failure $ret"); }
635 $str =~ s/^\s+|\s+$//g;
641 if (exists $variables->{WVDESC}) {
642 print STDERR "Testing \"$variables->{WVDESC}\" in $last_fn:\n";
643 } elsif ($last_fn =~ /\.wv$/) {
644 print STDERR "Testing \"all\" in $last_fn:\n";
647 ## Connect to the target and check whether it is not occupied
649 # We have to do this before file generation phase, because file
650 # generation is intermixed with file deployment phase and we want to
651 # check whether the target is not used by somebody else before
652 # deploying files. Otherwise, we may rewrite other user's files on a
655 my $exp; # Expect object to communicate with the target over serial line
657 my ($amt_user, $amt_password, $amt_host, $amt_port);
659 if (defined $iprelay || defined $iprelay_cmd) {
660 if (defined $iprelay) {
662 $iprelay =~ /([.0-9]+)(:([0-9]+))?/;
665 my $paddr = sockaddr_in($port, inet_aton($addr));
666 my $proto = getprotobyname('tcp');
667 socket($IPRELAY, PF_INET, SOCK_STREAM, $proto) || die "socket: $!";
668 print STDERR "novaboot: Connecting to IP relay... ";
669 connect($IPRELAY, $paddr) || die "connect: $!";
670 print STDERR "done\n";
671 $exp = Expect->init(\*$IPRELAY);
674 if (defined $iprelay_cmd) {
675 print STDERR "novaboot: Running: $iprelay_cmd\n";
678 $exp->spawn($iprelay_cmd);
682 print $exp "\xFF\xF6"; # AYT
683 my $connected = $exp->expect(20, # Timeout in seconds
684 '<iprelayd: connected>',
685 '-re', '<WEB51 HW[^>]*>')
686 || die "iprelay connection: " . ($! || "timeout");
691 my ($relay, $onoff) = @_;
692 die unless ($relay == 1 || $relay == 2);
694 my $cmd = ($relay == 1 ? 0x5 : 0x6) | ($onoff ? 0x20 : 0x10);
695 return "\xFF\xFA\x2C\x32".chr($cmd)."\xFF\xF0";
699 my ($relay, $onoff) = @_;
700 die unless ($relay == 1 || $relay == 2);
701 my $cmd = ($relay == 1 ? 0xdf : 0xbf) | ($onoff ? 0x00 : 0xff);
702 return "\xFF\xFA\x2C\x97".chr($cmd)."\xFF\xF0";
706 my ($relay, $onoff, $can_giveup) = @_;
707 my $confirmation = '';
709 print $exp relaycmd($relay, $onoff);
710 my $confirmed = $exp->expect(20, # Timeout in seconds
711 relayconf($relay, $onoff))
712 || die "iprelay command: " . ($! || "timeout");
715 print("Relay confirmation timeout - ignoring\n");
717 die "Relay confirmation timeout";
723 $target_reset = sub {
724 relay(2, 1, 1); # Reset the machine
729 $target_power_off = sub {
730 relay(1, 1); # Press power button
731 usleep(6000000); # Long press to switch off
735 $target_power_on = sub {
736 relay(1, 1); # Press power button
737 usleep(100000); # Short press
743 system_verbose("stty -F $serial $stty");
744 open($CONN, "+<", $serial) || die "open $serial: $!";
745 $exp = Expect->init(\*$CONN);
747 elsif ($remote_cmd) {
748 print STDERR "novaboot: Running: $remote_cmd\n";
749 $exp = Expect->spawn($remote_cmd);
751 elsif (defined $amt) {
752 require LWP::UserAgent;
753 require LWP::Authen::Digest;
756 my ($host, $username, $password, $schema, $className, $pstate) = @_;
757 #AMT numbers for PowerStateChange (MNI => bluescreen on windows;-)
758 my %pstates = ("on" => 2,
765 <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">
766 <s:Header><a:To>http://$host:16992/wsman</a:To>
767 <w:ResourceURI s:mustUnderstand="true">$schema</w:ResourceURI>
768 <a:ReplyTo><a:Address s:mustUnderstand="true">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address></a:ReplyTo>
769 <a:Action s:mustUnderstand="true">$schema$className</a:Action>
770 <w:MaxEnvelopeSize s:mustUnderstand="true">153600</w:MaxEnvelopeSize>
771 <a:MessageID>uuid:709072C9-609C-4B43-B301-075004043C7C</a:MessageID>
772 <w:Locale xml:lang="en-US" s:mustUnderstand="false" />
773 <w:OperationTimeout>PT60.000S</w:OperationTimeout>
774 <w:SelectorSet><w:Selector Name="Name">Intel(r) AMT Power Management Service</w:Selector></w:SelectorSet>
776 <p:RequestPowerStateChange_INPUT xmlns:p="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_PowerManagementService">
777 <p:PowerState>$pstates{$pstate}</p:PowerState>
778 <p:ManagedElement><a:Address>http://$host:16992/wsman</a:Address>
779 <a:ReferenceParameters><w:ResourceURI>http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ComputerSystem</w:ResourceURI>
780 <w:SelectorSet><w:Selector Name="Name">ManagedSystem</w:Selector></w:SelectorSet>
781 </a:ReferenceParameters></p:ManagedElement>
782 </p:RequestPowerStateChange_INPUT>
783 </s:Body></s:Envelope>
788 my ($host, $username, $password, $content) = @_;
790 my $ua = LWP::UserAgent->new();
791 $ua->agent("novaboot");
793 my $req = HTTP::Request->new(POST => "http://$host:16992/wsman");
794 my $res = $ua->request($req);
795 die ("Unexpected AMT response: " . $res->status_line) unless $res->code == 401;
797 my ($realm) = $res->header("WWW-Authenticate") =~ /Digest realm="(.*?)"/;
798 $ua->credentials("$host:16992", $realm, $username => $password);
801 $req = HTTP::Request->new(POST => "http://$host:16992/wsman");
802 $req->content_type('application/x-www-form-urlencoded');
803 $req->content($content);
804 $res = $ua->request($req);
805 die ("AMT power change request failed: " . $res->status_line) unless $res->is_success;
806 $res->content() =~ /<g:ReturnValue>(\d+)<\/g:ReturnValue>/;
811 my ($host, $username, $password, $pstate)=@_;
812 my $schema="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_PowerManagementService";
813 my $className="/RequestPowerStateChange";
814 my $content = genXML($host, $username, $password ,$schema, $className, $pstate);
815 return sendPOST($host, $username, $password, $content);
818 ($amt_user,$amt_password,$amt_host,$amt_port) = ($amt =~ /(?:(.*?)(?::(.*))?@)?([^:]*)(?::([0-9]*))?/);;
819 $amt_user ||= "admin";
820 $amt_password ||= $ENV{'AMT_PASSWORD'} || die "AMT password not specified";
821 $amt_host || die "AMT host not specified";
825 $target_power_off = sub {
827 my $result = powerChange($amt_host,$amt_user,$amt_password, "off");
828 die "AMT power off failed (ReturnValue $result)" if $result != 0;
831 $target_power_on = sub {
832 my $result = powerChange($amt_host,$amt_user,$amt_password, "on");
833 die "AMT power on failed (ReturnValue $result)" if $result != 0;
836 $target_reset = sub {
837 my $result = powerChange($amt_host,$amt_user,$amt_password, "reset");
839 print STDERR "Warning: Cannot reset $amt_host, trying power on. ";
840 $result = powerChange($amt_host,$amt_user,$amt_password, "on");
842 die "AMT reset failed (ReturnValue $result)" if $result != 0;
845 my $cmd = "amtterm -u $amt_user -p $amt_password $amt_host $amt_port";
846 print STDERR "novaboot: Running: $cmd\n" =~ s/\Q$amt_password\E/???/r;
847 $exp = Expect->spawn($cmd);
848 $exp->expect(10, "RUN_SOL") || die "Expect for 'RUN_SOL': " . ($! || "timeout");
852 if ($remote_expect) {
853 $exp || die("No serial line connection");
854 my $log = $exp->log_stdout;
855 if (defined $remote_expect_silent) {
858 $exp->expect($remote_expect_timeout >= 0 ? $remote_expect_timeout : undef,
859 $remote_expect) || die "Expect for '$remote_expect':" . ($! || "timeout");;
860 if (defined $remote_expect_silent) {
861 $exp->log_stdout($log);
862 print $exp->after() if $log;
866 if (defined $reset_cmd) {
867 $target_reset = sub {
868 system_verbose($reset_cmd);
872 if (defined $reset_send) {
873 $target_reset = sub {
874 $reset_send =~ s/\\n/\n/g;
875 $exp->send($reset_send);
879 if (defined $on_opt && defined $target_power_on) {
883 if (defined $off_opt && defined $target_power_off) {
884 print STDERR "novaboot: Switching the target off...\n";
885 &$target_power_off();
889 $builddir ||= dirname(File::Spec->rel2abs( ${$scripts[0]}{filename})) if scalar @scripts;
890 if (defined $builddir) {
891 chdir($builddir) or die "Can't change directory to $builddir: $!";
892 print STDERR "novaboot: Entering directory `$builddir'\n";
894 $builddir = $invocation_dir;
897 ## File generation phase
898 my (%files_iso, $menu_iso, $filename);
899 my $config_name = '';
902 foreach my $script (@scripts) {
903 $filename = $$script{filename};
904 $modules = $$script{modules};
905 $generated = $$script{generated};
906 $variables = $$script{variables};
908 ($config_name = $filename) =~ s#.*/##;
909 $config_name = $config_name_opt if (defined $config_name_opt);
911 if (exists $variables->{BUILDDIR}) {
912 $builddir = File::Spec->rel2abs($variables->{BUILDDIR});
913 chdir($builddir) or die "Can't change directory to $builddir: $!";
914 print STDERR "novaboot: Entering directory `$builddir'\n";
918 $prefix = $grub_prefix;
919 $prefix =~ s/\$NAME/$config_name/;
920 $prefix =~ s/\$BUILDDIR/$builddir/;
922 # TODO: use $grub_prefix as first parameter if some switch is given
923 generate_configs('', $generated, $filename);
925 ### Generate bootloader configuration files
926 my @bootloader_configs;
927 push @bootloader_configs, generate_grub_config($grub_config, $config_name, $prefix, $modules, $grub_preamble) if (defined $grub_config);
928 push @bootloader_configs, generate_grub2_config($grub2_config, $config_name, $prefix, $modules, $grub_preamble, $grub2_prolog) if (defined $grub2_config);
929 push @bootloader_configs, generate_pulsar_config('config-'.($pulsar||'novaboot'), $modules, $chainload) if (defined $pulsar);
931 ### Run scons or make
934 push @all, @$modules;
936 push @all, @$chainload;
937 my @files = map({ ($file) = m/([^ ]*)/; $file; } @all);
939 # Filter-out generated files
940 my @to_build = grep({ my $file = $_; !scalar(grep($file eq ($$_{filename} || ''), @$generated)) } @files);
942 system_verbose($scons || $CFG::scons." ".join(" ", @to_build)) if (defined $scons);
943 system_verbose($make || $CFG::make ." ".join(" ", @to_build)) if (defined $make);
946 ### Copy files (using rsync)
947 if (defined $server && !defined($gen_only)) {
948 (my $real_server = $server) =~ s/\$NAME/$config_name/;
950 my ($hostname, $path) = split(":", $real_server, 2);
951 if (! defined $path) {
955 my $files = join(" ", map({ ($file) = m/([^ ]*)/; $file; } ( @$modules, @bootloader_configs, @$copy)));
956 map({ my $file = (split)[0]; die "$file: $!" if ! -f $file; } @$modules);
957 my $istty = -t STDOUT && ($ENV{'TERM'} || 'dumb') ne 'dumb';
958 my $progress = $istty ? "--progress" : "";
960 system_verbose("rsync $progress -RLp $rsync_flags $files $real_server");
961 if ($server =~ m|/\$NAME$| && $concat) {
962 my $cmd = join("; ", map { "( cd $path/.. && cat */$_ > $_ )" } @bootloader_configs);
963 system_verbose($hostname ? "ssh $hostname '$cmd'" : $cmd);
968 ### Prepare ISO image generation
969 if (defined $iso_image) {
970 generate_configs("(cd)", $generated, $filename);
972 generate_syslinux_config(\$menu, $config_name, "/", $modules);
973 $menu_iso .= "$menu\n";
974 map { ($file,undef) = split; $files_iso{$file} = 1; } @$modules;
978 ## Generate ISO image
979 if (defined $iso_image) {
980 system_verbose("mkdir -p isolinux");
983 if (-f '/usr/lib/ISOLINUX/isolinux.bin') {
984 # Newer ISOLINUX version
985 @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);
987 # Older ISOLINUX version
988 @files = qw(/usr/lib/syslinux/isolinux.bin /usr/lib/syslinux/mboot.c32 /usr/lib/syslinux/menu.c32);
990 system_verbose("cp @files isolinux");
991 open(my $fh, ">isolinux/isolinux.cfg");
993 print $fh "TIMEOUT 50\n";
994 print $fh "DEFAULT menu\n";
996 print $fh "DEFAULT $config_name\n";
998 print $fh "$menu_iso";
1001 my $files = join(" ", map("$_=$_", (keys(%files_iso), 'isolinux/isolinux.cfg', map(s|.*/|isolinux/|r, @files))));
1002 $iso_image ||= "$config_name.iso";
1004 # Note: We use -U flag below to "Allow 'untranslated' filenames,
1005 # completely violating the ISO9660 standards". Without this
1006 # option, isolinux is not able to read files names for example
1008 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");
1009 print("ISO image created: $builddir/$iso_image\n");
1012 exit(0) if defined $gen_only;
1014 ## Boot the system using various methods and send serial output to stdout
1016 if (scalar(@scripts) > 1 && ( defined $dhcp_tftp || defined $serial || defined $iprelay)) {
1017 die "You cannot do this with multiple scripts simultaneously";
1020 if ($variables->{WVTEST_TIMEOUT}) {
1021 print STDERR "wvtest: timeout ", $variables->{WVTEST_TIMEOUT}, "\n";
1026 if (defined $qemu) {
1028 $qemu ||= $variables->{QEMU} || $CFG::qemu;
1029 my @qemu_flags = split(" ", $qemu);
1030 $qemu = shift(@qemu_flags);
1032 @qemu_flags = split(/ +/, trim($variables->{QEMU_FLAGS})) if exists $variables->{QEMU_FLAGS};
1033 @qemu_flags = split(/ +/, trim($qemu_flags_cmd)) if $qemu_flags_cmd;
1034 push(@qemu_flags, split(/ +/, trim($qemu_append || '')));
1036 if (defined $iso_image) {
1037 # Boot NOVA with grub (and test the iso image)
1038 push(@qemu_flags, ('-cdrom', $iso_image));
1040 # Boot NOVA without GRUB
1042 # Non-patched qemu doesn't like commas, but NUL can live with pluses instead of commans
1043 foreach (@$modules) {s/,/+/g;}
1044 generate_configs("", $generated, $filename);
1046 if (scalar @$modules) {
1047 my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);
1048 $kcmd = '' if !defined $kcmd;
1050 @$modules = map { if (/\.dtb$/) { $dtb=$_; (); } else { $_ } } @$modules;
1051 my $initrd = join ",", @$modules;
1053 push(@qemu_flags, ('-kernel', $kbin, '-append', $kcmd));
1054 push(@qemu_flags, ('-initrd', $initrd)) if $initrd;
1055 push(@qemu_flags, ('-dtb', $dtb)) if $dtb;
1058 if (!grep /^-serial$/, @qemu_flags) {
1059 push(@qemu_flags, qw(-serial stdio)); # Redirect serial output (for collecting test restuls)
1061 unshift(@qemu_flags, ('-name', $config_name));
1062 print STDERR "novaboot: Running: ".shell_cmd_string($qemu, @qemu_flags)."\n";
1063 $exp = Expect->spawn(($qemu, @qemu_flags)) || die("exec() failed: $!");
1066 ### Local DHCPD and TFTPD
1068 my ($dhcpd_pid, $tftpd_pid);
1070 $tftp=1 if $tftp_port;
1072 if (defined $dhcp_tftp)
1074 generate_configs("(nd)", $generated, $filename);
1075 system_verbose('mkdir -p tftpboot');
1076 generate_grub_config("tftpboot/os-menu.lst", $config_name, "(nd)", \@$modules, "timeout 0");
1077 open(my $fh, '>', 'dhcpd.conf');
1078 my $mac = `cat /sys/class/net/$netif/address`;
1080 print $fh "subnet 10.23.23.0 netmask 255.255.255.0 {
1081 range 10.23.23.10 10.23.23.100;
1082 filename \"bin/boot/grub/pxegrub.pxe\";
1083 next-server 10.23.23.1;
1086 hardware ethernet $mac;
1087 fixed-address 10.23.23.1;
1090 system_verbose("sudo ip a add 10.23.23.1/24 dev $netif;
1091 sudo ip l set dev $netif up;
1092 sudo touch dhcpd.leases");
1094 # We run servers by forking ourselves, because the servers end up
1095 # in our process group and get killed by signals sent to the
1096 # process group (e.g. Ctrl-C on terminal).
1097 $dhcpd_pid = fork();
1098 exec_verbose("sudo dhcpd -d -cf dhcpd.conf -lf dhcpd.leases -pf dhcpd.pid") if ($dhcpd_pid == 0);
1101 if (defined $dhcp_tftp || defined $tftp) {
1103 # Unfortunately, tftpd requires root privileges even with
1104 # non-privileged (>1023) port due to initgroups().
1105 system_verbose("sudo in.tftpd --listen --secure -v -v -v --pidfile tftpd.pid --address :$tftp_port $builddir");
1107 # Kill server when we die
1108 $SIG{__DIE__} = sub { system_verbose('sudo pkill --pidfile=dhcpd.pid') if (defined $dhcp_tftp);
1109 system_verbose('sudo pkill --pidfile=tftpd.pid'); };
1111 # We have to kill tftpd explicitely, because it is not in our process group
1112 $SIG{INT} = sub { system_verbose('sudo pkill --pidfile=tftpd.pid'); exit(0); };
1116 if (defined $ider) {
1117 my $ider_cmd= "amtider -c $iso_image -u $amt_user -p $amt_password $amt_host $amt_port" ;
1118 print STDERR "novaboot: Running: $ider_cmd\n" =~ s/\Q$amt_password\E/???/r;
1119 my $ider_pid = fork();
1120 if ($ider_pid == 0) {
1122 die "IDE redirection failed";
1124 # FIXME: This collides with --tftp option. Hopefully, nobody needs
1125 # to use both simultaneously.
1126 $SIG{__DIE__} = sub { system_verbose('kill $ider_pid'); };
1129 ### Reset target (IP relay, AMT, ...)
1131 if (defined $target_reset && $reset) {
1132 print STDERR "novaboot: Reseting the test box... ";
1134 print STDERR "done\n";
1136 # We don't want to output anything printed by the target
1137 # before reset so we clear the buffers now. This is, however,
1138 # not ideal because we may loose some data that were sent
1139 # after the reset. If this is a problem, one should reset and
1140 # connect to serial line in atomic manner. For example, if
1141 # supported by hardware, use --remote-cmd 'sterm -d ...' and
1142 # do not use separate --reset-cmd.
1143 my $log = $exp->log_stdout;
1144 $exp->log_stdout(0);
1145 $exp->expect(0); # Read data from target
1146 $exp->clear_accum(); # Clear the read data
1147 $exp->log_stdout($log);
1151 ### U-boot conversation
1152 if (defined $uboot) {
1153 my $uboot_prompt = $uboot || '=> ';
1154 print STDERR "novaboot: Waiting for U-Boot prompt...\n";
1155 $exp || die("No serial line connection");
1156 $exp->log_stdout(1);
1157 #$exp->exp_internal(1);
1159 [qr/Hit any key to stop autoboot:/, sub { $exp->send("\n"); exp_continue; }],
1160 $uboot_prompt) || die "No U-Boot prompt deteceted";
1161 foreach my $cmdspec (@uboot_init) {
1162 my ($cmd, $timeout);
1163 die "Internal error - please report a bug" unless ref($cmdspec) eq "HASH";
1165 if ($cmdspec->{system}) {
1166 $cmd = `$cmdspec->{system}`;
1168 $cmd = $cmdspec->{command};
1170 $timeout = $cmdspec->{timeout} // 10;
1172 if ($cmd =~ /\$NB_MYIP/) {
1173 my $ip = (grep /inet /, `ip addr show $netif`)[0] || die "Problem determining IP address of $netif";
1174 $ip =~ s/\s*inet ([0-9.]*).*/$1/;
1175 $cmd =~ s/\$NB_MYIP/$ip/g;
1177 if ($cmd =~ /\$NB_PREFIX/) {
1180 $cmd =~ s/\$NB_PREFIX/$p/g;
1183 $exp->send("$cmd\n");
1185 my ($matched_pattern_position, $error,
1186 $successfully_matching_string,
1187 $before_match, $after_match) =
1188 $exp->expect($timeout, $uboot_prompt);
1189 die "No U-Boot prompt: $error" if $error;
1191 if ($cmdspec->{dest}) {
1192 open(my $fh, ">", $cmdspec->{dest}) or die "Cannot open '$cmdspec->{dest}': $!";
1193 print $fh $before_match;
1198 # Load files if there are some load lines in the script
1199 if (scalar(@$modules) > 0 && !$variables->{NO_BOOT}) {
1200 my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);
1202 @$modules = map { if (/\.dtb$/) { $dtb=$_; (); } else { $_ } } @$modules;
1203 my $initrd = shift @$modules;
1205 if (defined $kbin) {
1206 die "No '--uboot-addr kernel' given" unless $uboot_addr{kernel};
1207 $exp->send("tftpboot $uboot_addr{kernel} $prefix$kbin\n");
1209 [qr/##/, sub { exp_continue; }],
1210 $uboot_prompt) || die "Kernel load: " . ($! || "timeout");
1213 die "No '--uboot-addr fdt' given" unless $uboot_addr{fdt};
1214 $exp->send("tftpboot $uboot_addr{fdt} $prefix$dtb\n");
1216 [qr/##/, sub { exp_continue; }],
1217 $uboot_prompt) || die "Device tree load: " . ($! || "timeout");
1219 $uboot_addr{fdt} = '';
1221 if (defined $initrd) {
1222 die "No '--uboot-addr ramdisk' given" unless $uboot_addr{ramdisk};
1223 $exp->send("tftpboot $uboot_addr{ramdisk} $prefix$initrd\n");
1225 [qr/##/, sub { exp_continue; }],
1226 $uboot_prompt) || die "Initrd load: " . ($! || "timeout");
1228 $uboot_addr{ramdisk} = '-';
1232 $exp->send("setenv bootargs $kcmd\n");
1233 $exp->expect(5, $uboot_prompt) || die "U-Boot prompt: " . ($! || "timeout");
1236 $uboot_cmd //= $variables->{UBOOT_CMD} // 'bootm $kernel_addr $ramdisk_addr $fdt_addr';
1237 if (!$variables->{NO_BOOT} && $uboot_cmd ne '') {
1238 $uboot_cmd =~ s/\$kernel_addr/$uboot_addr{kernel}/g;
1239 $uboot_cmd =~ s/\$ramdisk_addr/$uboot_addr{ramdisk}/g;
1240 $uboot_cmd =~ s/\$fdt_addr/$uboot_addr{fdt}/g;
1242 $exp->send($uboot_cmd . "\n");
1243 $exp->expect(5, "\n") || die "U-Boot command: " . ($! || "timeout");
1247 ### Serial line interaction
1248 if ($interaction && defined $exp) {
1249 # Serial line of the target is available
1250 my $interrupt = 'Ctrl-C';
1251 if ($interactive && !@exiton) {
1252 $interrupt = '"~~."';
1254 print STDERR "novaboot: Serial line interaction (press $interrupt to interrupt)...\n";
1255 $exp->log_stdout(1);
1257 $exp->expect($exiton_timeout, @expect_raw, @exiton) || die("exiton: " . ($! || "timeout"));
1259 my @inputs = ($exp);
1260 my $infile = new IO::File;
1261 $infile->IO::File::fdopen(*STDIN,'r');
1262 my $in_object = Expect->exp_init($infile);
1263 $in_object->set_group($exp);
1266 $in_object->set_seq('~~\.', sub { print STDERR "novaboot: Escape sequence detected\r\n"; undef; });
1267 $in_object->manual_stty(0); # Use raw terminal mode
1269 $in_object->manual_stty(1); # Do not modify terminal settings
1271 push(@inputs, $in_object);
1273 #print Dumper(\@expect_raw);
1274 $exp->expect(undef, @expect_raw) if @expect_raw;
1276 $^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.
1277 Expect::interconnect(@inputs) unless defined($exp->exitstatus);
1282 ## Kill dhcpc or tftpd
1283 if (defined $dhcp_tftp || defined $tftp) {
1284 die("novaboot: This should kill servers on background\n");
1287 # Always finish novaboot output with newline
1288 print "\n" if $final_eol;
1295 novaboot - Boots a locally compiled operating system on a remote
1302 B<novaboot> [option]... [--] script...
1304 B<./script> [option]...
1308 Novaboot makes booting of a locally compiled operating system (OS)
1309 (e.g. NOVA or Linux) on remote targets as simple as running a program
1310 locally. It automates things like copying OS images to a TFTP server,
1311 generation of bootloader configuration files, resetting of target
1312 hardware or redirection of target's serial line to stdin/out. Novaboot
1313 is highly configurable and makes it easy to boot a single image on
1314 different targets or different images on a single target.
1316 Novaboot operation is controlled by configuration files, command line
1317 options and by a so-called novaboot script, which can be thought as a
1318 generalization of bootloader configuration files (see L</"NOVABOOT
1319 SCRIPT SYNTAX">). The typical way of using novaboot is to make the
1320 novaboot script executable and set its first line to I<#!/usr/bin/env
1321 novaboot>. Then, booting a particular OS configuration becomes the
1322 same as executing a local program – the novaboot script.
1324 Novaboot uses configuration files to, among other things, define
1325 command line options needed for different targets. Users typically use
1326 only the B<-t>/B<--target> command line option to select the target.
1327 Internally, this option expands to the pre-configured options.
1328 Novaboot searches configuration files at multiple places, which allows
1329 having per-system, per-user or per-project configurations.
1330 Configuration file syntax is described in section L</"CONFIGURATION
1333 Simple examples of using C<novaboot>:
1339 Running an OS in Qemu can be accomplished by giving the B<--qemu> option.
1342 novaboot --qemu myos
1344 (or C<./myos --qemu> as described above) will run Qemu and make it
1345 boot the configuration specified in the F<myos> script.
1349 Create a bootloader configuration file (currently supported
1350 bootloaders are GRUB, GRUB2, ISOLINUX, Pulsar, and U-Boot) and copy it
1351 with all other files needed for booting to a remote boot server. Then
1352 use a TCP/IP-controlled relay/serial-to-TCP converter to reset the
1353 target and receive its serial output.
1355 ./myos --grub2 --server=192.168.1.1:/tftp --iprelay=192.168.1.2
1357 Alternatively, you can put these switches to the configuration file
1360 ./myos --target mytarget
1364 Run DHCP and TFTP server on developer's machine to boot the target
1369 This usage is useful when no network infrastructure is in place, and
1370 the target is connected directly to developer's box.
1374 Create bootable ISO image.
1376 novaboot --iso -- script1 script2
1378 The created ISO image will have ISOLINUX bootloader installed on it,
1379 and the boot menu will allow selecting between I<script1> and
1380 I<script2> configurations.
1384 =head1 PHASES AND OPTIONS
1386 Novaboot performs its work in several phases. Command like options
1387 described bellow influence the execution of each phase or allow their
1388 skipping. The list of phases (in the execution order) is as follows.
1392 =item 1. L<Configuration reading|/Configuration reading phase>
1394 =item 2. L<Command line processing|/Command line processing phase>
1396 =item 3. L<Script preprocessing|/Script preprocessing phase>
1398 =item 4. L<File generation|/File generation phase>
1400 =item 5. L<Target connection|/Target connection check>
1402 =item 6. L<File deployment|/File deployment phase>
1404 =item 7. L<Target power-on and reset|/Target power-on and reset phase>
1406 =item 8. L<Interaction with the bootloader|/Interaction with the bootloader on the target>
1408 =item 9. L<Target interaction|/Target interaction phase>
1412 Each phase is described in the following sections together with the
1413 command line options that control it.
1415 =head2 Configuration reading phase
1417 After starting, novaboot reads zero or more configuration files. We
1418 describe their content in section L</"CONFIGURATION FILES">. By default, the
1419 configuration is read from multiple locations. First from the system
1420 configuration directory (F</etc/novaboot.d/>), second from the user
1421 configuration file (F<~/.config/novaboot>) and third from F<.novaboot>
1422 files along the path to the current directory. Alternatively, a single
1423 configuration file specified with the B<-c> switch or with the
1424 C<NOVABOOT_CONFIG> environment variable is read. The latter read files
1425 override settings from the former ones.
1427 The system configuration directory is determined by the content of
1428 NOVABOOT_CONFIG_DIR environment variable and defaults to
1429 F</etc/novaboot.d>. Files in this directory with names consisting
1430 solely of English letters, numbers, dashes '-' and underscores '_'
1431 (note that dot '.' is not included) are read in alphabetical order.
1433 Then, the user configuration file is read from
1434 F<$XDG_CONFIG_HOME/novaboot>. If C<$XDG_CONFIG_HOME> environment
1435 variable is not set F<~/.config/novaboot> is read instead.
1437 Finally, novaboot searches for files named F<.novaboot> starting from the
1438 directory of the novaboot script (or working directory, see bellow)
1439 and continuing upwards up to the root directory. The found
1440 configuration files are then read in the opposite order (i.e. from the
1441 root directory downwards). This ordering allows having, for example, a project
1442 specific configuration in F<~/project/.novaboot>.
1444 Note the difference between F<~/.config/novaboot> and F<~/.novaboot>.
1445 The former one is always read, whereas the latter only when novaboot
1446 script or working directory is under the C<$HOME> directory.
1448 In certain cases, the location of the novaboot script cannot be
1449 determined in this early phase. This situation happens either when the script is
1450 read from the standard input or when novaboot is invoked explicitly as
1451 in the example L</"4."> above. In this case, the current working
1452 directory is used as a starting point for configuration file search
1453 instead of the novaboot script directory.
1457 =item -c, --config=I<filename>
1459 Use the specified configuration file instead of the default one(s).
1463 =head2 Command line processing phase
1469 Dump the current configuration to stdout end exit. Useful as an
1470 initial template for a configuration file.
1474 Print short (B<-h>) or long (B<--help>) help.
1476 =item -t, --target=I<target>
1478 This option serves as a user configurable shortcut for other novaboot
1479 options. The effect of this option is the same as specifying the
1480 options stored in the C<%targets> configuration variable under key
1481 I<target>. See also L</"CONFIGURATION FILES">.
1483 When this option is not given, novaboot tries to determine the target
1484 to use from either B<NOVABOOT_TARGET> environment variable or
1485 B<$default_target> configuration file variable.
1487 =item --ssh=I<user@hostname>
1489 Configures novaboot to control the target via C<novaboot-shell>
1490 running remotely via SSH.
1492 Using this option is the same as specifying B<--remote-cmd>,
1493 B<--remote-expect>, B<--server> B<--rsync-flags>, B<--prefix> and
1494 B<--reset-cmd> manually in a way compatible with C<novaboot-shell>.
1495 The server can be configured to provide other, safe bootloader-related
1496 options, to the client. When this happens, novaboot prints them to
1499 Currently, this in an initial experimental implementation. We plan to
1500 change/extend this feature soon!
1504 =head2 Script preprocessing phase
1506 This phase allows modifying the parsed novaboot script before it is
1507 used in the later phases.
1511 =item -a, --append=I<parameters>
1513 Append a string to the first C<load> line in the novaboot script. This option
1514 can be used to append parameters to the kernel's or root task's
1515 command line. This option can appear multiple times.
1519 Use L<Bender|https://github.com/TUD-OS/morbo/blob/master/standalone/bender.c>
1520 chainloader. Bender scans the PCI bus for PCI serial ports and stores
1521 the information about them in the BIOS data area for use by the
1524 =item --chainloader=I<chainloader>
1526 Specifies a chainloader that is loaded before the kernel and other
1527 files specified in the novaboot script. E.g. 'bin/boot/bender
1532 Print the modules to boot and their parameters, after this phase
1533 finishes. Then exit. This is useful for seeing the effect of other
1534 options in this section.
1536 =item -k, --kernel=F<file>
1538 Replace the first word on the first C<load> line in the novaboot
1539 script with F<file>.
1541 =item --scriptmod=I<Perl expression>
1543 When novaboot reads the script, I<Perl expression> is executed for every
1544 line (in $_ variable). For example, C<novaboot
1545 --scriptmod=s/sigma0/omega6/g> replaces every occurrence of I<sigma0>
1546 in the script with I<omega6>.
1548 When this option is present, it overrides I<$script_modifier> variable
1549 from the configuration file, which has the same effect. If this option
1550 is given multiple times all expressions are evaluated in the command
1555 =head2 File generation phase
1557 In this phase, files needed for booting are generated in a so-called
1558 I<build directory> (see L</--build-dir>). In most cases configuration
1559 for a bootloader is generated automatically by novaboot. It is also
1560 possible to generate other files using I<heredoc> or I<"<"> syntax in
1561 novaboot scripts. Finally, novaboot can generate binaries in this phases by
1562 running C<scons> or C<make>.
1566 =item --build-dir=I<directory>
1568 Overrides the default build directory location.
1570 The default build directory location is determined as follows: If the
1571 configuration file defines the C<$builddir> variable, its value is
1572 used. Otherwise, it is the directory that contains the first processed
1575 See also L</BUILDDIR> variable.
1577 =item -g, --grub[=I<filename>]
1579 Generates grub bootloader menu file. If the I<filename> is not
1580 specified, F<menu.lst> is used. The I<filename> is relative to the
1581 build directory (see B<--build-dir>).
1583 =item --grub-preamble=I<prefix>
1585 Specifies the I<preamble> that is at the beginning of the generated
1586 GRUB or GRUB2 config files. This is useful for specifying GRUB's
1589 =item --prefix=I<prefix>
1591 Specifies I<prefix> (e.g. F</srv/tftp>) that is put in front of every
1592 filename in generated bootloader configuration files (or in U-Boot
1595 If the I<prefix> contains string $NAME, it will be replaced with the
1596 name of the novaboot script (see also B<--name>).
1598 If the I<prefix> contains string $BUILDDIR, it will be replaced with
1599 the build directory (see also B<--build-dir>).
1603 Alias for B<--prefix>.
1605 =item --grub2[=I<filename>]
1607 Generate GRUB2 menu entry in I<filename>. If I<filename> is not
1608 specified F<grub.cfg> is used. The content of the menu entry can be
1609 customized with B<--grub-preamble>, B<--grub2-prolog> or
1610 B<--grub_prefix> options.
1612 To use the generated menu entry on your development
1613 machine that uses GRUB2, append the following snippet to
1614 F</etc/grub.d/40_custom> file and regenerate your grub configuration,
1615 i.e. run update-grub on Debian/Ubuntu.
1617 if [ -f /path/to/nul/build/grub.cfg ]; then
1618 source /path/to/nul/build/grub.cfg
1621 =item --grub2-prolog=I<prolog>
1623 Specifies the text that novaboot puts at the beginning of the GRUB2 menu entry.
1625 =item -m, --make[=make command]
1627 Runs C<make> to build files that are not generated by novaboot itself.
1629 =item --name=I<string>
1631 Use the name I<string> instead of the name of the novaboot script.
1632 This name is used for things like a title of grub menu or for the
1633 server directory where the boot files are copied to.
1637 Do not run external commands to generate files (i.e. "<" syntax and
1638 C<run> keyword). This switch does not influence the generation of files
1639 specified with "<<WORD" syntax.
1641 =item -p, --pulsar[=mac]
1643 Generates pulsar bootloader configuration file named F<config-I<mac>>
1644 The I<mac> string is typically a MAC address and defaults to
1647 =item --scons[=scons command]
1649 Runs C<scons> to build files that are not generated by novaboot
1654 Strip I<rom://> prefix from command lines and generated config files.
1655 The I<rom://> prefix is used by NUL. For NRE, it has to be stripped.
1659 Exit novaboot after file generation phase.
1663 =head2 Target connection check
1665 In this phase novaboot connects to target's serial port (if it has
1666 one). If another novaboot user/instance occupies the target, novaboot
1667 exits here with an error message.
1671 =item --amt=I<"[user[:password]@]host[:port]>
1673 Use Intel AMT technology to control the target machine. WS management
1674 is used to powercycle it and Serial-Over-Lan (SOL) for input/output.
1675 The hostname or (IP address) is given by the I<host> parameter. If the
1676 I<password> is not specified, environment variable AMT_PASSWORD is
1677 used. The I<port> specifies a TCP port for SOL. If not specified, the
1678 default is 16992. The default I<user> is admin.
1680 =item --iprelay=I<addr[:port]>
1682 Use TCP/IP relay and serial port to access the target's serial port
1683 and powercycle it. The I<addr> parameter specifies the IP address of
1684 the relay. If I<port> is not specified, it defaults to 23.
1686 Note: This option is supposed to work with HWG-ER02a IP relays.
1688 =item --iprelay-cmd=I<command>
1690 Similar to B<--iprelay> but uses I<command> to talk to the iprelay
1691 rather than direct network connection.
1693 =item -s, --serial[=device]
1695 Target's serial line is connected to host's serial line (device). The
1696 default value for device is F</dev/ttyUSB0>.
1698 The value of this option is exported in NB_NOVABOOT environment
1699 variable to all subprocesses run by C<novaboot>.
1701 =item --stty=I<settings>
1703 Specifies settings passed to C<stty> invoked on the serial line
1704 specified with B<--serial> option. If this option is not given,
1705 C<stty> is called with C<raw -crtscts -onlcr 115200> settings.
1707 =item --remote-cmd=I<cmd>
1709 Command that mediates connection to the target's serial line. For
1710 example C<ssh server 'cu -l /dev/ttyS0'>.
1712 =item --remote-expect=I<string>
1714 Wait for reception of I<string> after establishing the remote
1715 connection. This option is needed when novaboot should wait for
1716 confirmation before deploying files to the target, e.g. to not
1717 overwrite other user's files when they are using the target.
1719 =item --remote-expect-silent=I<string>
1721 The same as B<--remote-expect> except that the remote output is not
1722 echoed to stdout while waiting for the I<string>. Everything after the
1723 matched string is printed to stdout, so you may want to include line
1724 end characters in the I<string> as well.
1726 =item --remote-expect-timeout=I<seconds>
1728 Timeout in seconds for B<--remote-expect> or
1729 B<--remote-expect-seconds>. When negative, waits forever. The default
1734 =head2 File deployment phase
1736 In some setups, it is necessary to copy the files needed for booting
1737 to a particular location, e.g. to a TFTP boot server or to the
1742 =item -d, --dhcp-tftp
1744 Turns your workstation into a DHCP and TFTP server so that the OS can
1745 be booted via PXE BIOS (or similar mechanism) on the test machine
1746 directly connected by a plain Ethernet cable to your workstation.
1748 The DHCP and TFTP servers require root privileges and C<novaboot>
1749 uses C<sudo> command to obtain those. You can put the following to
1750 I</etc/sudoers> to allow running the necessary commands without asking
1753 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
1754 your_login ALL=NOPASSWD: NOVABOOT
1758 Starts a TFTP server on your workstation. This is similar to
1759 B<--dhcp-tftp> except that DHCP server is not started.
1761 The TFTP server requires root privileges and C<novaboot> uses C<sudo>
1762 command to obtain those. You can put the following to I</etc/sudoers>
1763 to allow running the necessary commands without asking for a password.
1765 Cmnd_Alias NOVABOOT = /usr/sbin/in.tftpd --listen --secure -v -v -v --pidfile tftpd.pid *, /usr/bin/pkill --pidfile=tftpd.pid
1766 your_login ALL=NOPASSWD: NOVABOOT
1768 =item --tftp-port=I<port>
1770 Port to run the TFTP server on. Implies B<--tftp>.
1772 =item --netif=I<network interface>
1774 Network interface used to deploy files to the target. The default value is
1775 I<eth0>. This option influences the configuration of the DHCP server started
1776 by B<--dhcp-tftp> and the value that B<$NB_MYIP> get replaced with during
1777 U-Boot conversation.
1779 =item --iso[=filename]
1781 Generates the ISO image that boots NOVA system via GRUB. If no filename
1782 is given, the image is stored under I<NAME>.iso, where I<NAME> is the name
1783 of the novaboot script (see also B<--name>).
1785 =item --server[=[[user@]server:]path]
1787 Copy all files needed for booting to another location. The files will
1788 be copied (by B<rsync> tool) to the directory I<path>. If the I<path>
1789 contains string $NAME, it will be replaced with the name of the
1790 novaboot script (see also B<--name>).
1792 =item --rsync-flags=I<flags>
1794 Specifies I<flags> to append to F<rsync> command line when
1795 copying files as a result of I<--server> option.
1799 If B<--server> is used and its value ends with $NAME, then after
1800 copying the files, a new bootloader configuration file (e.g. menu.lst)
1801 is created at I<path-wo-name>, i.e. the path specified by B<--server>
1802 with $NAME part removed. The content of the file is created by
1803 concatenating all files of the same name from all subdirectories of
1804 I<path-wo-name> found on the "server".
1808 Use Intel AMT technology for IDE redirection. This allows the target
1809 machine to boot from novaboot created ISO image. Implies B<--iso>.
1811 The experimental C<amtider> utility needed by this option can be
1812 obtained from https://github.com/wentasah/amtterm.
1816 =head2 Target power-on and reset phase
1818 At this point, the target is reset (or switched on/off). There are
1819 several ways how this can be accomplished. Resetting a physical target
1820 can currently be accomplished by the following options: B<--amt>,
1821 B<--iprelay>, B<--reset-cmd> and B<--reset-send>.
1827 Switch on/off the target machine and exit. The script (if any) is
1828 completely ignored. Currently, it works only with the following
1829 options: B<--iprelay>, B<--amt>, B<--ssh>.
1831 =item -Q, --qemu[=I<qemu-binary>]
1833 Boot the configuration in qemu. Optionally, the name of qemu binary
1834 can be specified as a parameter.
1836 =item --qemu-append=I<flags>
1838 Append I<flags> to the default qemu flags (QEMU_FLAGS variable or
1839 C<-cpu coreduo -smp 2>).
1841 =item -q, --qemu-flags=I<flags>
1843 Replace the default qemu flags (QEMU_FLAGS variable or C<-cpu coreduo
1844 -smp 2>) with I<flags> specified here.
1846 =item --reset-cmd=I<cmd>
1848 Runs command I<cmd> to reset the target.
1850 =item --reset-send=I<string>
1852 Reset the target by sending the given I<string> to the remote serial
1853 line. "\n" sequences are replaced with the newline character.
1855 =item --no-reset, --reset
1857 Disable/enable resetting of the target.
1861 =head2 Interaction with the bootloader on the target
1865 =item --uboot[=I<prompt>]
1867 Interact with U-Boot bootloader to boot the thing described in the
1868 novaboot script. I<prompt> specifies the U-Boot's prompt (default is
1869 "=> ", other common prompts are "U-Boot> " or "U-Boot# ").
1873 Disable U-Boot interaction previously enabled with B<--uboot>.
1877 Command(s) to send the U-Boot bootloader before loading the images and
1878 booting them. This option can be given multiple times. After sending
1879 commands from each option novaboot waits for U-Boot I<prompt>.
1881 If the command contains string I<$NB_MYIP> then this string is
1882 replaced by IPv4 address of eth0 interface (see also B<--netif>).
1883 Similarly, I<$NB_PREFIX> is replaced with prefix given by B<--prefix>.
1885 See also C<uboot> keyword in L</"NOVABOOT SCRIPT SYNTAX">).
1887 =item --uboot-addr I<name>=I<address>
1889 Load address of U-Boot's C<tftpboot> command for loading I<name>,
1890 where name is one of I<kernel>, I<ramdisk> or I<fdt> (flattened device
1893 The default addresses are ${I<name>_addr_r}, i.e. U-Boot environment
1894 variables used by convention for this purpose.
1896 =item --uboot-cmd=I<command>
1898 Specifies U-Boot command used to execute the OS. If the command
1899 contains strings C<$kernel_addr>, C<$ramdisk_addr>, C<$fdt_addr>,
1900 these are replaced with the addresses configured with B<--uboot-addr>.
1902 The default value is
1904 bootm $kernel_addr $ramdisk_addr $fdt_addr
1906 or the C<UBOOT_CMD> variable if defined in the novaboot script.
1910 =head2 Target interaction phase
1912 In this phase, target's serial output is redirected to stdout and if
1913 stdin is a TTY, it is redirected to the target's serial input allowing
1914 interactive work with the target.
1918 =item --exiton=I<string>
1920 When the I<string> is sent by the target, novaboot exits. This option can
1921 be specified multiple times, in which case novaboot exits whenever
1922 either of the specified strings is sent.
1924 If the I<string> is C<-re>, then the next B<--exiton>'s I<string> is
1925 treated as a regular expression. For example:
1927 --exiton -re --exiton 'error:.*failed'
1929 =item --exiton-re=I<regex>
1931 The same as --exiton -re --exiton I<regex>.
1933 =item --exiton-timeout=I<seconds>
1935 By default B<--exiton> waits for the string match forever. When this
1936 option is specified, "exiton" timeouts after the specifies the number of
1937 seconds and novaboot returns non-zero exit code.
1939 =item -i, --interactive
1941 Setup things for the interactive use of the target. Your terminal will
1942 be switched to raw mode. In raw mode, your local terminal does not
1943 process input in any way (no echoing of entered characters, no
1944 interpretation of special characters). This, among others, means that
1945 Ctrl-C is passed to the target and does not interrupt novaboot. To
1946 exit from novaboot interactive mode type "~~.".
1948 =item --no-interaction, --interaction
1950 Skip resp. force target interaction phase. When skipped, novaboot exits
1951 immediately after the boot is initiated.
1953 =item --expect=I<string>
1955 When the I<string> is received from the target, send the string specified
1956 with the subsequent B<--send*> option to the target.
1958 =item --expect-re=I<regex>
1960 When target's output matches regular expression I<regex>, send the
1961 string specified with the subsequent B<--send*> option to the target.
1963 =item --expect-raw=I<perl-code>
1965 Provides direct control over Perl's Expect module.
1967 =item --send=I<string>
1969 Send I<string> to the target after the previously specified
1970 B<--expect*> was matched in the target's output. The I<string> may
1971 contain escape sequences such as "\n".
1973 Note that I<string> is actually interpreted by Perl, so it can contain
1974 much more that escape sequences. This behavior may change in the
1977 Example: C<--expect='login: ' --send='root\n'>
1979 =item --sendcont=I<string>
1981 Similar to B<--send> but continue expecting more input.
1983 Example: C<--expect='Continue?' --sendcont='yes\n'>
1985 =item --final-eol, --no-final-eol
1987 By default, B<novaboot> always prints an end-of-line character at the
1988 end of its execution in order to ensure that the output of programs
1989 started after novaboot appears at the beginning of the line. When this
1990 is not desired B<--no-final-eol> option can be used to override this
1995 =head1 NOVABOOT SCRIPT SYNTAX
1997 The syntax tries to mimic POSIX shell syntax. The syntax is defined
1998 by the following rules.
2000 Lines starting with "#" and empty lines are ignored.
2002 Lines that end with "\" are concatenated with the following line after
2003 removal of the final "\" and leading whitespace of the following line.
2005 Lines of the form I<VARIABLE=...> (i.e. matching '^[A-Z_]+=' regular
2006 expression) assign values to internal variables. See L</VARIABLES>
2009 Otherwise, the first word on the line defines the meaning of the line.
2010 The following keywords are supported:
2016 These lines represent modules to boot. The
2017 word after C<load> is a file name (relative to the build directory
2018 (see B<--build-dir>) of the module to load and the remaining words are
2019 passed to it as the command line parameters.
2021 When the C<load> line ends with "<<WORD" then the subsequent lines
2022 until the line containing solely WORD are copied literally to the file
2023 named on that line. This is similar to shell's heredoc feature.
2025 When the C<load> line ends with "< CMD" then command CMD is executed
2026 with F</bin/sh> and its standard output is stored in the file named on
2027 that line. The SRCDIR variable in CMD's environment is set to the
2028 absolute path of the directory containing the interpreted novaboot
2033 These lines are similar to C<load> lines. The
2034 file mentioned there is copied to the same place as in the case of C<load>
2035 (e.g. tftp server), but the file is not used in the bootloader
2036 configuration. Such a file can be used by the target for other
2037 purposes than booting, e.g. at OS runtime or for firmware update.
2041 Chainload another bootloader. Instead of loading multiboot modules
2042 identified with C<load> keyword, run another bootloader. This is
2043 currently supported only by pulsar and can be used to load e.g. Grub
2044 as in the example below:
2046 chld boot/grub/i386-pc/core.0
2051 Lines starting with C<run> keyword contain shell commands that are run
2052 during file generation phase. This is the same as the "< CMD" syntax
2053 for C<load> keyboard except that the command's output is not
2054 redirected to a file. The ordering of commands is the same as they
2055 appear in the novaboot script.
2059 These lines represent U-Boot commands that are sent to the target if
2060 B<--uboot> option is given. Having a U-Boot line in the novaboot
2061 script is the same as giving B<--uboot-init> option to novaboot. The
2062 following syntax variants are supported:
2065 uboot[:<timeout>] <string> [> <file>]
2066 uboot[:<timeout>] < <shell> [> <file>]
2068 C<string> is the literal U-Boot command.
2070 The C<uboot> keyword can be suffixed with timeout specification. The
2071 syntax is C<uboot:Ns>, where C<N> is the whole number of seconds. If
2072 the U-Boot command prompt does not appear before the timeout, novaboot
2073 fails. The default timeout is 10 seconds.
2075 In the second variant with the C<<> character the shell code is
2076 executed and its standard output is sent to U-Boot. Example:
2078 uboot < printf "mmc write \$loadaddr 1 %x" $(($(/usr/bin/stat -c%s rootfs.ext4) / 512))
2080 When C<E<gt> file> part is present, the output of the U-Boot command
2081 is written into the given file.
2087 #!/usr/bin/env novaboot
2088 load bzImage console=ttyS0,115200
2089 run make -C buildroot
2090 load rootfs.cpio < gen_cpio buildroot/images/rootfs.cpio "myapp->/etc/init.d/S99myapp"
2092 Example (NOVA User Land - NUL):
2094 #!/usr/bin/env novaboot
2095 WVDESC=Example program
2096 load bin/apps/sigma0.nul S0_DEFAULT script_start:1,1 \
2097 verbose hostkeyb:0,0x60,1,12,2
2098 load bin/apps/hello.nul
2099 load hello.nulconfig <<EOF
2100 sigma0::mem:16 name::/s0/log name::/s0/timer name::/s0/fs/rom ||
2101 rom://bin/apps/hello.nul
2104 This example will load three modules: F<sigma0.nul>, F<hello.nul> and
2105 F<hello.nulconfig>. sigma0 receives some command line parameters and
2106 F<hello.nulconfig> file is generated on the fly from the lines between
2107 C<<<EOF> and C<EOF>.
2109 Example (Zynq system update via U-Boot):
2111 #!/usr/bin/env novaboot
2115 # Write kernel to FAT filesystem on the 1st SD card partition
2116 run mkimage -f uboot-image.its image.ub
2118 uboot:60s tftpboot ${loadaddr} $NB_PREFIX/image.ub
2119 uboot fatwrite mmc 0:1 ${loadaddr} image.ub $filesize
2120 uboot set bootargs console=ttyPS0,115200 root=/dev/mmcblk0p2
2122 # Write root FS image to the 2nd SD card partition
2123 copy rootfs/images/rootfs.ext4
2124 uboot:60s tftpboot ${loadaddr} $NB_PREFIX/rootfs/images/rootfs.ext4
2125 uboot mmc part > mmc-part.txt
2126 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))
2133 The following variables are interpreted in the novaboot script:
2139 Novaboot chdir()s to this directory before file generation phase. The
2140 directory name specified here is relative to the build directory
2141 specified by other means (see L</--build-dir>).
2145 Assigning this variable has the same effect as specifying L</--exiton>
2148 =item HYPERVISOR_PARAMS
2150 Parameters passed to the hypervisor. The default value is "serial", unless
2151 overridden in the configuration file.
2155 The kernel to use instead of the hypervisor specified in the
2156 configuration file with the C<$hypervisor> variable. The value should
2157 contain the name of the kernel image as well as its command line
2158 parameters. If this variable is defined and non-empty, the variable
2159 HYPERVISOR_PARAMS is not used.
2163 If this variable is 1, the system is not booted. This is currently
2164 only implemented for U-Boot bootloader where it is useful for
2165 interacting with the bootloader without booting the system - e.g. for
2170 Use a specific qemu binary (can be overridden with B<-Q>) and flags
2171 when booting this script under qemu. If QEMU_FLAGS variable is also
2172 specified flags specified in QEMU variable are replaced by those in
2177 Use specific qemu flags (can be overridden with B<-q>).
2181 See L</--uboot-cmd>.
2185 Description of the WvTest-compliant program.
2187 =item WVTEST_TIMEOUT
2189 The timeout in seconds for WvTest harness. If no complete line appears
2190 in the test output within the time specified here, the test fails. It
2191 is necessary to specify this for long running tests that produce no
2192 intermediate output.
2196 =head1 CONFIGURATION FILES
2198 Novaboot can read its configuration from one or more files. By
2199 default, novaboot looks for files in F</etc/novaboot.d>, file
2200 F<~/.config/novaboot> and files named F<.novaboot> as described in
2201 L</Configuration reading phase>. Alternatively, configuration file
2202 location can be specified with the B<-c> switch or with the
2203 NOVABOOT_CONFIG environment variable. The configuration file has Perl
2204 syntax (i.e. it is better to put C<1;> as the last line) and should set
2205 values of certain Perl variables. The current configuration can be
2206 dumped with the B<--dump-config> switch. Some configuration variables
2207 can be overridden by environment variables (see below) or by command
2210 Supported configuration variables include:
2216 Build directory location relative to the location of the configuration
2219 =item $default_target
2221 Default target (see below) to use when no target is explicitly
2222 specified with the B<--target> command line option or
2223 B<NOVABOOT_TARGET> environment variable.
2227 Hash of target definitions to be used with the B<--target> option. The
2228 key is the identifier of the target, the value is the string with
2229 command line options. For instance, if the configuration file contains:
2231 $targets{'mybox'} = '--server=boot:/tftproot --serial=/dev/ttyUSB0 --grub',
2233 then the following two commands are equivalent:
2235 ./myos --server=boot:/tftproot --serial=/dev/ttyUSB0 --grub
2240 =head1 ENVIRONMENT VARIABLES
2242 Some options can be specified not only via config file or command line
2243 but also through environment variables. Environment variables override
2244 the values from the configuration file and command line parameters
2245 override the environment variables.
2249 =item NOVABOOT_CONFIG
2251 Name of the novaboot configuration file to use instead of the default
2254 =item NOVABOOT_CONFIG_DIR
2256 Name of the novaboot configuration directory. When not specified
2257 F</etc/novaboot.d> is used.
2259 =item NOVABOOT_TARGET
2261 Name of the novaboot target to use. This overrides the value of
2262 B<$default_target> from the configuration file and can be overridden
2263 with the B<--target> command line option.
2265 =item NOVABOOT_BENDER
2267 Defining this variable has the same effect as using B<--bender>
2274 Michal Sojka <sojka@os.inf.tu-dresden.de>
2276 Latest novaboot version can be found at
2277 L<https://github.com/wentasah/novaboot>.
2281 # LocalWords: novaboot Novaboot NOVABOOT TFTP PXE DHCP filename stty
2282 # LocalWords: chainloader stdout Qemu qemu preprocessing ISOLINUX bootable
2283 # LocalWords: config subprocesses sudo sudoers tftp dhcp IDE stdin
2284 # LocalWords: subdirectories TTY whitespace heredoc POSIX WvTest