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 my $p = Getopt::Long::Parser->new;
260 $p->configure(qw/no_ignore_case no_pass_through/);
261 $p->getoptionsfromarray(\@target_config, %opt_spec_safe) or die("Error processing configuration from the server");
263 if (scalar @target_config) { die "Unsuported configuration received from the server: ".join(", ", @target_config); }
266 # First process target options
268 my $t = defined($explicit_target) ? $explicit_target : $CFG::default_target;
270 Getopt::Long::Configure(qw/no_ignore_case pass_through/);
272 exists $CFG::targets{$t} or die("Unknown target '$t' (valid targets are: ".join(", ", sort keys(%CFG::targets)).")");
274 undef $explicit_target;
275 my ($ret, $remaining_args) = GetOptionsFromString ($CFG::targets{$t}, ("target|t=s" => \$explicit_target));
276 if (!$ret) { die "Error parsing target $t option"; }
277 push(@target_expanded, @$remaining_args);
278 $t = $explicit_target;
281 my @args = (@target_expanded, @ARGV);
282 print STDERR "novaboot: Effective options: @args\n";
284 Getopt::Long::Configure(qw/no_ignore_case no_pass_through/);
285 GetOptionsFromArray(\@target_expanded, %opt_spec) or die ("Error in target definition");
288 # Then process other command line options - some of them may override
289 # what was specified by the target
290 GetOptions %opt_spec or die("Error in command line arguments");
291 pod2usage(1) if $help;
292 pod2usage(-exitstatus => 0, -verbose => 2) if $man;
294 ### Dump sanitized configuration (if requested)
298 $Data::Dumper::Indent=1;
299 print "# This file is in perl syntax.\n";
300 foreach my $key(sort(keys(%CFG::))) { # See "Symbol Tables" in perlmod(1)
301 if (defined ${$CFG::{$key}}) { print Data::Dumper->Dump([${$CFG::{$key}}], ["*$key"]); }
302 if ( @{$CFG::{$key}}) { print Data::Dumper->Dump([\@{$CFG::{$key}}], ["*$key"]); }
303 if ( %{$CFG::{$key}}) { print Data::Dumper->Dump([\%{$CFG::{$key}}], ["*$key"]); }
309 ### Sanitize configuration
311 if ($interactive && !-t STDIN) {
312 die("novaboot: Interactive mode not supported when not on terminal");
315 if (defined $config_name_opt && scalar(@ARGV) > 1) { die "You cannot use --name with multiple scripts"; }
318 $iso_image //= ''; # IDE-R needs an ISO image
319 if (!defined $amt) { die "Error: --ider requires --amt"; }
323 my %input_opts = ('--iprelay' => \$iprelay,
324 '--iprelay-cmd'=> \$iprelay_cmd,
325 '--serial' => \$serial,
326 '--remote-cmd' => \$remote_cmd,
328 my @opts = grep(defined(${$input_opts{$_}}) , keys %input_opts);
330 die("novaboot: More than one target connection option: ".join(', ', @opts)) if scalar @opts > 1;
334 if (defined $serial) {
335 $serial ||= "/dev/ttyUSB0";
336 $ENV{NB_SERIAL} = $serial;
338 if (defined $grub_config) { $grub_config ||= "menu.lst"; }
339 if (defined $grub2_config) { $grub2_config ||= "grub.cfg"; }
341 ## Parse the novaboot script(s)
346 my ($modules, $variables, $generated, $copy, $chainload, $continuation) = ([], {}, [], []);
347 my $skip_reading = defined($on_opt) || defined($off_opt);
348 while (!$skip_reading && ($_ = <>)) {
349 if ($ARGV ne $last_fn) { # New script
350 die "Missing EOF in $last_fn" if $file;
351 die "Unfinished line in $last_fn" if $continuation;
353 push @scripts, { 'filename' => $ARGV,
354 'modules' => $modules = [],
355 'variables' => $variables = {},
356 'generated' => $generated = [],
357 'copy' => $copy = [],
358 'chainload' => $chainload = [],
363 next if /^#/ || /^\s*$/; # Skip comments and empty lines
365 $_ =~ s/^[[:space:]]*// if ($continuation);
367 if (/\\$/) { # Line continuation
368 $continuation .= substr($_, 0, length($_)-1);
372 if ($continuation) { # Last continuation line
373 $_ = $continuation . $_;
377 foreach my $mod(@scriptmod) { eval $mod; }
379 if ($file && $_ eq $EOF) { # Heredoc end
383 if ($file) { # Heredoc content
384 push @{$file}, "$_\n";
387 if (/^([A-Z_]+)=(.*)$/) { # Internal variable
388 $$variables{$1} = $2;
389 push(@exiton, $2) if ($1 eq "EXITON");
392 sub process_load_copy($) {
393 die("novaboot: '$last_fn' line $.: Missing file name\n") unless /^[^ <]+/;
394 if (/^([^ ]*)(.*?)[[:space:]]*<<([^ ]*)$/) { # Heredoc start
396 push @$generated, {filename => $1, content => $file};
400 if (/^([^ ]*)(.*?)[[:space:]]*< ?(.*)$/) { # Command substitution
401 push @$generated, {filename => $1, command => $3};
406 if (s/^load *//) { # Load line
407 push @$modules, process_load_copy($_);
410 if (s/^copy *//) { # Copy line
411 push @$copy, process_load_copy($_);
414 if (s/^chld *//) { # Chainload line
415 push @$chainload, process_load_copy($_);
418 if (/^run (.*)/) { # run line
419 push @$generated, {command => $1};
422 if (/^uboot(?::([0-9]+)s)? +(< *)?(.*)/) { # uboot line
423 # TODO: If U-Boot supports some interactive menu, it might
424 # make sense to store uboot lines per novaboot script.
425 my ($timeout, $redir, $string, $dest) = ($1, $2, $3);
426 if ($string =~ /(.*) *> *(.*)/) {
430 push @uboot_init, { command => $redir ? "" : $string,
431 system => $redir ? $string : "",
438 die("novaboot: Cannot parse script '$last_fn' line $.. Didn't you forget 'load' keyword?\n");
441 # print Dumper(\@scripts);
443 foreach my $script (@scripts) {
444 $modules = $$script{modules};
445 @$modules[0] =~ s/^[^ ]*/$kernel_opt/ if $kernel_opt;
446 @$modules[0] .= ' ' . join(' ', @append) if @append;
449 if (exists $variables->{KERNEL}) {
450 $kernel = $variables->{KERNEL};
452 if ($CFG::hypervisor) {
453 $kernel = $CFG::hypervisor . " ";
454 if (exists $variables->{HYPERVISOR_PARAMS}) {
455 $kernel .= $variables->{HYPERVISOR_PARAMS};
457 $kernel .= $CFG::hypervisor_params;
461 @$modules = ($kernel, @$modules) if $kernel;
462 @$modules = (@chainloaders, @$modules);
463 @$modules = ("bin/boot/bender", @$modules) if ($bender || defined $ENV{'NOVABOOT_BENDER'});
467 foreach my $script (@scripts) {
468 print join("\n", @{$$script{modules}})."\n";
475 sub generate_configs($$$) {
476 my ($base, $generated, $filename) = @_;
477 if ($base) { $base = "$base/"; };
478 foreach my $g(@$generated) {
479 if (exists $$g{content}) {
480 my $config = $$g{content};
481 my $fn = $$g{filename};
482 open(my $f, '>', $fn) || die("$fn: $!");
483 map { s|\brom://([^ ]*)|$rom_prefix$base$1|g; print $f "$_"; } @{$config};
485 print STDERR "novaboot: Created $fn\n";
486 } elsif (exists $$g{command} && ! $no_file_gen) {
487 $ENV{SRCDIR} = dirname(File::Spec->rel2abs( $filename, $invocation_dir ));
488 if (exists $$g{filename}) {
489 system_verbose("( $$g{command} ) > $$g{filename}");
491 system_verbose($$g{command});
497 sub generate_grub_config($$$$;$)
499 my ($filename, $title, $base, $modules_ref, $preamble) = @_;
500 if ($base) { $base = "$base/"; };
501 open(my $fg, '>', $filename) or die "$filename: $!";
502 print $fg "$preamble\n" if $preamble;
503 print $fg "title $title\n" if $title;
504 #print $fg "root $base\n"; # root doesn't really work for (nd)
506 foreach (@$modules_ref) {
509 my ($kbin, $kcmd) = split(' ', $_, 2);
510 $kcmd = '' if !defined $kcmd;
511 print $fg "kernel ${base}$kbin $kcmd\n";
513 s|\brom://([^ ]*)|$rom_prefix$base$1|g; # Translate rom:// files - needed for vdisk parameter of sigma0
514 print $fg "module $base$_\n";
518 print("novaboot: Created $builddir/$filename\n");
522 sub generate_syslinux_config($$$$)
524 my ($filename, $title, $base, $modules_ref) = @_;
525 if ($base && $base !~ /\/$/) { $base = "$base/"; };
526 open(my $fg, '>', $filename) or die "$filename: $!";
527 print $fg "LABEL $title\n";
528 #TODO print $fg "MENU LABEL $human_readable_title\n";
530 my ($kbin, $kcmd) = split(' ', @$modules_ref[0], 2);
532 if (system("file $kbin|grep 'Linux kernel'") == 0) {
533 my $initrd = @$modules_ref[1];
534 die('To many "load" lines for Linux kernel') if (scalar @$modules_ref > 2);
535 print $fg "LINUX $base$kbin\n";
536 print $fg "APPEND $kcmd\n";
537 print $fg "INITRD $base$initrd\n";
539 print $fg "KERNEL mboot.c32\n";
541 foreach (@$modules_ref) {
542 s|\brom://([^ ]*)|$rom_prefix$base$1|g; # Translate rom:// files - needed for vdisk parameter of sigma0
543 push @append, "$base$_";
544 print $fg "APPEND ".join(' --- ', @append)."\n";
547 #TODO print $fg "TEXT HELP\n";
548 #TODO print $fg "some help here\n";
549 #TODO print $fg "ENDTEXT\n";
551 print("novaboot: Created $builddir/$filename\n");
555 sub generate_grub2_config($$$$;$$)
557 my ($filename, $title, $base, $modules_ref, $preamble, $prolog) = @_;
558 if ($base && substr($base,-1,1) ne '/') { $base = "$base/"; };
559 open(my $fg, '>', $filename) or die "$filename: $!";
560 print $fg "$preamble\n" if $preamble;
561 $title ||= 'novaboot';
562 print $fg "menuentry $title {\n";
563 print $fg "$prolog\n" if $prolog;
565 foreach (@$modules_ref) {
568 my ($kbin, $kcmd) = split(' ', $_, 2);
569 $kcmd = '' if !defined $kcmd;
570 print $fg " multiboot ${base}$kbin $kcmd\n";
573 # GRUB2 doesn't pass filename in multiboot info so we have to duplicate it here
574 $_ = join(' ', ($args[0], @args));
575 s|\brom://|$rom_prefix|g; # We do not need to translate path for GRUB2
576 print $fg " module $base$_\n";
581 print("novaboot: Created $builddir/$filename\n");
585 sub generate_pulsar_config($$$)
587 my ($filename, $modules_ref, $chainload_ref) = @_;
588 open(my $fg, '>', $filename) or die "$filename: $!";
589 print $fg "root $pulsar_root\n" if defined $pulsar_root;
590 if (scalar(@$chainload_ref) > 0) {
591 print $fg "chld $$chainload_ref[0]\n";
595 foreach (@$modules_ref) {
598 ($kbin, $kcmd) = split(' ', $_, 2);
599 $kcmd = '' if !defined $kcmd;
602 s|\brom://|$rom_prefix|g;
603 print $fg "load $_\n";
606 # Put kernel as last - this is needed for booting Linux and has no influence on non-Linux OSes
607 print $fg "exec $kbin $kcmd\n";
610 print("novaboot: Created $builddir/$filename\n");
614 sub shell_cmd_string(@)
616 return join(' ', map((/^[-_=a-zA-Z0-9\/\.\+]+$/ ? "$_" : "'$_'"), @_));
621 print STDERR "novaboot: Running: ".shell_cmd_string(@_)."\n";
623 exit(1); # should not be reached
626 sub system_verbose($)
629 print STDERR "novaboot: Running: $cmd\n";
630 my $ret = system($cmd);
631 if ($ret & 0x007f) { die("Command terminated by a signal"); }
632 if ($ret & 0xff00) {die("Command exit with non-zero exit code"); }
633 if ($ret) { die("Command failure $ret"); }
638 $str =~ s/^\s+|\s+$//g;
644 if (exists $variables->{WVDESC}) {
645 print STDERR "Testing \"$variables->{WVDESC}\" in $last_fn:\n";
646 } elsif ($last_fn =~ /\.wv$/) {
647 print STDERR "Testing \"all\" in $last_fn:\n";
650 ## Connect to the target and check whether it is not occupied
652 # We have to do this before file generation phase, because file
653 # generation is intermixed with file deployment phase and we want to
654 # check whether the target is not used by somebody else before
655 # deploying files. Otherwise, we may rewrite other user's files on a
658 my $exp; # Expect object to communicate with the target over serial line
660 my ($amt_user, $amt_password, $amt_host, $amt_port);
662 if (defined $iprelay || defined $iprelay_cmd) {
663 if (defined $iprelay) {
665 $iprelay =~ /([.0-9]+)(:([0-9]+))?/;
668 my $paddr = sockaddr_in($port, inet_aton($addr));
669 my $proto = getprotobyname('tcp');
670 socket($IPRELAY, PF_INET, SOCK_STREAM, $proto) || die "socket: $!";
671 print STDERR "novaboot: Connecting to IP relay... ";
672 connect($IPRELAY, $paddr) || die "connect: $!";
673 print STDERR "done\n";
674 $exp = Expect->init(\*$IPRELAY);
677 if (defined $iprelay_cmd) {
678 print STDERR "novaboot: Running: $iprelay_cmd\n";
681 $exp->spawn($iprelay_cmd);
685 print $exp "\xFF\xF6"; # AYT
686 my $connected = $exp->expect(20, # Timeout in seconds
687 '<iprelayd: connected>',
688 '-re', '<WEB51 HW[^>]*>')
689 || die "iprelay connection: " . ($! || "timeout");
694 my ($relay, $onoff) = @_;
695 die unless ($relay == 1 || $relay == 2);
697 my $cmd = ($relay == 1 ? 0x5 : 0x6) | ($onoff ? 0x20 : 0x10);
698 return "\xFF\xFA\x2C\x32".chr($cmd)."\xFF\xF0";
702 my ($relay, $onoff) = @_;
703 die unless ($relay == 1 || $relay == 2);
704 my $cmd = ($relay == 1 ? 0xdf : 0xbf) | ($onoff ? 0x00 : 0xff);
705 return "\xFF\xFA\x2C\x97".chr($cmd)."\xFF\xF0";
709 my ($relay, $onoff, $can_giveup) = @_;
710 my $confirmation = '';
712 print $exp relaycmd($relay, $onoff);
713 my $confirmed = $exp->expect(20, # Timeout in seconds
714 relayconf($relay, $onoff))
715 || die "iprelay command: " . ($! || "timeout");
718 print("Relay confirmation timeout - ignoring\n");
720 die "Relay confirmation timeout";
726 $target_reset = sub {
727 relay(2, 1, 1); # Reset the machine
732 $target_power_off = sub {
733 relay(1, 1); # Press power button
734 usleep(6000000); # Long press to switch off
738 $target_power_on = sub {
739 relay(1, 1); # Press power button
740 usleep(100000); # Short press
746 system_verbose("stty -F $serial $stty");
747 open($CONN, "+<", $serial) || die "open $serial: $!";
748 $exp = Expect->init(\*$CONN);
750 elsif ($remote_cmd) {
751 print STDERR "novaboot: Running: $remote_cmd\n";
752 $exp = Expect->spawn($remote_cmd);
754 elsif (defined $amt) {
755 require LWP::UserAgent;
756 require LWP::Authen::Digest;
759 my ($host, $username, $password, $schema, $className, $pstate) = @_;
760 #AMT numbers for PowerStateChange (MNI => bluescreen on windows;-)
761 my %pstates = ("on" => 2,
768 <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">
769 <s:Header><a:To>http://$host:16992/wsman</a:To>
770 <w:ResourceURI s:mustUnderstand="true">$schema</w:ResourceURI>
771 <a:ReplyTo><a:Address s:mustUnderstand="true">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address></a:ReplyTo>
772 <a:Action s:mustUnderstand="true">$schema$className</a:Action>
773 <w:MaxEnvelopeSize s:mustUnderstand="true">153600</w:MaxEnvelopeSize>
774 <a:MessageID>uuid:709072C9-609C-4B43-B301-075004043C7C</a:MessageID>
775 <w:Locale xml:lang="en-US" s:mustUnderstand="false" />
776 <w:OperationTimeout>PT60.000S</w:OperationTimeout>
777 <w:SelectorSet><w:Selector Name="Name">Intel(r) AMT Power Management Service</w:Selector></w:SelectorSet>
779 <p:RequestPowerStateChange_INPUT xmlns:p="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_PowerManagementService">
780 <p:PowerState>$pstates{$pstate}</p:PowerState>
781 <p:ManagedElement><a:Address>http://$host:16992/wsman</a:Address>
782 <a:ReferenceParameters><w:ResourceURI>http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ComputerSystem</w:ResourceURI>
783 <w:SelectorSet><w:Selector Name="Name">ManagedSystem</w:Selector></w:SelectorSet>
784 </a:ReferenceParameters></p:ManagedElement>
785 </p:RequestPowerStateChange_INPUT>
786 </s:Body></s:Envelope>
791 my ($host, $username, $password, $content) = @_;
793 my $ua = LWP::UserAgent->new();
794 $ua->agent("novaboot");
796 my $req = HTTP::Request->new(POST => "http://$host:16992/wsman");
797 my $res = $ua->request($req);
798 die ("Unexpected AMT response: " . $res->status_line) unless $res->code == 401;
800 my ($realm) = $res->header("WWW-Authenticate") =~ /Digest realm="(.*?)"/;
801 $ua->credentials("$host:16992", $realm, $username => $password);
804 $req = HTTP::Request->new(POST => "http://$host:16992/wsman");
805 $req->content_type('application/x-www-form-urlencoded');
806 $req->content($content);
807 $res = $ua->request($req);
808 die ("AMT power change request failed: " . $res->status_line) unless $res->is_success;
809 $res->content() =~ /<g:ReturnValue>(\d+)<\/g:ReturnValue>/;
814 my ($host, $username, $password, $pstate)=@_;
815 my $schema="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_PowerManagementService";
816 my $className="/RequestPowerStateChange";
817 my $content = genXML($host, $username, $password ,$schema, $className, $pstate);
818 return sendPOST($host, $username, $password, $content);
821 ($amt_user,$amt_password,$amt_host,$amt_port) = ($amt =~ /(?:(.*?)(?::(.*))?@)?([^:]*)(?::([0-9]*))?/);;
822 $amt_user ||= "admin";
823 $amt_password ||= $ENV{'AMT_PASSWORD'} || die "AMT password not specified";
824 $amt_host || die "AMT host not specified";
828 $target_power_off = sub {
830 my $result = powerChange($amt_host,$amt_user,$amt_password, "off");
831 die "AMT power off failed (ReturnValue $result)" if $result != 0;
834 $target_power_on = sub {
835 my $result = powerChange($amt_host,$amt_user,$amt_password, "on");
836 die "AMT power on failed (ReturnValue $result)" if $result != 0;
839 $target_reset = sub {
840 my $result = powerChange($amt_host,$amt_user,$amt_password, "reset");
842 print STDERR "Warning: Cannot reset $amt_host, trying power on. ";
843 $result = powerChange($amt_host,$amt_user,$amt_password, "on");
845 die "AMT reset failed (ReturnValue $result)" if $result != 0;
848 my $cmd = "amtterm -u $amt_user -p $amt_password $amt_host $amt_port";
849 print STDERR "novaboot: Running: $cmd\n" =~ s/\Q$amt_password\E/???/r;
850 $exp = Expect->spawn($cmd);
851 $exp->expect(10, "RUN_SOL") || die "Expect for 'RUN_SOL': " . ($! || "timeout");
855 if ($remote_expect) {
856 $exp || die("No serial line connection");
857 my $log = $exp->log_stdout;
858 if (defined $remote_expect_silent) {
861 $exp->expect($remote_expect_timeout >= 0 ? $remote_expect_timeout : undef,
862 $remote_expect) || die "Expect for '$remote_expect':" . ($! || "timeout");;
863 if (defined $remote_expect_silent) {
864 $exp->log_stdout($log);
865 print $exp->after() if $log;
869 if (defined $reset_cmd) {
870 $target_reset = sub {
871 system_verbose($reset_cmd);
875 if (defined $reset_send) {
876 $target_reset = sub {
877 $reset_send =~ s/\\n/\n/g;
878 $exp->send($reset_send);
882 if (defined $on_opt && defined $target_power_on) {
886 if (defined $off_opt && defined $target_power_off) {
887 print STDERR "novaboot: Switching the target off...\n";
888 &$target_power_off();
892 $builddir ||= dirname(File::Spec->rel2abs( ${$scripts[0]}{filename})) if scalar @scripts;
893 if (defined $builddir) {
894 chdir($builddir) or die "Can't change directory to $builddir: $!";
895 print STDERR "novaboot: Entering directory `$builddir'\n";
897 $builddir = $invocation_dir;
900 ## File generation phase
901 my (%files_iso, $menu_iso, $filename);
902 my $config_name = '';
905 foreach my $script (@scripts) {
906 $filename = $$script{filename};
907 $modules = $$script{modules};
908 $generated = $$script{generated};
909 $variables = $$script{variables};
911 ($config_name = $filename) =~ s#.*/##;
912 $config_name = $config_name_opt if (defined $config_name_opt);
914 if (exists $variables->{BUILDDIR}) {
915 $builddir = File::Spec->rel2abs($variables->{BUILDDIR});
916 chdir($builddir) or die "Can't change directory to $builddir: $!";
917 print STDERR "novaboot: Entering directory `$builddir'\n";
921 $prefix = $grub_prefix;
922 $prefix =~ s/\$NAME/$config_name/;
923 $prefix =~ s/\$BUILDDIR/$builddir/;
925 # TODO: use $grub_prefix as first parameter if some switch is given
926 generate_configs('', $generated, $filename);
928 ### Generate bootloader configuration files
929 my @bootloader_configs;
930 push @bootloader_configs, generate_grub_config($grub_config, $config_name, $prefix, $modules, $grub_preamble) if (defined $grub_config);
931 push @bootloader_configs, generate_grub2_config($grub2_config, $config_name, $prefix, $modules, $grub_preamble, $grub2_prolog) if (defined $grub2_config);
932 push @bootloader_configs, generate_pulsar_config('config-'.($pulsar||'novaboot'), $modules, $chainload) if (defined $pulsar);
934 ### Run scons or make
937 push @all, @$modules;
939 push @all, @$chainload;
940 my @files = map({ ($file) = m/([^ ]*)/; $file; } @all);
942 # Filter-out generated files
943 my @to_build = grep({ my $file = $_; !scalar(grep($file eq ($$_{filename} || ''), @$generated)) } @files);
945 system_verbose($scons || $CFG::scons." ".join(" ", @to_build)) if (defined $scons);
946 system_verbose($make || $CFG::make ." ".join(" ", @to_build)) if (defined $make);
949 ### Copy files (using rsync)
950 if (defined $server && !defined($gen_only)) {
951 (my $real_server = $server) =~ s/\$NAME/$config_name/;
953 my ($hostname, $path) = split(":", $real_server, 2);
954 if (! defined $path) {
958 my $files = join(" ", map({ ($file) = m/([^ ]*)/; $file; } ( @$modules, @bootloader_configs, @$copy)));
959 map({ my $file = (split)[0]; die "$file: $!" if ! -f $file; } @$modules);
960 my $istty = -t STDOUT && ($ENV{'TERM'} || 'dumb') ne 'dumb';
961 my $progress = $istty ? "--progress" : "";
963 system_verbose("rsync $progress -RLp $rsync_flags $files $real_server");
964 if ($server =~ m|/\$NAME$| && $concat) {
965 my $cmd = join("; ", map { "( cd $path/.. && cat */$_ > $_ )" } @bootloader_configs);
966 system_verbose($hostname ? "ssh $hostname '$cmd'" : $cmd);
971 ### Prepare ISO image generation
972 if (defined $iso_image) {
973 generate_configs("(cd)", $generated, $filename);
975 generate_syslinux_config(\$menu, $config_name, "/", $modules);
976 $menu_iso .= "$menu\n";
977 map { ($file,undef) = split; $files_iso{$file} = 1; } @$modules;
981 ## Generate ISO image
982 if (defined $iso_image) {
983 system_verbose("mkdir -p isolinux");
986 if (-f '/usr/lib/ISOLINUX/isolinux.bin') {
987 # Newer ISOLINUX version
988 @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);
990 # Older ISOLINUX version
991 @files = qw(/usr/lib/syslinux/isolinux.bin /usr/lib/syslinux/mboot.c32 /usr/lib/syslinux/menu.c32);
993 system_verbose("cp @files isolinux");
994 open(my $fh, ">isolinux/isolinux.cfg");
996 print $fh "TIMEOUT 50\n";
997 print $fh "DEFAULT menu\n";
999 print $fh "DEFAULT $config_name\n";
1001 print $fh "$menu_iso";
1004 my $files = join(" ", map("$_=$_", (keys(%files_iso), 'isolinux/isolinux.cfg', map(s|.*/|isolinux/|r, @files))));
1005 $iso_image ||= "$config_name.iso";
1007 # Note: We use -U flag below to "Allow 'untranslated' filenames,
1008 # completely violating the ISO9660 standards". Without this
1009 # option, isolinux is not able to read files names for example
1011 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");
1012 print("ISO image created: $builddir/$iso_image\n");
1015 exit(0) if defined $gen_only;
1017 ## Boot the system using various methods and send serial output to stdout
1019 if (scalar(@scripts) > 1 && ( defined $dhcp_tftp || defined $serial || defined $iprelay)) {
1020 die "You cannot do this with multiple scripts simultaneously";
1023 if ($variables->{WVTEST_TIMEOUT}) {
1024 print STDERR "wvtest: timeout ", $variables->{WVTEST_TIMEOUT}, "\n";
1029 if (defined $qemu) {
1031 $qemu ||= $variables->{QEMU} || $CFG::qemu;
1032 my @qemu_flags = split(" ", $qemu);
1033 $qemu = shift(@qemu_flags);
1035 @qemu_flags = split(/ +/, trim($variables->{QEMU_FLAGS})) if exists $variables->{QEMU_FLAGS};
1036 @qemu_flags = split(/ +/, trim($qemu_flags_cmd)) if $qemu_flags_cmd;
1037 push(@qemu_flags, split(/ +/, trim($qemu_append || '')));
1039 if (defined $iso_image) {
1040 # Boot NOVA with grub (and test the iso image)
1041 push(@qemu_flags, ('-cdrom', $iso_image));
1043 # Boot NOVA without GRUB
1045 # Non-patched qemu doesn't like commas, but NUL can live with pluses instead of commans
1046 foreach (@$modules) {s/,/+/g;}
1047 generate_configs("", $generated, $filename);
1049 if (scalar @$modules) {
1050 my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);
1051 $kcmd = '' if !defined $kcmd;
1053 @$modules = map { if (/\.dtb$/) { $dtb=$_; (); } else { $_ } } @$modules;
1054 my $initrd = join ",", @$modules;
1056 push(@qemu_flags, ('-kernel', $kbin, '-append', $kcmd));
1057 push(@qemu_flags, ('-initrd', $initrd)) if $initrd;
1058 push(@qemu_flags, ('-dtb', $dtb)) if $dtb;
1061 if (!grep /^-serial$/, @qemu_flags) {
1062 push(@qemu_flags, qw(-serial stdio)); # Redirect serial output (for collecting test restuls)
1064 unshift(@qemu_flags, ('-name', $config_name));
1065 print STDERR "novaboot: Running: ".shell_cmd_string($qemu, @qemu_flags)."\n";
1066 $exp = Expect->spawn(($qemu, @qemu_flags)) || die("exec() failed: $!");
1069 ### Local DHCPD and TFTPD
1071 my ($dhcpd_pid, $tftpd_pid);
1073 $tftp=1 if $tftp_port;
1075 if (defined $dhcp_tftp)
1077 generate_configs("(nd)", $generated, $filename);
1078 system_verbose('mkdir -p tftpboot');
1079 generate_grub_config("tftpboot/os-menu.lst", $config_name, "(nd)", \@$modules, "timeout 0");
1080 open(my $fh, '>', 'dhcpd.conf');
1081 my $mac = `cat /sys/class/net/$netif/address`;
1083 print $fh "subnet 10.23.23.0 netmask 255.255.255.0 {
1084 range 10.23.23.10 10.23.23.100;
1085 filename \"bin/boot/grub/pxegrub.pxe\";
1086 next-server 10.23.23.1;
1089 hardware ethernet $mac;
1090 fixed-address 10.23.23.1;
1093 system_verbose("sudo ip a add 10.23.23.1/24 dev $netif;
1094 sudo ip l set dev $netif up;
1095 sudo touch dhcpd.leases");
1097 # We run servers by forking ourselves, because the servers end up
1098 # in our process group and get killed by signals sent to the
1099 # process group (e.g. Ctrl-C on terminal).
1100 $dhcpd_pid = fork();
1101 exec_verbose("sudo dhcpd -d -cf dhcpd.conf -lf dhcpd.leases -pf dhcpd.pid") if ($dhcpd_pid == 0);
1104 if (defined $dhcp_tftp || defined $tftp) {
1106 # Unfortunately, tftpd requires root privileges even with
1107 # non-privileged (>1023) port due to initgroups().
1108 system_verbose("sudo in.tftpd --listen --secure -v -v -v --pidfile tftpd.pid --address :$tftp_port $builddir");
1110 # Kill server when we die
1111 $SIG{__DIE__} = sub { system_verbose('sudo pkill --pidfile=dhcpd.pid') if (defined $dhcp_tftp);
1112 system_verbose('sudo pkill --pidfile=tftpd.pid'); };
1114 # We have to kill tftpd explicitely, because it is not in our process group
1115 $SIG{INT} = sub { system_verbose('sudo pkill --pidfile=tftpd.pid'); exit(0); };
1119 if (defined $ider) {
1120 my $ider_cmd= "amtider -c $iso_image -u $amt_user -p $amt_password $amt_host $amt_port" ;
1121 print STDERR "novaboot: Running: $ider_cmd\n" =~ s/\Q$amt_password\E/???/r;
1122 my $ider_pid = fork();
1123 if ($ider_pid == 0) {
1125 die "IDE redirection failed";
1127 # FIXME: This collides with --tftp option. Hopefully, nobody needs
1128 # to use both simultaneously.
1129 $SIG{__DIE__} = sub { system_verbose('kill $ider_pid'); };
1132 ### Reset target (IP relay, AMT, ...)
1134 if (defined $target_reset && $reset) {
1135 print STDERR "novaboot: Reseting the test box... ";
1137 print STDERR "done\n";
1139 # We don't want to output anything printed by the target
1140 # before reset so we clear the buffers now. This is, however,
1141 # not ideal because we may loose some data that were sent
1142 # after the reset. If this is a problem, one should reset and
1143 # connect to serial line in atomic manner. For example, if
1144 # supported by hardware, use --remote-cmd 'sterm -d ...' and
1145 # do not use separate --reset-cmd.
1146 my $log = $exp->log_stdout;
1147 $exp->log_stdout(0);
1148 $exp->expect(0); # Read data from target
1149 $exp->clear_accum(); # Clear the read data
1150 $exp->log_stdout($log);
1154 ### U-boot conversation
1155 if (defined $uboot) {
1156 my $uboot_prompt = $uboot || '=> ';
1157 print STDERR "novaboot: Waiting for U-Boot prompt...\n";
1158 $exp || die("No serial line connection");
1159 $exp->log_stdout(1);
1160 #$exp->exp_internal(1);
1162 [qr/Hit any key to stop autoboot:/, sub { $exp->send("\n"); exp_continue; }],
1163 $uboot_prompt) || die "No U-Boot prompt deteceted";
1164 foreach my $cmdspec (@uboot_init) {
1165 my ($cmd, $timeout);
1166 die "Internal error - please report a bug" unless ref($cmdspec) eq "HASH";
1168 if ($cmdspec->{system}) {
1169 $cmd = `$cmdspec->{system}`;
1171 $cmd = $cmdspec->{command};
1173 $timeout = $cmdspec->{timeout} // 10;
1175 if ($cmd =~ /\$NB_MYIP/) {
1176 my $ip = (grep /inet /, `ip addr show $netif`)[0] || die "Problem determining IP address of $netif";
1177 $ip =~ s/\s*inet ([0-9.]*).*/$1/;
1178 $cmd =~ s/\$NB_MYIP/$ip/g;
1180 if ($cmd =~ /\$NB_PREFIX/) {
1183 $cmd =~ s/\$NB_PREFIX/$p/g;
1186 $exp->send("$cmd\n");
1188 my ($matched_pattern_position, $error,
1189 $successfully_matching_string,
1190 $before_match, $after_match) =
1191 $exp->expect($timeout, $uboot_prompt);
1192 die "No U-Boot prompt: $error" if $error;
1194 if ($cmdspec->{dest}) {
1195 open(my $fh, ">", $cmdspec->{dest}) or die "Cannot open '$cmdspec->{dest}': $!";
1196 print $fh $before_match;
1201 # Load files if there are some load lines in the script
1202 if (scalar(@$modules) > 0 && !$variables->{NO_BOOT}) {
1203 my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);
1205 @$modules = map { if (/\.dtb$/) { $dtb=$_; (); } else { $_ } } @$modules;
1206 my $initrd = shift @$modules;
1208 if (defined $kbin) {
1209 die "No '--uboot-addr kernel' given" unless $uboot_addr{kernel};
1210 $exp->send("tftpboot $uboot_addr{kernel} $prefix$kbin\n");
1212 [qr/##/, sub { exp_continue; }],
1213 $uboot_prompt) || die "Kernel load: " . ($! || "timeout");
1216 die "No '--uboot-addr fdt' given" unless $uboot_addr{fdt};
1217 $exp->send("tftpboot $uboot_addr{fdt} $prefix$dtb\n");
1219 [qr/##/, sub { exp_continue; }],
1220 $uboot_prompt) || die "Device tree load: " . ($! || "timeout");
1222 $uboot_addr{fdt} = '';
1224 if (defined $initrd) {
1225 die "No '--uboot-addr ramdisk' given" unless $uboot_addr{ramdisk};
1226 $exp->send("tftpboot $uboot_addr{ramdisk} $prefix$initrd\n");
1228 [qr/##/, sub { exp_continue; }],
1229 $uboot_prompt) || die "Initrd load: " . ($! || "timeout");
1231 $uboot_addr{ramdisk} = '-';
1235 $exp->send("setenv bootargs $kcmd\n");
1236 $exp->expect(5, $uboot_prompt) || die "U-Boot prompt: " . ($! || "timeout");
1239 $uboot_cmd //= $variables->{UBOOT_CMD} // 'bootm $kernel_addr $ramdisk_addr $fdt_addr';
1240 if (!$variables->{NO_BOOT} && $uboot_cmd ne '') {
1241 $uboot_cmd =~ s/\$kernel_addr/$uboot_addr{kernel}/g;
1242 $uboot_cmd =~ s/\$ramdisk_addr/$uboot_addr{ramdisk}/g;
1243 $uboot_cmd =~ s/\$fdt_addr/$uboot_addr{fdt}/g;
1245 $exp->send($uboot_cmd . "\n");
1246 $exp->expect(5, "\n") || die "U-Boot command: " . ($! || "timeout");
1250 ### Serial line interaction
1251 if ($interaction && defined $exp) {
1252 # Serial line of the target is available
1253 my $interrupt = 'Ctrl-C';
1254 if ($interactive && !@exiton) {
1255 $interrupt = '"~~."';
1257 print STDERR "novaboot: Serial line interaction (press $interrupt to interrupt)...\n";
1258 $exp->log_stdout(1);
1260 $exp->expect($exiton_timeout, @expect_raw, @exiton) || die("exiton: " . ($! || "timeout"));
1262 my @inputs = ($exp);
1263 my $infile = new IO::File;
1264 $infile->IO::File::fdopen(*STDIN,'r');
1265 my $in_object = Expect->exp_init($infile);
1266 $in_object->set_group($exp);
1269 $in_object->set_seq('~~\.', sub { print STDERR "novaboot: Escape sequence detected\r\n"; undef; });
1270 $in_object->manual_stty(0); # Use raw terminal mode
1272 $in_object->manual_stty(1); # Do not modify terminal settings
1274 push(@inputs, $in_object);
1276 #print Dumper(\@expect_raw);
1277 $exp->expect(undef, @expect_raw) if @expect_raw;
1279 $^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.
1280 Expect::interconnect(@inputs) unless defined($exp->exitstatus);
1285 ## Kill dhcpc or tftpd
1286 if (defined $dhcp_tftp || defined $tftp) {
1287 die("novaboot: This should kill servers on background\n");
1290 # Always finish novaboot output with newline
1291 print "\n" if $final_eol;
1298 novaboot - Boots a locally compiled operating system on a remote
1305 B<novaboot> [option]... [--] script...
1307 B<./script> [option]...
1311 Novaboot makes booting of a locally compiled operating system (OS)
1312 (e.g. NOVA or Linux) on remote targets as simple as running a program
1313 locally. It automates things like copying OS images to a TFTP server,
1314 generation of bootloader configuration files, resetting of target
1315 hardware or redirection of target's serial line to stdin/out. Novaboot
1316 is highly configurable and makes it easy to boot a single image on
1317 different targets or different images on a single target.
1319 Novaboot operation is controlled by configuration files, command line
1320 options and by a so-called novaboot script, which can be thought as a
1321 generalization of bootloader configuration files (see L</"NOVABOOT
1322 SCRIPT SYNTAX">). The typical way of using novaboot is to make the
1323 novaboot script executable and set its first line to I<#!/usr/bin/env
1324 novaboot>. Then, booting a particular OS configuration becomes the
1325 same as executing a local program – the novaboot script.
1327 Novaboot uses configuration files to, among other things, define
1328 command line options needed for different targets. Users typically use
1329 only the B<-t>/B<--target> command line option to select the target.
1330 Internally, this option expands to the pre-configured options.
1331 Novaboot searches configuration files at multiple places, which allows
1332 having per-system, per-user or per-project configurations.
1333 Configuration file syntax is described in section L</"CONFIGURATION
1336 Simple examples of using C<novaboot>:
1342 Running an OS in Qemu can be accomplished by giving the B<--qemu> option.
1345 novaboot --qemu myos
1347 (or C<./myos --qemu> as described above) will run Qemu and make it
1348 boot the configuration specified in the F<myos> script.
1352 Create a bootloader configuration file (currently supported
1353 bootloaders are GRUB, GRUB2, ISOLINUX, Pulsar, and U-Boot) and copy it
1354 with all other files needed for booting to a remote boot server. Then
1355 use a TCP/IP-controlled relay/serial-to-TCP converter to reset the
1356 target and receive its serial output.
1358 ./myos --grub2 --server=192.168.1.1:/tftp --iprelay=192.168.1.2
1360 Alternatively, you can put these switches to the configuration file
1363 ./myos --target mytarget
1367 Run DHCP and TFTP server on developer's machine to boot the target
1372 This usage is useful when no network infrastructure is in place, and
1373 the target is connected directly to developer's box.
1377 Create bootable ISO image.
1379 novaboot --iso -- script1 script2
1381 The created ISO image will have ISOLINUX bootloader installed on it,
1382 and the boot menu will allow selecting between I<script1> and
1383 I<script2> configurations.
1387 =head1 PHASES AND OPTIONS
1389 Novaboot performs its work in several phases. Command like options
1390 described bellow influence the execution of each phase or allow their
1391 skipping. The list of phases (in the execution order) is as follows.
1395 =item 1. L<Configuration reading|/Configuration reading phase>
1397 =item 2. L<Command line processing|/Command line processing phase>
1399 =item 3. L<Script preprocessing|/Script preprocessing phase>
1401 =item 4. L<File generation|/File generation phase>
1403 =item 5. L<Target connection|/Target connection check>
1405 =item 6. L<File deployment|/File deployment phase>
1407 =item 7. L<Target power-on and reset|/Target power-on and reset phase>
1409 =item 8. L<Interaction with the bootloader|/Interaction with the bootloader on the target>
1411 =item 9. L<Target interaction|/Target interaction phase>
1415 Each phase is described in the following sections together with the
1416 command line options that control it.
1418 =head2 Configuration reading phase
1420 After starting, novaboot reads zero or more configuration files. We
1421 describe their content in section L</"CONFIGURATION FILES">. By default, the
1422 configuration is read from multiple locations. First from the system
1423 configuration directory (F</etc/novaboot.d/>), second from the user
1424 configuration file (F<~/.config/novaboot>) and third from F<.novaboot>
1425 files along the path to the current directory. Alternatively, a single
1426 configuration file specified with the B<-c> switch or with the
1427 C<NOVABOOT_CONFIG> environment variable is read. The latter read files
1428 override settings from the former ones.
1430 The system configuration directory is determined by the content of
1431 NOVABOOT_CONFIG_DIR environment variable and defaults to
1432 F</etc/novaboot.d>. Files in this directory with names consisting
1433 solely of English letters, numbers, dashes '-' and underscores '_'
1434 (note that dot '.' is not included) are read in alphabetical order.
1436 Then, the user configuration file is read from
1437 F<$XDG_CONFIG_HOME/novaboot>. If C<$XDG_CONFIG_HOME> environment
1438 variable is not set F<~/.config/novaboot> is read instead.
1440 Finally, novaboot searches for files named F<.novaboot> starting from the
1441 directory of the novaboot script (or working directory, see bellow)
1442 and continuing upwards up to the root directory. The found
1443 configuration files are then read in the opposite order (i.e. from the
1444 root directory downwards). This ordering allows having, for example, a project
1445 specific configuration in F<~/project/.novaboot>.
1447 Note the difference between F<~/.config/novaboot> and F<~/.novaboot>.
1448 The former one is always read, whereas the latter only when novaboot
1449 script or working directory is under the C<$HOME> directory.
1451 In certain cases, the location of the novaboot script cannot be
1452 determined in this early phase. This situation happens either when the script is
1453 read from the standard input or when novaboot is invoked explicitly as
1454 in the example L</"4."> above. In this case, the current working
1455 directory is used as a starting point for configuration file search
1456 instead of the novaboot script directory.
1460 =item -c, --config=I<filename>
1462 Use the specified configuration file instead of the default one(s).
1466 =head2 Command line processing phase
1472 Dump the current configuration to stdout end exit. Useful as an
1473 initial template for a configuration file.
1477 Print short (B<-h>) or long (B<--help>) help.
1479 =item -t, --target=I<target>
1481 This option serves as a user configurable shortcut for other novaboot
1482 options. The effect of this option is the same as specifying the
1483 options stored in the C<%targets> configuration variable under key
1484 I<target>. See also L</"CONFIGURATION FILES">.
1486 When this option is not given, novaboot tries to determine the target
1487 to use from either B<NOVABOOT_TARGET> environment variable or
1488 B<$default_target> configuration file variable.
1490 =item --ssh=I<user@hostname>
1492 Configures novaboot to control the target via C<novaboot-shell>
1493 running remotely via SSH.
1495 Using this option is the same as specifying B<--remote-cmd>,
1496 B<--remote-expect>, B<--server> B<--rsync-flags>, B<--prefix> and
1497 B<--reset-cmd> manually in a way compatible with C<novaboot-shell>.
1498 The server can be configured to provide other, safe bootloader-related
1499 options, to the client. When this happens, novaboot prints them to
1502 Currently, this in an initial experimental implementation. We plan to
1503 change/extend this feature soon!
1507 =head2 Script preprocessing phase
1509 This phase allows modifying the parsed novaboot script before it is
1510 used in the later phases.
1514 =item -a, --append=I<parameters>
1516 Append a string to the first C<load> line in the novaboot script. This option
1517 can be used to append parameters to the kernel's or root task's
1518 command line. This option can appear multiple times.
1522 Use L<Bender|https://github.com/TUD-OS/morbo/blob/master/standalone/bender.c>
1523 chainloader. Bender scans the PCI bus for PCI serial ports and stores
1524 the information about them in the BIOS data area for use by the
1527 =item --chainloader=I<chainloader>
1529 Specifies a chainloader that is loaded before the kernel and other
1530 files specified in the novaboot script. E.g. 'bin/boot/bender
1535 Print the modules to boot and their parameters, after this phase
1536 finishes. Then exit. This is useful for seeing the effect of other
1537 options in this section.
1539 =item -k, --kernel=F<file>
1541 Replace the first word on the first C<load> line in the novaboot
1542 script with F<file>.
1544 =item --scriptmod=I<Perl expression>
1546 When novaboot reads the script, I<Perl expression> is executed for every
1547 line (in $_ variable). For example, C<novaboot
1548 --scriptmod=s/sigma0/omega6/g> replaces every occurrence of I<sigma0>
1549 in the script with I<omega6>.
1551 When this option is present, it overrides I<$script_modifier> variable
1552 from the configuration file, which has the same effect. If this option
1553 is given multiple times all expressions are evaluated in the command
1558 =head2 File generation phase
1560 In this phase, files needed for booting are generated in a so-called
1561 I<build directory> (see L</--build-dir>). In most cases configuration
1562 for a bootloader is generated automatically by novaboot. It is also
1563 possible to generate other files using I<heredoc> or I<"<"> syntax in
1564 novaboot scripts. Finally, novaboot can generate binaries in this phases by
1565 running C<scons> or C<make>.
1569 =item --build-dir=I<directory>
1571 Overrides the default build directory location.
1573 The default build directory location is determined as follows: If the
1574 configuration file defines the C<$builddir> variable, its value is
1575 used. Otherwise, it is the directory that contains the first processed
1578 See also L</BUILDDIR> variable.
1580 =item -g, --grub[=I<filename>]
1582 Generates grub bootloader menu file. If the I<filename> is not
1583 specified, F<menu.lst> is used. The I<filename> is relative to the
1584 build directory (see B<--build-dir>).
1586 =item --grub-preamble=I<prefix>
1588 Specifies the I<preamble> that is at the beginning of the generated
1589 GRUB or GRUB2 config files. This is useful for specifying GRUB's
1592 =item --prefix=I<prefix>
1594 Specifies I<prefix> (e.g. F</srv/tftp>) that is put in front of every
1595 filename in generated bootloader configuration files (or in U-Boot
1598 If the I<prefix> contains string $NAME, it will be replaced with the
1599 name of the novaboot script (see also B<--name>).
1601 If the I<prefix> contains string $BUILDDIR, it will be replaced with
1602 the build directory (see also B<--build-dir>).
1606 Alias for B<--prefix>.
1608 =item --grub2[=I<filename>]
1610 Generate GRUB2 menu entry in I<filename>. If I<filename> is not
1611 specified F<grub.cfg> is used. The content of the menu entry can be
1612 customized with B<--grub-preamble>, B<--grub2-prolog> or
1613 B<--grub_prefix> options.
1615 To use the generated menu entry on your development
1616 machine that uses GRUB2, append the following snippet to
1617 F</etc/grub.d/40_custom> file and regenerate your grub configuration,
1618 i.e. run update-grub on Debian/Ubuntu.
1620 if [ -f /path/to/nul/build/grub.cfg ]; then
1621 source /path/to/nul/build/grub.cfg
1624 =item --grub2-prolog=I<prolog>
1626 Specifies the text that novaboot puts at the beginning of the GRUB2 menu entry.
1628 =item -m, --make[=make command]
1630 Runs C<make> to build files that are not generated by novaboot itself.
1632 =item --name=I<string>
1634 Use the name I<string> instead of the name of the novaboot script.
1635 This name is used for things like a title of grub menu or for the
1636 server directory where the boot files are copied to.
1640 Do not run external commands to generate files (i.e. "<" syntax and
1641 C<run> keyword). This switch does not influence the generation of files
1642 specified with "<<WORD" syntax.
1644 =item -p, --pulsar[=mac]
1646 Generates pulsar bootloader configuration file named F<config-I<mac>>
1647 The I<mac> string is typically a MAC address and defaults to
1650 =item --scons[=scons command]
1652 Runs C<scons> to build files that are not generated by novaboot
1657 Strip I<rom://> prefix from command lines and generated config files.
1658 The I<rom://> prefix is used by NUL. For NRE, it has to be stripped.
1662 Exit novaboot after file generation phase.
1666 =head2 Target connection check
1668 In this phase novaboot connects to target's serial port (if it has
1669 one). If another novaboot user/instance occupies the target, novaboot
1670 exits here with an error message.
1674 =item --amt=I<"[user[:password]@]host[:port]>
1676 Use Intel AMT technology to control the target machine. WS management
1677 is used to powercycle it and Serial-Over-Lan (SOL) for input/output.
1678 The hostname or (IP address) is given by the I<host> parameter. If the
1679 I<password> is not specified, environment variable AMT_PASSWORD is
1680 used. The I<port> specifies a TCP port for SOL. If not specified, the
1681 default is 16992. The default I<user> is admin.
1683 =item --iprelay=I<addr[:port]>
1685 Use TCP/IP relay and serial port to access the target's serial port
1686 and powercycle it. The I<addr> parameter specifies the IP address of
1687 the relay. If I<port> is not specified, it defaults to 23.
1689 Note: This option is supposed to work with HWG-ER02a IP relays.
1691 =item --iprelay-cmd=I<command>
1693 Similar to B<--iprelay> but uses I<command> to talk to the iprelay
1694 rather than direct network connection.
1696 =item -s, --serial[=device]
1698 Target's serial line is connected to host's serial line (device). The
1699 default value for device is F</dev/ttyUSB0>.
1701 The value of this option is exported in NB_NOVABOOT environment
1702 variable to all subprocesses run by C<novaboot>.
1704 =item --stty=I<settings>
1706 Specifies settings passed to C<stty> invoked on the serial line
1707 specified with B<--serial> option. If this option is not given,
1708 C<stty> is called with C<raw -crtscts -onlcr 115200> settings.
1710 =item --remote-cmd=I<cmd>
1712 Command that mediates connection to the target's serial line. For
1713 example C<ssh server 'cu -l /dev/ttyS0'>.
1715 =item --remote-expect=I<string>
1717 Wait for reception of I<string> after establishing the remote
1718 connection. This option is needed when novaboot should wait for
1719 confirmation before deploying files to the target, e.g. to not
1720 overwrite other user's files when they are using the target.
1722 =item --remote-expect-silent=I<string>
1724 The same as B<--remote-expect> except that the remote output is not
1725 echoed to stdout while waiting for the I<string>. Everything after the
1726 matched string is printed to stdout, so you may want to include line
1727 end characters in the I<string> as well.
1729 =item --remote-expect-timeout=I<seconds>
1731 Timeout in seconds for B<--remote-expect> or
1732 B<--remote-expect-seconds>. When negative, waits forever. The default
1737 =head2 File deployment phase
1739 In some setups, it is necessary to copy the files needed for booting
1740 to a particular location, e.g. to a TFTP boot server or to the
1745 =item -d, --dhcp-tftp
1747 Turns your workstation into a DHCP and TFTP server so that the OS can
1748 be booted via PXE BIOS (or similar mechanism) on the test machine
1749 directly connected by a plain Ethernet cable to your workstation.
1751 The DHCP and TFTP servers require root privileges and C<novaboot>
1752 uses C<sudo> command to obtain those. You can put the following to
1753 I</etc/sudoers> to allow running the necessary commands without asking
1756 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
1757 your_login ALL=NOPASSWD: NOVABOOT
1761 Starts a TFTP server on your workstation. This is similar to
1762 B<--dhcp-tftp> except that DHCP server is not started.
1764 The TFTP server requires root privileges and C<novaboot> uses C<sudo>
1765 command to obtain those. You can put the following to I</etc/sudoers>
1766 to allow running the necessary commands without asking for a password.
1768 Cmnd_Alias NOVABOOT = /usr/sbin/in.tftpd --listen --secure -v -v -v --pidfile tftpd.pid *, /usr/bin/pkill --pidfile=tftpd.pid
1769 your_login ALL=NOPASSWD: NOVABOOT
1771 =item --tftp-port=I<port>
1773 Port to run the TFTP server on. Implies B<--tftp>.
1775 =item --netif=I<network interface>
1777 Network interface used to deploy files to the target. The default value is
1778 I<eth0>. This option influences the configuration of the DHCP server started
1779 by B<--dhcp-tftp> and the value that B<$NB_MYIP> get replaced with during
1780 U-Boot conversation.
1782 =item --iso[=filename]
1784 Generates the ISO image that boots NOVA system via GRUB. If no filename
1785 is given, the image is stored under I<NAME>.iso, where I<NAME> is the name
1786 of the novaboot script (see also B<--name>).
1788 =item --server[=[[user@]server:]path]
1790 Copy all files needed for booting to another location. The files will
1791 be copied (by B<rsync> tool) to the directory I<path>. If the I<path>
1792 contains string $NAME, it will be replaced with the name of the
1793 novaboot script (see also B<--name>).
1795 =item --rsync-flags=I<flags>
1797 Specifies I<flags> to append to F<rsync> command line when
1798 copying files as a result of I<--server> option.
1802 If B<--server> is used and its value ends with $NAME, then after
1803 copying the files, a new bootloader configuration file (e.g. menu.lst)
1804 is created at I<path-wo-name>, i.e. the path specified by B<--server>
1805 with $NAME part removed. The content of the file is created by
1806 concatenating all files of the same name from all subdirectories of
1807 I<path-wo-name> found on the "server".
1811 Use Intel AMT technology for IDE redirection. This allows the target
1812 machine to boot from novaboot created ISO image. Implies B<--iso>.
1814 The experimental C<amtider> utility needed by this option can be
1815 obtained from https://github.com/wentasah/amtterm.
1819 =head2 Target power-on and reset phase
1821 At this point, the target is reset (or switched on/off). There are
1822 several ways how this can be accomplished. Resetting a physical target
1823 can currently be accomplished by the following options: B<--amt>,
1824 B<--iprelay>, B<--reset-cmd> and B<--reset-send>.
1830 Switch on/off the target machine and exit. The script (if any) is
1831 completely ignored. Currently, it works only with the following
1832 options: B<--iprelay>, B<--amt>, B<--ssh>.
1834 =item -Q, --qemu[=I<qemu-binary>]
1836 Boot the configuration in qemu. Optionally, the name of qemu binary
1837 can be specified as a parameter.
1839 =item --qemu-append=I<flags>
1841 Append I<flags> to the default qemu flags (QEMU_FLAGS variable or
1842 C<-cpu coreduo -smp 2>).
1844 =item -q, --qemu-flags=I<flags>
1846 Replace the default qemu flags (QEMU_FLAGS variable or C<-cpu coreduo
1847 -smp 2>) with I<flags> specified here.
1849 =item --reset-cmd=I<cmd>
1851 Runs command I<cmd> to reset the target.
1853 =item --reset-send=I<string>
1855 Reset the target by sending the given I<string> to the remote serial
1856 line. "\n" sequences are replaced with the newline character.
1858 =item --no-reset, --reset
1860 Disable/enable resetting of the target.
1864 =head2 Interaction with the bootloader on the target
1868 =item --uboot[=I<prompt>]
1870 Interact with U-Boot bootloader to boot the thing described in the
1871 novaboot script. I<prompt> specifies the U-Boot's prompt (default is
1872 "=> ", other common prompts are "U-Boot> " or "U-Boot# ").
1876 Disable U-Boot interaction previously enabled with B<--uboot>.
1880 Command(s) to send the U-Boot bootloader before loading the images and
1881 booting them. This option can be given multiple times. After sending
1882 commands from each option novaboot waits for U-Boot I<prompt>.
1884 If the command contains string I<$NB_MYIP> then this string is
1885 replaced by IPv4 address of eth0 interface (see also B<--netif>).
1886 Similarly, I<$NB_PREFIX> is replaced with prefix given by B<--prefix>.
1888 See also C<uboot> keyword in L</"NOVABOOT SCRIPT SYNTAX">).
1890 =item --uboot-addr I<name>=I<address>
1892 Load address of U-Boot's C<tftpboot> command for loading I<name>,
1893 where name is one of I<kernel>, I<ramdisk> or I<fdt> (flattened device
1896 The default addresses are ${I<name>_addr_r}, i.e. U-Boot environment
1897 variables used by convention for this purpose.
1899 =item --uboot-cmd=I<command>
1901 Specifies U-Boot command used to execute the OS. If the command
1902 contains strings C<$kernel_addr>, C<$ramdisk_addr>, C<$fdt_addr>,
1903 these are replaced with the addresses configured with B<--uboot-addr>.
1905 The default value is
1907 bootm $kernel_addr $ramdisk_addr $fdt_addr
1909 or the C<UBOOT_CMD> variable if defined in the novaboot script.
1913 =head2 Target interaction phase
1915 In this phase, target's serial output is redirected to stdout and if
1916 stdin is a TTY, it is redirected to the target's serial input allowing
1917 interactive work with the target.
1921 =item --exiton=I<string>
1923 When the I<string> is sent by the target, novaboot exits. This option can
1924 be specified multiple times, in which case novaboot exits whenever
1925 either of the specified strings is sent.
1927 If the I<string> is C<-re>, then the next B<--exiton>'s I<string> is
1928 treated as a regular expression. For example:
1930 --exiton -re --exiton 'error:.*failed'
1932 =item --exiton-re=I<regex>
1934 The same as --exiton -re --exiton I<regex>.
1936 =item --exiton-timeout=I<seconds>
1938 By default B<--exiton> waits for the string match forever. When this
1939 option is specified, "exiton" timeouts after the specifies the number of
1940 seconds and novaboot returns non-zero exit code.
1942 =item -i, --interactive
1944 Setup things for the interactive use of the target. Your terminal will
1945 be switched to raw mode. In raw mode, your local terminal does not
1946 process input in any way (no echoing of entered characters, no
1947 interpretation of special characters). This, among others, means that
1948 Ctrl-C is passed to the target and does not interrupt novaboot. To
1949 exit from novaboot interactive mode type "~~.".
1951 =item --no-interaction, --interaction
1953 Skip resp. force target interaction phase. When skipped, novaboot exits
1954 immediately after the boot is initiated.
1956 =item --expect=I<string>
1958 When the I<string> is received from the target, send the string specified
1959 with the subsequent B<--send*> option to the target.
1961 =item --expect-re=I<regex>
1963 When target's output matches regular expression I<regex>, send the
1964 string specified with the subsequent B<--send*> option to the target.
1966 =item --expect-raw=I<perl-code>
1968 Provides direct control over Perl's Expect module.
1970 =item --send=I<string>
1972 Send I<string> to the target after the previously specified
1973 B<--expect*> was matched in the target's output. The I<string> may
1974 contain escape sequences such as "\n".
1976 Note that I<string> is actually interpreted by Perl, so it can contain
1977 much more that escape sequences. This behavior may change in the
1980 Example: C<--expect='login: ' --send='root\n'>
1982 =item --sendcont=I<string>
1984 Similar to B<--send> but continue expecting more input.
1986 Example: C<--expect='Continue?' --sendcont='yes\n'>
1988 =item --final-eol, --no-final-eol
1990 By default, B<novaboot> always prints an end-of-line character at the
1991 end of its execution in order to ensure that the output of programs
1992 started after novaboot appears at the beginning of the line. When this
1993 is not desired B<--no-final-eol> option can be used to override this
1998 =head1 NOVABOOT SCRIPT SYNTAX
2000 The syntax tries to mimic POSIX shell syntax. The syntax is defined
2001 by the following rules.
2003 Lines starting with "#" and empty lines are ignored.
2005 Lines that end with "\" are concatenated with the following line after
2006 removal of the final "\" and leading whitespace of the following line.
2008 Lines of the form I<VARIABLE=...> (i.e. matching '^[A-Z_]+=' regular
2009 expression) assign values to internal variables. See L</VARIABLES>
2012 Otherwise, the first word on the line defines the meaning of the line.
2013 The following keywords are supported:
2019 These lines represent modules to boot. The
2020 word after C<load> is a file name (relative to the build directory
2021 (see B<--build-dir>) of the module to load and the remaining words are
2022 passed to it as the command line parameters.
2024 When the C<load> line ends with "<<WORD" then the subsequent lines
2025 until the line containing solely WORD are copied literally to the file
2026 named on that line. This is similar to shell's heredoc feature.
2028 When the C<load> line ends with "< CMD" then command CMD is executed
2029 with F</bin/sh> and its standard output is stored in the file named on
2030 that line. The SRCDIR variable in CMD's environment is set to the
2031 absolute path of the directory containing the interpreted novaboot
2036 These lines are similar to C<load> lines. The
2037 file mentioned there is copied to the same place as in the case of C<load>
2038 (e.g. tftp server), but the file is not used in the bootloader
2039 configuration. Such a file can be used by the target for other
2040 purposes than booting, e.g. at OS runtime or for firmware update.
2044 Chainload another bootloader. Instead of loading multiboot modules
2045 identified with C<load> keyword, run another bootloader. This is
2046 currently supported only by pulsar and can be used to load e.g. Grub
2047 as in the example below:
2049 chld boot/grub/i386-pc/core.0
2054 Lines starting with C<run> keyword contain shell commands that are run
2055 during file generation phase. This is the same as the "< CMD" syntax
2056 for C<load> keyboard except that the command's output is not
2057 redirected to a file. The ordering of commands is the same as they
2058 appear in the novaboot script.
2062 These lines represent U-Boot commands that are sent to the target if
2063 B<--uboot> option is given. Having a U-Boot line in the novaboot
2064 script is the same as giving B<--uboot-init> option to novaboot. The
2065 following syntax variants are supported:
2068 uboot[:<timeout>] <string> [> <file>]
2069 uboot[:<timeout>] < <shell> [> <file>]
2071 C<string> is the literal U-Boot command.
2073 The C<uboot> keyword can be suffixed with timeout specification. The
2074 syntax is C<uboot:Ns>, where C<N> is the whole number of seconds. If
2075 the U-Boot command prompt does not appear before the timeout, novaboot
2076 fails. The default timeout is 10 seconds.
2078 In the second variant with the C<<> character the shell code is
2079 executed and its standard output is sent to U-Boot. Example:
2081 uboot < printf "mmc write \$loadaddr 1 %x" $(($(/usr/bin/stat -c%s rootfs.ext4) / 512))
2083 When C<E<gt> file> part is present, the output of the U-Boot command
2084 is written into the given file.
2090 #!/usr/bin/env novaboot
2091 load bzImage console=ttyS0,115200
2092 run make -C buildroot
2093 load rootfs.cpio < gen_cpio buildroot/images/rootfs.cpio "myapp->/etc/init.d/S99myapp"
2095 Example (NOVA User Land - NUL):
2097 #!/usr/bin/env novaboot
2098 WVDESC=Example program
2099 load bin/apps/sigma0.nul S0_DEFAULT script_start:1,1 \
2100 verbose hostkeyb:0,0x60,1,12,2
2101 load bin/apps/hello.nul
2102 load hello.nulconfig <<EOF
2103 sigma0::mem:16 name::/s0/log name::/s0/timer name::/s0/fs/rom ||
2104 rom://bin/apps/hello.nul
2107 This example will load three modules: F<sigma0.nul>, F<hello.nul> and
2108 F<hello.nulconfig>. sigma0 receives some command line parameters and
2109 F<hello.nulconfig> file is generated on the fly from the lines between
2110 C<<<EOF> and C<EOF>.
2112 Example (Zynq system update via U-Boot):
2114 #!/usr/bin/env novaboot
2118 # Write kernel to FAT filesystem on the 1st SD card partition
2119 run mkimage -f uboot-image.its image.ub
2121 uboot:60s tftpboot ${loadaddr} $NB_PREFIX/image.ub
2122 uboot fatwrite mmc 0:1 ${loadaddr} image.ub $filesize
2123 uboot set bootargs console=ttyPS0,115200 root=/dev/mmcblk0p2
2125 # Write root FS image to the 2nd SD card partition
2126 copy rootfs/images/rootfs.ext4
2127 uboot:60s tftpboot ${loadaddr} $NB_PREFIX/rootfs/images/rootfs.ext4
2128 uboot mmc part > mmc-part.txt
2129 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))
2136 The following variables are interpreted in the novaboot script:
2142 Novaboot chdir()s to this directory before file generation phase. The
2143 directory name specified here is relative to the build directory
2144 specified by other means (see L</--build-dir>).
2148 Assigning this variable has the same effect as specifying L</--exiton>
2151 =item HYPERVISOR_PARAMS
2153 Parameters passed to the hypervisor. The default value is "serial", unless
2154 overridden in the configuration file.
2158 The kernel to use instead of the hypervisor specified in the
2159 configuration file with the C<$hypervisor> variable. The value should
2160 contain the name of the kernel image as well as its command line
2161 parameters. If this variable is defined and non-empty, the variable
2162 HYPERVISOR_PARAMS is not used.
2166 If this variable is 1, the system is not booted. This is currently
2167 only implemented for U-Boot bootloader where it is useful for
2168 interacting with the bootloader without booting the system - e.g. for
2173 Use a specific qemu binary (can be overridden with B<-Q>) and flags
2174 when booting this script under qemu. If QEMU_FLAGS variable is also
2175 specified flags specified in QEMU variable are replaced by those in
2180 Use specific qemu flags (can be overridden with B<-q>).
2184 See L</--uboot-cmd>.
2188 Description of the WvTest-compliant program.
2190 =item WVTEST_TIMEOUT
2192 The timeout in seconds for WvTest harness. If no complete line appears
2193 in the test output within the time specified here, the test fails. It
2194 is necessary to specify this for long running tests that produce no
2195 intermediate output.
2199 =head1 CONFIGURATION FILES
2201 Novaboot can read its configuration from one or more files. By
2202 default, novaboot looks for files in F</etc/novaboot.d>, file
2203 F<~/.config/novaboot> and files named F<.novaboot> as described in
2204 L</Configuration reading phase>. Alternatively, configuration file
2205 location can be specified with the B<-c> switch or with the
2206 NOVABOOT_CONFIG environment variable. The configuration file has Perl
2207 syntax (i.e. it is better to put C<1;> as the last line) and should set
2208 values of certain Perl variables. The current configuration can be
2209 dumped with the B<--dump-config> switch. Some configuration variables
2210 can be overridden by environment variables (see below) or by command
2213 Supported configuration variables include:
2219 Build directory location relative to the location of the configuration
2222 =item $default_target
2224 Default target (see below) to use when no target is explicitly
2225 specified with the B<--target> command line option or
2226 B<NOVABOOT_TARGET> environment variable.
2230 Hash of target definitions to be used with the B<--target> option. The
2231 key is the identifier of the target, the value is the string with
2232 command line options. For instance, if the configuration file contains:
2234 $targets{'mybox'} = '--server=boot:/tftproot --serial=/dev/ttyUSB0 --grub',
2236 then the following two commands are equivalent:
2238 ./myos --server=boot:/tftproot --serial=/dev/ttyUSB0 --grub
2243 =head1 ENVIRONMENT VARIABLES
2245 Some options can be specified not only via config file or command line
2246 but also through environment variables. Environment variables override
2247 the values from the configuration file and command line parameters
2248 override the environment variables.
2252 =item NOVABOOT_CONFIG
2254 Name of the novaboot configuration file to use instead of the default
2257 =item NOVABOOT_CONFIG_DIR
2259 Name of the novaboot configuration directory. When not specified
2260 F</etc/novaboot.d> is used.
2262 =item NOVABOOT_TARGET
2264 Name of the novaboot target to use. This overrides the value of
2265 B<$default_target> from the configuration file and can be overridden
2266 with the B<--target> command line option.
2268 =item NOVABOOT_BENDER
2270 Defining this variable has the same effect as using B<--bender>
2277 Michal Sojka <sojka@os.inf.tu-dresden.de>
2279 Latest novaboot version can be found at
2280 L<https://github.com/wentasah/novaboot>.
2284 # LocalWords: novaboot Novaboot NOVABOOT TFTP PXE DHCP filename stty
2285 # LocalWords: chainloader stdout Qemu qemu preprocessing ISOLINUX bootable
2286 # LocalWords: config subprocesses sudo sudoers tftp dhcp IDE stdin
2287 # LocalWords: subdirectories TTY whitespace heredoc POSIX WvTest