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 sub handle_novaboot_server
164 my $xdg_runtime_dir = $ENV{XDG_RUNTIME_DIR} || '/var/run';
165 my $ssh_ctl_path = "${xdg_runtime_dir}/novaboot-%r@%h%p";
167 $remote_cmd = "ssh -tt -M -S '${ssh_ctl_path}' '${val}' console";
168 $remote_expect = "novaboot-shell: Connected";
170 $rsync_flags = "--rsh='ssh -S \'${ssh_ctl_path}\''";
171 ($grub_prefix = $val) =~ s/@.*// if index($val, '@') != -1;
172 $reset_cmd = "ssh -tt -S '${ssh_ctl_path}' '${val}' reset";
174 $target_power_off = sub { system_verbose("ssh -tt -S '${ssh_ctl_path}' '${val}' off"); };
175 $target_power_on = sub { system_verbose("ssh -tt -S '${ssh_ctl_path}' '${val}' on"); };
181 "append|a=s" => \@append,
182 "bender|b" => \$bender,
183 "build-dir=s" => sub { my ($n, $v) = @_; $builddir = File::Spec->rel2abs($v); },
184 "concat" => \$concat,
185 "chainloader=s" => \@chainloaders,
186 "dhcp-tftp|d" => \$dhcp_tftp,
187 "dump" => \$dump_opt,
188 "dump-config" => \$dump_config,
189 "exiton=s" => \@exiton,
190 "exiton-timeout=i"=> \$exiton_timeout,
191 "exiton-re=s" => sub { my ($n, $v) = @_; push(@exiton, '-re', $v); },
192 "expect=s" => \&handle_expect,
193 "expect-re=s" => \&handle_expect,
194 "expect-raw=s" => sub { my ($n, $v) = @_; unshift(@expect_raw, eval($v)); },
195 "final-eol!" => \$final_eol,
196 "gen-only" => \$gen_only,
197 "grub|g:s" => \$grub_config,
198 "grub-preamble=s"=> \$grub_preamble,
199 "prefix|grub-prefix=s" => \$grub_prefix,
200 "grub2:s" => \$grub2_config,
201 "grub2-prolog=s" => \$grub2_prolog,
203 "interaction!" => \$interaction,
204 "iprelay=s" => \$iprelay,
205 "iprelay-cmd=s" => \$iprelay_cmd,
206 "iso:s" => \$iso_image,
207 "kernel|k=s" => \$kernel_opt,
208 "interactive|i" => \$interactive,
209 "name=s" => \$config_name_opt,
210 "make|m:s" => \$make,
211 "netif=s" => \$netif,
212 "no-file-gen" => \$no_file_gen,
215 "pulsar|p:s" => \$pulsar,
216 "pulsar-root=s" => \$pulsar_root,
217 "qemu|Q:s" => \$qemu,
218 "qemu-append=s" => \$qemu_append,
219 "qemu-flags|q=s" => \$qemu_flags_cmd,
220 "remote-cmd=s" => \$remote_cmd,
221 "remote-expect=s"=> \$remote_expect,
222 "remote-expect-silent=s"=> sub { $remote_expect=$_[1]; $remote_expect_silent=1; },
223 "remote-expect-timeout=i"=> \$remote_expect_timeout,
225 "reset-cmd=s" => \$reset_cmd,
226 "reset-send=s" => \$reset_send,
227 "rsync-flags=s" => \$rsync_flags,
228 "scons:s" => \$scons,
229 "scriptmod=s" => \@scriptmod,
230 "send=s" => \&handle_send,
231 "sendcont=s" => \&handle_send,
232 "serial|s:s" => \$serial,
233 "server:s" => \$server,
234 "ssh:s" => \&handle_novaboot_server,
235 "strip-rom" => sub { $rom_prefix = ''; },
238 "tftp-port=i" => \$tftp_port,
239 "uboot:s" => \$uboot,
240 "no-uboot" => sub { undef $uboot; },
241 "uboot-addr=s" => \%uboot_addr,
242 "uboot-cmd=s" => \$uboot_cmd,
243 "uboot-init=s" => sub { push @uboot_init, { command => $_[1] }; },
248 # First process target options
250 my $t = defined($explicit_target) ? $explicit_target : $CFG::default_target;
252 Getopt::Long::Configure(qw/no_ignore_case pass_through/);
254 exists $CFG::targets{$t} or die("Unknown target '$t' (valid targets are: ".join(", ", sort keys(%CFG::targets)).")");
256 undef $explicit_target;
257 my ($ret, $remaining_args) = GetOptionsFromString ($CFG::targets{$t}, ("target|t=s" => \$explicit_target));
258 if (!$ret) { die "Error parsing target $t option"; }
259 push(@target_expanded, @$remaining_args);
260 $t = $explicit_target;
263 my @args = (@target_expanded, @ARGV);
264 print STDERR "novaboot: Effective options: @args\n";
266 Getopt::Long::Configure(qw/no_ignore_case no_pass_through/);
267 GetOptionsFromArray(\@target_expanded, %opt_spec) or die ("Error in target definition");
270 # Then process other command line options - some of them may override
271 # what was specified by the target
272 GetOptions %opt_spec or die("Error in command line arguments");
273 pod2usage(1) if $help;
274 pod2usage(-exitstatus => 0, -verbose => 2) if $man;
276 ### Dump sanitized configuration (if requested)
280 $Data::Dumper::Indent=1;
281 print "# This file is in perl syntax.\n";
282 foreach my $key(sort(keys(%CFG::))) { # See "Symbol Tables" in perlmod(1)
283 if (defined ${$CFG::{$key}}) { print Data::Dumper->Dump([${$CFG::{$key}}], ["*$key"]); }
284 if ( @{$CFG::{$key}}) { print Data::Dumper->Dump([\@{$CFG::{$key}}], ["*$key"]); }
285 if ( %{$CFG::{$key}}) { print Data::Dumper->Dump([\%{$CFG::{$key}}], ["*$key"]); }
291 ### Sanitize configuration
293 if ($interactive && !-t STDIN) {
294 die("novaboot: Interactive mode not supported when not on terminal");
297 if (defined $config_name_opt && scalar(@ARGV) > 1) { die "You cannot use --name with multiple scripts"; }
300 $iso_image //= ''; # IDE-R needs an ISO image
301 if (!defined $amt) { die "Error: --ider requires --amt"; }
305 my %input_opts = ('--iprelay' => \$iprelay,
306 '--iprelay-cmd'=> \$iprelay_cmd,
307 '--serial' => \$serial,
308 '--remote-cmd' => \$remote_cmd,
310 my @opts = grep(defined(${$input_opts{$_}}) , keys %input_opts);
312 die("novaboot: More than one target connection option: ".join(', ', @opts)) if scalar @opts > 1;
316 if (defined $serial) {
317 $serial ||= "/dev/ttyUSB0";
318 $ENV{NB_SERIAL} = $serial;
320 if (defined $grub_config) { $grub_config ||= "menu.lst"; }
321 if (defined $grub2_config) { $grub2_config ||= "grub.cfg"; }
323 ## Parse the novaboot script(s)
328 my ($modules, $variables, $generated, $copy, $chainload, $continuation) = ([], {}, [], []);
329 my $skip_reading = defined($on_opt) || defined($off_opt);
330 while (!$skip_reading && ($_ = <>)) {
331 if ($ARGV ne $last_fn) { # New script
332 die "Missing EOF in $last_fn" if $file;
333 die "Unfinished line in $last_fn" if $continuation;
335 push @scripts, { 'filename' => $ARGV,
336 'modules' => $modules = [],
337 'variables' => $variables = {},
338 'generated' => $generated = [],
339 'copy' => $copy = [],
340 'chainload' => $chainload = [],
345 next if /^#/ || /^\s*$/; # Skip comments and empty lines
347 $_ =~ s/^[[:space:]]*// if ($continuation);
349 if (/\\$/) { # Line continuation
350 $continuation .= substr($_, 0, length($_)-1);
354 if ($continuation) { # Last continuation line
355 $_ = $continuation . $_;
359 foreach my $mod(@scriptmod) { eval $mod; }
361 if ($file && $_ eq $EOF) { # Heredoc end
365 if ($file) { # Heredoc content
366 push @{$file}, "$_\n";
369 if (/^([A-Z_]+)=(.*)$/) { # Internal variable
370 $$variables{$1} = $2;
371 push(@exiton, $2) if ($1 eq "EXITON");
374 sub process_load_copy($) {
375 die("novaboot: '$last_fn' line $.: Missing file name\n") unless /^[^ <]+/;
376 if (/^([^ ]*)(.*?)[[:space:]]*<<([^ ]*)$/) { # Heredoc start
378 push @$generated, {filename => $1, content => $file};
382 if (/^([^ ]*)(.*?)[[:space:]]*< ?(.*)$/) { # Command substitution
383 push @$generated, {filename => $1, command => $3};
388 if (s/^load *//) { # Load line
389 push @$modules, process_load_copy($_);
392 if (s/^copy *//) { # Copy line
393 push @$copy, process_load_copy($_);
396 if (s/^chld *//) { # Chainload line
397 push @$chainload, process_load_copy($_);
400 if (/^run (.*)/) { # run line
401 push @$generated, {command => $1};
404 if (/^uboot(?::([0-9]+)s)? +(< *)?(.*)/) { # uboot line
405 # TODO: If U-Boot supports some interactive menu, it might
406 # make sense to store uboot lines per novaboot script.
407 my ($timeout, $redir, $string, $dest) = ($1, $2, $3);
408 if ($string =~ /(.*) *> *(.*)/) {
412 push @uboot_init, { command => $redir ? "" : $string,
413 system => $redir ? $string : "",
420 die("novaboot: Cannot parse script '$last_fn' line $.. Didn't you forget 'load' keyword?\n");
423 # print Dumper(\@scripts);
425 foreach my $script (@scripts) {
426 $modules = $$script{modules};
427 @$modules[0] =~ s/^[^ ]*/$kernel_opt/ if $kernel_opt;
428 @$modules[0] .= ' ' . join(' ', @append) if @append;
431 if (exists $variables->{KERNEL}) {
432 $kernel = $variables->{KERNEL};
434 if ($CFG::hypervisor) {
435 $kernel = $CFG::hypervisor . " ";
436 if (exists $variables->{HYPERVISOR_PARAMS}) {
437 $kernel .= $variables->{HYPERVISOR_PARAMS};
439 $kernel .= $CFG::hypervisor_params;
443 @$modules = ($kernel, @$modules) if $kernel;
444 @$modules = (@chainloaders, @$modules);
445 @$modules = ("bin/boot/bender", @$modules) if ($bender || defined $ENV{'NOVABOOT_BENDER'});
449 foreach my $script (@scripts) {
450 print join("\n", @{$$script{modules}})."\n";
457 sub generate_configs($$$) {
458 my ($base, $generated, $filename) = @_;
459 if ($base) { $base = "$base/"; };
460 foreach my $g(@$generated) {
461 if (exists $$g{content}) {
462 my $config = $$g{content};
463 my $fn = $$g{filename};
464 open(my $f, '>', $fn) || die("$fn: $!");
465 map { s|\brom://([^ ]*)|$rom_prefix$base$1|g; print $f "$_"; } @{$config};
467 print STDERR "novaboot: Created $fn\n";
468 } elsif (exists $$g{command} && ! $no_file_gen) {
469 $ENV{SRCDIR} = dirname(File::Spec->rel2abs( $filename, $invocation_dir ));
470 if (exists $$g{filename}) {
471 system_verbose("( $$g{command} ) > $$g{filename}");
473 system_verbose($$g{command});
479 sub generate_grub_config($$$$;$)
481 my ($filename, $title, $base, $modules_ref, $preamble) = @_;
482 if ($base) { $base = "$base/"; };
483 open(my $fg, '>', $filename) or die "$filename: $!";
484 print $fg "$preamble\n" if $preamble;
485 print $fg "title $title\n" if $title;
486 #print $fg "root $base\n"; # root doesn't really work for (nd)
488 foreach (@$modules_ref) {
491 my ($kbin, $kcmd) = split(' ', $_, 2);
492 $kcmd = '' if !defined $kcmd;
493 print $fg "kernel ${base}$kbin $kcmd\n";
495 s|\brom://([^ ]*)|$rom_prefix$base$1|g; # Translate rom:// files - needed for vdisk parameter of sigma0
496 print $fg "module $base$_\n";
500 print("novaboot: Created $builddir/$filename\n");
504 sub generate_syslinux_config($$$$)
506 my ($filename, $title, $base, $modules_ref) = @_;
507 if ($base && $base !~ /\/$/) { $base = "$base/"; };
508 open(my $fg, '>', $filename) or die "$filename: $!";
509 print $fg "LABEL $title\n";
510 #TODO print $fg "MENU LABEL $human_readable_title\n";
512 my ($kbin, $kcmd) = split(' ', @$modules_ref[0], 2);
514 if (system("file $kbin|grep 'Linux kernel'") == 0) {
515 my $initrd = @$modules_ref[1];
516 die('To many "load" lines for Linux kernel') if (scalar @$modules_ref > 2);
517 print $fg "LINUX $base$kbin\n";
518 print $fg "APPEND $kcmd\n";
519 print $fg "INITRD $base$initrd\n";
521 print $fg "KERNEL mboot.c32\n";
523 foreach (@$modules_ref) {
524 s|\brom://([^ ]*)|$rom_prefix$base$1|g; # Translate rom:// files - needed for vdisk parameter of sigma0
525 push @append, "$base$_";
526 print $fg "APPEND ".join(' --- ', @append)."\n";
529 #TODO print $fg "TEXT HELP\n";
530 #TODO print $fg "some help here\n";
531 #TODO print $fg "ENDTEXT\n";
533 print("novaboot: Created $builddir/$filename\n");
537 sub generate_grub2_config($$$$;$$)
539 my ($filename, $title, $base, $modules_ref, $preamble, $prolog) = @_;
540 if ($base && substr($base,-1,1) ne '/') { $base = "$base/"; };
541 open(my $fg, '>', $filename) or die "$filename: $!";
542 print $fg "$preamble\n" if $preamble;
543 $title ||= 'novaboot';
544 print $fg "menuentry $title {\n";
545 print $fg "$prolog\n" if $prolog;
547 foreach (@$modules_ref) {
550 my ($kbin, $kcmd) = split(' ', $_, 2);
551 $kcmd = '' if !defined $kcmd;
552 print $fg " multiboot ${base}$kbin $kcmd\n";
555 # GRUB2 doesn't pass filename in multiboot info so we have to duplicate it here
556 $_ = join(' ', ($args[0], @args));
557 s|\brom://|$rom_prefix|g; # We do not need to translate path for GRUB2
558 print $fg " module $base$_\n";
563 print("novaboot: Created $builddir/$filename\n");
567 sub generate_pulsar_config($$$)
569 my ($filename, $modules_ref, $chainload_ref) = @_;
570 open(my $fg, '>', $filename) or die "$filename: $!";
571 print $fg "root $pulsar_root\n" if defined $pulsar_root;
572 if (scalar(@$chainload_ref) > 0) {
573 print $fg "chld $$chainload_ref[0]\n";
577 foreach (@$modules_ref) {
580 ($kbin, $kcmd) = split(' ', $_, 2);
581 $kcmd = '' if !defined $kcmd;
584 s|\brom://|$rom_prefix|g;
585 print $fg "load $_\n";
588 # Put kernel as last - this is needed for booting Linux and has no influence on non-Linux OSes
589 print $fg "exec $kbin $kcmd\n";
592 print("novaboot: Created $builddir/$filename\n");
596 sub shell_cmd_string(@)
598 return join(' ', map((/^[-_=a-zA-Z0-9\/\.\+]+$/ ? "$_" : "'$_'"), @_));
603 print STDERR "novaboot: Running: ".shell_cmd_string(@_)."\n";
605 exit(1); # should not be reached
608 sub system_verbose($)
611 print STDERR "novaboot: Running: $cmd\n";
612 my $ret = system($cmd);
613 if ($ret & 0x007f) { die("Command terminated by a signal"); }
614 if ($ret & 0xff00) {die("Command exit with non-zero exit code"); }
615 if ($ret) { die("Command failure $ret"); }
620 $str =~ s/^\s+|\s+$//g;
626 if (exists $variables->{WVDESC}) {
627 print STDERR "Testing \"$variables->{WVDESC}\" in $last_fn:\n";
628 } elsif ($last_fn =~ /\.wv$/) {
629 print STDERR "Testing \"all\" in $last_fn:\n";
632 ## Connect to the target and check whether it is not occupied
634 # We have to do this before file generation phase, because file
635 # generation is intermixed with file deployment phase and we want to
636 # check whether the target is not used by somebody else before
637 # deploying files. Otherwise, we may rewrite other user's files on a
640 my $exp; # Expect object to communicate with the target over serial line
642 my ($amt_user, $amt_password, $amt_host, $amt_port);
644 if (defined $iprelay || defined $iprelay_cmd) {
645 if (defined $iprelay) {
647 $iprelay =~ /([.0-9]+)(:([0-9]+))?/;
650 my $paddr = sockaddr_in($port, inet_aton($addr));
651 my $proto = getprotobyname('tcp');
652 socket($IPRELAY, PF_INET, SOCK_STREAM, $proto) || die "socket: $!";
653 print STDERR "novaboot: Connecting to IP relay... ";
654 connect($IPRELAY, $paddr) || die "connect: $!";
655 print STDERR "done\n";
656 $exp = Expect->init(\*$IPRELAY);
659 if (defined $iprelay_cmd) {
660 print STDERR "novaboot: Running: $iprelay_cmd\n";
663 $exp->spawn($iprelay_cmd);
667 print $exp "\xFF\xF6"; # AYT
668 my $connected = $exp->expect(20, # Timeout in seconds
669 '<iprelayd: connected>',
670 '-re', '<WEB51 HW[^>]*>')
671 || die "iprelay connection: " . ($! || "timeout");
676 my ($relay, $onoff) = @_;
677 die unless ($relay == 1 || $relay == 2);
679 my $cmd = ($relay == 1 ? 0x5 : 0x6) | ($onoff ? 0x20 : 0x10);
680 return "\xFF\xFA\x2C\x32".chr($cmd)."\xFF\xF0";
684 my ($relay, $onoff) = @_;
685 die unless ($relay == 1 || $relay == 2);
686 my $cmd = ($relay == 1 ? 0xdf : 0xbf) | ($onoff ? 0x00 : 0xff);
687 return "\xFF\xFA\x2C\x97".chr($cmd)."\xFF\xF0";
691 my ($relay, $onoff, $can_giveup) = @_;
692 my $confirmation = '';
694 print $exp relaycmd($relay, $onoff);
695 my $confirmed = $exp->expect(20, # Timeout in seconds
696 relayconf($relay, $onoff))
697 || die "iprelay command: " . ($! || "timeout");
700 print("Relay confirmation timeout - ignoring\n");
702 die "Relay confirmation timeout";
708 $target_reset = sub {
709 relay(2, 1, 1); # Reset the machine
714 $target_power_off = sub {
715 relay(1, 1); # Press power button
716 usleep(6000000); # Long press to switch off
720 $target_power_on = sub {
721 relay(1, 1); # Press power button
722 usleep(100000); # Short press
728 system_verbose("stty -F $serial $stty");
729 open($CONN, "+<", $serial) || die "open $serial: $!";
730 $exp = Expect->init(\*$CONN);
732 elsif ($remote_cmd) {
733 print STDERR "novaboot: Running: $remote_cmd\n";
734 $exp = Expect->spawn($remote_cmd);
736 elsif (defined $amt) {
737 require LWP::UserAgent;
738 require LWP::Authen::Digest;
741 my ($host, $username, $password, $schema, $className, $pstate) = @_;
742 #AMT numbers for PowerStateChange (MNI => bluescreen on windows;-)
743 my %pstates = ("on" => 2,
750 <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">
751 <s:Header><a:To>http://$host:16992/wsman</a:To>
752 <w:ResourceURI s:mustUnderstand="true">$schema</w:ResourceURI>
753 <a:ReplyTo><a:Address s:mustUnderstand="true">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address></a:ReplyTo>
754 <a:Action s:mustUnderstand="true">$schema$className</a:Action>
755 <w:MaxEnvelopeSize s:mustUnderstand="true">153600</w:MaxEnvelopeSize>
756 <a:MessageID>uuid:709072C9-609C-4B43-B301-075004043C7C</a:MessageID>
757 <w:Locale xml:lang="en-US" s:mustUnderstand="false" />
758 <w:OperationTimeout>PT60.000S</w:OperationTimeout>
759 <w:SelectorSet><w:Selector Name="Name">Intel(r) AMT Power Management Service</w:Selector></w:SelectorSet>
761 <p:RequestPowerStateChange_INPUT xmlns:p="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_PowerManagementService">
762 <p:PowerState>$pstates{$pstate}</p:PowerState>
763 <p:ManagedElement><a:Address>http://$host:16992/wsman</a:Address>
764 <a:ReferenceParameters><w:ResourceURI>http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ComputerSystem</w:ResourceURI>
765 <w:SelectorSet><w:Selector Name="Name">ManagedSystem</w:Selector></w:SelectorSet>
766 </a:ReferenceParameters></p:ManagedElement>
767 </p:RequestPowerStateChange_INPUT>
768 </s:Body></s:Envelope>
773 my ($host, $username, $password, $content) = @_;
775 my $ua = LWP::UserAgent->new();
776 $ua->agent("novaboot");
778 my $req = HTTP::Request->new(POST => "http://$host:16992/wsman");
779 my $res = $ua->request($req);
780 die ("Unexpected AMT response: " . $res->status_line) unless $res->code == 401;
782 my ($realm) = $res->header("WWW-Authenticate") =~ /Digest realm="(.*?)"/;
783 $ua->credentials("$host:16992", $realm, $username => $password);
786 $req = HTTP::Request->new(POST => "http://$host:16992/wsman");
787 $req->content_type('application/x-www-form-urlencoded');
788 $req->content($content);
789 $res = $ua->request($req);
790 die ("AMT power change request failed: " . $res->status_line) unless $res->is_success;
791 $res->content() =~ /<g:ReturnValue>(\d+)<\/g:ReturnValue>/;
796 my ($host, $username, $password, $pstate)=@_;
797 my $schema="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_PowerManagementService";
798 my $className="/RequestPowerStateChange";
799 my $content = genXML($host, $username, $password ,$schema, $className, $pstate);
800 return sendPOST($host, $username, $password, $content);
803 ($amt_user,$amt_password,$amt_host,$amt_port) = ($amt =~ /(?:(.*?)(?::(.*))?@)?([^:]*)(?::([0-9]*))?/);;
804 $amt_user ||= "admin";
805 $amt_password ||= $ENV{'AMT_PASSWORD'} || die "AMT password not specified";
806 $amt_host || die "AMT host not specified";
810 $target_power_off = sub {
812 my $result = powerChange($amt_host,$amt_user,$amt_password, "off");
813 die "AMT power off failed (ReturnValue $result)" if $result != 0;
816 $target_power_on = sub {
817 my $result = powerChange($amt_host,$amt_user,$amt_password, "on");
818 die "AMT power on failed (ReturnValue $result)" if $result != 0;
821 $target_reset = sub {
822 my $result = powerChange($amt_host,$amt_user,$amt_password, "reset");
824 print STDERR "Warning: Cannot reset $amt_host, trying power on. ";
825 $result = powerChange($amt_host,$amt_user,$amt_password, "on");
827 die "AMT reset failed (ReturnValue $result)" if $result != 0;
830 my $cmd = "amtterm -u $amt_user -p $amt_password $amt_host $amt_port";
831 print STDERR "novaboot: Running: $cmd\n" =~ s/\Q$amt_password\E/???/r;
832 $exp = Expect->spawn($cmd);
833 $exp->expect(10, "RUN_SOL") || die "Expect for 'RUN_SOL': " . ($! || "timeout");
837 if ($remote_expect) {
838 $exp || die("No serial line connection");
839 my $log = $exp->log_stdout;
840 if (defined $remote_expect_silent) {
843 $exp->expect($remote_expect_timeout >= 0 ? $remote_expect_timeout : undef,
844 $remote_expect) || die "Expect for '$remote_expect':" . ($! || "timeout");;
845 if (defined $remote_expect_silent) {
846 $exp->log_stdout($log);
847 print $exp->after() if $log;
851 if (defined $reset_cmd) {
852 $target_reset = sub {
853 system_verbose($reset_cmd);
857 if (defined $reset_send) {
858 $target_reset = sub {
859 $reset_send =~ s/\\n/\n/g;
860 $exp->send($reset_send);
864 if (defined $on_opt && defined $target_power_on) {
868 if (defined $off_opt && defined $target_power_off) {
869 print STDERR "novaboot: Switching the target off...\n";
870 &$target_power_off();
874 $builddir ||= dirname(File::Spec->rel2abs( ${$scripts[0]}{filename})) if scalar @scripts;
875 if (defined $builddir) {
876 chdir($builddir) or die "Can't change directory to $builddir: $!";
877 print STDERR "novaboot: Entering directory `$builddir'\n";
879 $builddir = $invocation_dir;
882 ## File generation phase
883 my (%files_iso, $menu_iso, $filename);
884 my $config_name = '';
887 foreach my $script (@scripts) {
888 $filename = $$script{filename};
889 $modules = $$script{modules};
890 $generated = $$script{generated};
891 $variables = $$script{variables};
893 ($config_name = $filename) =~ s#.*/##;
894 $config_name = $config_name_opt if (defined $config_name_opt);
896 if (exists $variables->{BUILDDIR}) {
897 $builddir = File::Spec->rel2abs($variables->{BUILDDIR});
898 chdir($builddir) or die "Can't change directory to $builddir: $!";
899 print STDERR "novaboot: Entering directory `$builddir'\n";
903 $prefix = $grub_prefix;
904 $prefix =~ s/\$NAME/$config_name/;
905 $prefix =~ s/\$BUILDDIR/$builddir/;
907 # TODO: use $grub_prefix as first parameter if some switch is given
908 generate_configs('', $generated, $filename);
910 ### Generate bootloader configuration files
911 my @bootloader_configs;
912 push @bootloader_configs, generate_grub_config($grub_config, $config_name, $prefix, $modules, $grub_preamble) if (defined $grub_config);
913 push @bootloader_configs, generate_grub2_config($grub2_config, $config_name, $prefix, $modules, $grub_preamble, $grub2_prolog) if (defined $grub2_config);
914 push @bootloader_configs, generate_pulsar_config('config-'.($pulsar||'novaboot'), $modules, $chainload) if (defined $pulsar);
916 ### Run scons or make
919 push @all, @$modules;
921 push @all, @$chainload;
922 my @files = map({ ($file) = m/([^ ]*)/; $file; } @all);
924 # Filter-out generated files
925 my @to_build = grep({ my $file = $_; !scalar(grep($file eq ($$_{filename} || ''), @$generated)) } @files);
927 system_verbose($scons || $CFG::scons." ".join(" ", @to_build)) if (defined $scons);
928 system_verbose($make || $CFG::make ." ".join(" ", @to_build)) if (defined $make);
931 ### Copy files (using rsync)
932 if (defined $server && !defined($gen_only)) {
933 (my $real_server = $server) =~ s/\$NAME/$config_name/;
935 my ($hostname, $path) = split(":", $real_server, 2);
936 if (! defined $path) {
940 my $files = join(" ", map({ ($file) = m/([^ ]*)/; $file; } ( @$modules, @bootloader_configs, @$copy)));
941 map({ my $file = (split)[0]; die "$file: $!" if ! -f $file; } @$modules);
942 my $istty = -t STDOUT && ($ENV{'TERM'} || 'dumb') ne 'dumb';
943 my $progress = $istty ? "--progress" : "";
945 system_verbose("rsync $progress -RLp $rsync_flags $files $real_server");
946 if ($server =~ m|/\$NAME$| && $concat) {
947 my $cmd = join("; ", map { "( cd $path/.. && cat */$_ > $_ )" } @bootloader_configs);
948 system_verbose($hostname ? "ssh $hostname '$cmd'" : $cmd);
953 ### Prepare ISO image generation
954 if (defined $iso_image) {
955 generate_configs("(cd)", $generated, $filename);
957 generate_syslinux_config(\$menu, $config_name, "/", $modules);
958 $menu_iso .= "$menu\n";
959 map { ($file,undef) = split; $files_iso{$file} = 1; } @$modules;
963 ## Generate ISO image
964 if (defined $iso_image) {
965 system_verbose("mkdir -p isolinux");
968 if (-f '/usr/lib/ISOLINUX/isolinux.bin') {
969 # Newer ISOLINUX version
970 @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);
972 # Older ISOLINUX version
973 @files = qw(/usr/lib/syslinux/isolinux.bin /usr/lib/syslinux/mboot.c32 /usr/lib/syslinux/menu.c32);
975 system_verbose("cp @files isolinux");
976 open(my $fh, ">isolinux/isolinux.cfg");
978 print $fh "TIMEOUT 50\n";
979 print $fh "DEFAULT menu\n";
981 print $fh "DEFAULT $config_name\n";
983 print $fh "$menu_iso";
986 my $files = join(" ", map("$_=$_", (keys(%files_iso), 'isolinux/isolinux.cfg', map(s|.*/|isolinux/|r, @files))));
987 $iso_image ||= "$config_name.iso";
989 # Note: We use -U flag below to "Allow 'untranslated' filenames,
990 # completely violating the ISO9660 standards". Without this
991 # option, isolinux is not able to read files names for example
993 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");
994 print("ISO image created: $builddir/$iso_image\n");
997 exit(0) if defined $gen_only;
999 ## Boot the system using various methods and send serial output to stdout
1001 if (scalar(@scripts) > 1 && ( defined $dhcp_tftp || defined $serial || defined $iprelay)) {
1002 die "You cannot do this with multiple scripts simultaneously";
1005 if ($variables->{WVTEST_TIMEOUT}) {
1006 print STDERR "wvtest: timeout ", $variables->{WVTEST_TIMEOUT}, "\n";
1011 if (defined $qemu) {
1013 $qemu ||= $variables->{QEMU} || $CFG::qemu;
1014 my @qemu_flags = split(" ", $qemu);
1015 $qemu = shift(@qemu_flags);
1017 @qemu_flags = split(/ +/, trim($variables->{QEMU_FLAGS})) if exists $variables->{QEMU_FLAGS};
1018 @qemu_flags = split(/ +/, trim($qemu_flags_cmd)) if $qemu_flags_cmd;
1019 push(@qemu_flags, split(/ +/, trim($qemu_append || '')));
1021 if (defined $iso_image) {
1022 # Boot NOVA with grub (and test the iso image)
1023 push(@qemu_flags, ('-cdrom', $iso_image));
1025 # Boot NOVA without GRUB
1027 # Non-patched qemu doesn't like commas, but NUL can live with pluses instead of commans
1028 foreach (@$modules) {s/,/+/g;}
1029 generate_configs("", $generated, $filename);
1031 if (scalar @$modules) {
1032 my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);
1033 $kcmd = '' if !defined $kcmd;
1035 @$modules = map { if (/\.dtb$/) { $dtb=$_; (); } else { $_ } } @$modules;
1036 my $initrd = join ",", @$modules;
1038 push(@qemu_flags, ('-kernel', $kbin, '-append', $kcmd));
1039 push(@qemu_flags, ('-initrd', $initrd)) if $initrd;
1040 push(@qemu_flags, ('-dtb', $dtb)) if $dtb;
1043 if (!grep /^-serial$/, @qemu_flags) {
1044 push(@qemu_flags, qw(-serial stdio)); # Redirect serial output (for collecting test restuls)
1046 unshift(@qemu_flags, ('-name', $config_name));
1047 print STDERR "novaboot: Running: ".shell_cmd_string($qemu, @qemu_flags)."\n";
1048 $exp = Expect->spawn(($qemu, @qemu_flags)) || die("exec() failed: $!");
1051 ### Local DHCPD and TFTPD
1053 my ($dhcpd_pid, $tftpd_pid);
1055 $tftp=1 if $tftp_port;
1057 if (defined $dhcp_tftp)
1059 generate_configs("(nd)", $generated, $filename);
1060 system_verbose('mkdir -p tftpboot');
1061 generate_grub_config("tftpboot/os-menu.lst", $config_name, "(nd)", \@$modules, "timeout 0");
1062 open(my $fh, '>', 'dhcpd.conf');
1063 my $mac = `cat /sys/class/net/$netif/address`;
1065 print $fh "subnet 10.23.23.0 netmask 255.255.255.0 {
1066 range 10.23.23.10 10.23.23.100;
1067 filename \"bin/boot/grub/pxegrub.pxe\";
1068 next-server 10.23.23.1;
1071 hardware ethernet $mac;
1072 fixed-address 10.23.23.1;
1075 system_verbose("sudo ip a add 10.23.23.1/24 dev $netif;
1076 sudo ip l set dev $netif up;
1077 sudo touch dhcpd.leases");
1079 # We run servers by forking ourselves, because the servers end up
1080 # in our process group and get killed by signals sent to the
1081 # process group (e.g. Ctrl-C on terminal).
1082 $dhcpd_pid = fork();
1083 exec_verbose("sudo dhcpd -d -cf dhcpd.conf -lf dhcpd.leases -pf dhcpd.pid") if ($dhcpd_pid == 0);
1086 if (defined $dhcp_tftp || defined $tftp) {
1088 # Unfortunately, tftpd requires root privileges even with
1089 # non-privileged (>1023) port due to initgroups().
1090 system_verbose("sudo in.tftpd --listen --secure -v -v -v --pidfile tftpd.pid --address :$tftp_port $builddir");
1092 # Kill server when we die
1093 $SIG{__DIE__} = sub { system_verbose('sudo pkill --pidfile=dhcpd.pid') if (defined $dhcp_tftp);
1094 system_verbose('sudo pkill --pidfile=tftpd.pid'); };
1096 # We have to kill tftpd explicitely, because it is not in our process group
1097 $SIG{INT} = sub { system_verbose('sudo pkill --pidfile=tftpd.pid'); exit(0); };
1101 if (defined $ider) {
1102 my $ider_cmd= "amtider -c $iso_image -u $amt_user -p $amt_password $amt_host $amt_port" ;
1103 print STDERR "novaboot: Running: $ider_cmd\n" =~ s/\Q$amt_password\E/???/r;
1104 my $ider_pid = fork();
1105 if ($ider_pid == 0) {
1107 die "IDE redirection failed";
1109 # FIXME: This collides with --tftp option. Hopefully, nobody needs
1110 # to use both simultaneously.
1111 $SIG{__DIE__} = sub { system_verbose('kill $ider_pid'); };
1114 ### Reset target (IP relay, AMT, ...)
1116 if (defined $target_reset && $reset) {
1117 print STDERR "novaboot: Reseting the test box... ";
1119 print STDERR "done\n";
1121 # We don't want to output anything printed by the target
1122 # before reset so we clear the buffers now. This is, however,
1123 # not ideal because we may loose some data that were sent
1124 # after the reset. If this is a problem, one should reset and
1125 # connect to serial line in atomic manner. For example, if
1126 # supported by hardware, use --remote-cmd 'sterm -d ...' and
1127 # do not use separate --reset-cmd.
1128 my $log = $exp->log_stdout;
1129 $exp->log_stdout(0);
1130 $exp->expect(0); # Read data from target
1131 $exp->clear_accum(); # Clear the read data
1132 $exp->log_stdout($log);
1136 ### U-boot conversation
1137 if (defined $uboot) {
1138 my $uboot_prompt = $uboot || '=> ';
1139 print STDERR "novaboot: Waiting for U-Boot prompt...\n";
1140 $exp || die("No serial line connection");
1141 $exp->log_stdout(1);
1142 #$exp->exp_internal(1);
1144 [qr/Hit any key to stop autoboot:/, sub { $exp->send("\n"); exp_continue; }],
1145 $uboot_prompt) || die "No U-Boot prompt deteceted";
1146 foreach my $cmdspec (@uboot_init) {
1147 my ($cmd, $timeout);
1148 die "Internal error - please report a bug" unless ref($cmdspec) eq "HASH";
1150 if ($cmdspec->{system}) {
1151 $cmd = `$cmdspec->{system}`;
1153 $cmd = $cmdspec->{command};
1155 $timeout = $cmdspec->{timeout} // 10;
1157 if ($cmd =~ /\$NB_MYIP/) {
1158 my $ip = (grep /inet /, `ip addr show $netif`)[0] || die "Problem determining IP address of $netif";
1159 $ip =~ s/\s*inet ([0-9.]*).*/$1/;
1160 $cmd =~ s/\$NB_MYIP/$ip/g;
1162 if ($cmd =~ /\$NB_PREFIX/) {
1165 $cmd =~ s/\$NB_PREFIX/$p/g;
1168 $exp->send("$cmd\n");
1170 my ($matched_pattern_position, $error,
1171 $successfully_matching_string,
1172 $before_match, $after_match) =
1173 $exp->expect($timeout, $uboot_prompt);
1174 die "No U-Boot prompt: $error" if $error;
1176 if ($cmdspec->{dest}) {
1177 open(my $fh, ">", $cmdspec->{dest}) or die "Cannot open '$cmdspec->{dest}': $!";
1178 print $fh $before_match;
1183 # Load files if there are some load lines in the script
1184 if (scalar(@$modules) > 0 && !$variables->{NO_BOOT}) {
1185 my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);
1187 @$modules = map { if (/\.dtb$/) { $dtb=$_; (); } else { $_ } } @$modules;
1188 my $initrd = shift @$modules;
1190 if (defined $kbin) {
1191 die "No '--uboot-addr kernel' given" unless $uboot_addr{kernel};
1192 $exp->send("tftpboot $uboot_addr{kernel} $prefix$kbin\n");
1194 [qr/##/, sub { exp_continue; }],
1195 $uboot_prompt) || die "Kernel load: " . ($! || "timeout");
1198 die "No '--uboot-addr fdt' given" unless $uboot_addr{fdt};
1199 $exp->send("tftpboot $uboot_addr{fdt} $prefix$dtb\n");
1201 [qr/##/, sub { exp_continue; }],
1202 $uboot_prompt) || die "Device tree load: " . ($! || "timeout");
1204 $uboot_addr{fdt} = '';
1206 if (defined $initrd) {
1207 die "No '--uboot-addr ramdisk' given" unless $uboot_addr{ramdisk};
1208 $exp->send("tftpboot $uboot_addr{ramdisk} $prefix$initrd\n");
1210 [qr/##/, sub { exp_continue; }],
1211 $uboot_prompt) || die "Initrd load: " . ($! || "timeout");
1213 $uboot_addr{ramdisk} = '-';
1217 $exp->send("setenv bootargs $kcmd\n");
1218 $exp->expect(5, $uboot_prompt) || die "U-Boot prompt: " . ($! || "timeout");
1221 $uboot_cmd //= $variables->{UBOOT_CMD} // 'bootm $kernel_addr $ramdisk_addr $fdt_addr';
1222 if (!$variables->{NO_BOOT} && $uboot_cmd ne '') {
1223 $uboot_cmd =~ s/\$kernel_addr/$uboot_addr{kernel}/g;
1224 $uboot_cmd =~ s/\$ramdisk_addr/$uboot_addr{ramdisk}/g;
1225 $uboot_cmd =~ s/\$fdt_addr/$uboot_addr{fdt}/g;
1227 $exp->send($uboot_cmd . "\n");
1228 $exp->expect(5, "\n") || die "U-Boot command: " . ($! || "timeout");
1232 ### Serial line interaction
1233 if ($interaction && defined $exp) {
1234 # Serial line of the target is available
1235 my $interrupt = 'Ctrl-C';
1236 if ($interactive && !@exiton) {
1237 $interrupt = '"~~."';
1239 print STDERR "novaboot: Serial line interaction (press $interrupt to interrupt)...\n";
1240 $exp->log_stdout(1);
1242 $exp->expect($exiton_timeout, @expect_raw, @exiton) || die("exiton: " . ($! || "timeout"));
1244 my @inputs = ($exp);
1245 my $infile = new IO::File;
1246 $infile->IO::File::fdopen(*STDIN,'r');
1247 my $in_object = Expect->exp_init($infile);
1248 $in_object->set_group($exp);
1251 $in_object->set_seq('~~\.', sub { print STDERR "novaboot: Escape sequence detected\r\n"; undef; });
1252 $in_object->manual_stty(0); # Use raw terminal mode
1254 $in_object->manual_stty(1); # Do not modify terminal settings
1256 push(@inputs, $in_object);
1258 #print Dumper(\@expect_raw);
1259 $exp->expect(undef, @expect_raw) if @expect_raw;
1261 $^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.
1262 Expect::interconnect(@inputs) unless defined($exp->exitstatus);
1267 ## Kill dhcpc or tftpd
1268 if (defined $dhcp_tftp || defined $tftp) {
1269 die("novaboot: This should kill servers on background\n");
1272 # Always finish novaboot output with newline
1273 print "\n" if $final_eol;
1280 novaboot - Boots a locally compiled operating system on a remote
1287 B<novaboot> [option]... [--] script...
1289 B<./script> [option]...
1293 Novaboot makes booting of a locally compiled operating system (OS)
1294 (e.g. NOVA or Linux) on remote targets as simple as running a program
1295 locally. It automates things like copying OS images to a TFTP server,
1296 generation of bootloader configuration files, resetting of target
1297 hardware or redirection of target's serial line to stdin/out. Novaboot
1298 is highly configurable and makes it easy to boot a single image on
1299 different targets or different images on a single target.
1301 Novaboot operation is controlled by configuration files, command line
1302 options and by a so-called novaboot script, which can be thought as a
1303 generalization of bootloader configuration files (see L</"NOVABOOT
1304 SCRIPT SYNTAX">). The typical way of using novaboot is to make the
1305 novaboot script executable and set its first line to I<#!/usr/bin/env
1306 novaboot>. Then, booting a particular OS configuration becomes the
1307 same as executing a local program – the novaboot script.
1309 Novaboot uses configuration files to, among other things, define
1310 command line options needed for different targets. Users typically use
1311 only the B<-t>/B<--target> command line option to select the target.
1312 Internally, this option expands to the pre-configured options.
1313 Novaboot searches configuration files at multiple places, which allows
1314 having per-system, per-user or per-project configurations.
1315 Configuration file syntax is described in section L</"CONFIGURATION
1318 Simple examples of using C<novaboot>:
1324 Running an OS in Qemu can be accomplished by giving the B<--qemu> option.
1327 novaboot --qemu myos
1329 (or C<./myos --qemu> as described above) will run Qemu and make it
1330 boot the configuration specified in the F<myos> script.
1334 Create a bootloader configuration file (currently supported
1335 bootloaders are GRUB, GRUB2, ISOLINUX, Pulsar, and U-Boot) and copy it
1336 with all other files needed for booting to a remote boot server. Then
1337 use a TCP/IP-controlled relay/serial-to-TCP converter to reset the
1338 target and receive its serial output.
1340 ./myos --grub2 --server=192.168.1.1:/tftp --iprelay=192.168.1.2
1342 Alternatively, you can put these switches to the configuration file
1345 ./myos --target mytarget
1349 Run DHCP and TFTP server on developer's machine to boot the target
1354 This usage is useful when no network infrastructure is in place, and
1355 the target is connected directly to developer's box.
1359 Create bootable ISO image.
1361 novaboot --iso -- script1 script2
1363 The created ISO image will have ISOLINUX bootloader installed on it,
1364 and the boot menu will allow selecting between I<script1> and
1365 I<script2> configurations.
1369 =head1 PHASES AND OPTIONS
1371 Novaboot performs its work in several phases. Command like options
1372 described bellow influence the execution of each phase or allow their
1373 skipping. The list of phases (in the execution order) is as follows.
1377 =item 1. L<Configuration reading|/Configuration reading phase>
1379 =item 2. L<Command line processing|/Command line processing phase>
1381 =item 3. L<Script preprocessing|/Script preprocessing phase>
1383 =item 4. L<File generation|/File generation phase>
1385 =item 5. L<Target connection|/Target connection check>
1387 =item 6. L<File deployment|/File deployment phase>
1389 =item 7. L<Target power-on and reset|/Target power-on and reset phase>
1391 =item 8. L<Interaction with the bootloader|/Interaction with the bootloader on the target>
1393 =item 9. L<Target interaction|/Target interaction phase>
1397 Each phase is described in the following sections together with the
1398 command line options that control it.
1400 =head2 Configuration reading phase
1402 After starting, novaboot reads zero or more configuration files. We
1403 describe their content in section L</"CONFIGURATION FILES">. By default, the
1404 configuration is read from multiple locations. First from the system
1405 configuration directory (F</etc/novaboot.d/>), second from the user
1406 configuration file (F<~/.config/novaboot>) and third from F<.novaboot>
1407 files along the path to the current directory. Alternatively, a single
1408 configuration file specified with the B<-c> switch or with the
1409 C<NOVABOOT_CONFIG> environment variable is read. The latter read files
1410 override settings from the former ones.
1412 The system configuration directory is determined by the content of
1413 NOVABOOT_CONFIG_DIR environment variable and defaults to
1414 F</etc/novaboot.d>. Files in this directory with names consisting
1415 solely of English letters, numbers, dashes '-' and underscores '_'
1416 (note that dot '.' is not included) are read in alphabetical order.
1418 Then, the user configuration file is read from
1419 F<$XDG_CONFIG_HOME/novaboot>. If C<$XDG_CONFIG_HOME> environment
1420 variable is not set F<~/.config/novaboot> is read instead.
1422 Finally, novaboot searches for files named F<.novaboot> starting from the
1423 directory of the novaboot script (or working directory, see bellow)
1424 and continuing upwards up to the root directory. The found
1425 configuration files are then read in the opposite order (i.e. from the
1426 root directory downwards). This ordering allows having, for example, a project
1427 specific configuration in F<~/project/.novaboot>.
1429 Note the difference between F<~/.config/novaboot> and F<~/.novaboot>.
1430 The former one is always read, whereas the latter only when novaboot
1431 script or working directory is under the C<$HOME> directory.
1433 In certain cases, the location of the novaboot script cannot be
1434 determined in this early phase. This situation happens either when the script is
1435 read from the standard input or when novaboot is invoked explicitly as
1436 in the example L</"4."> above. In this case, the current working
1437 directory is used as a starting point for configuration file search
1438 instead of the novaboot script directory.
1442 =item -c, --config=I<filename>
1444 Use the specified configuration file instead of the default one(s).
1448 =head2 Command line processing phase
1454 Dump the current configuration to stdout end exit. Useful as an
1455 initial template for a configuration file.
1459 Print short (B<-h>) or long (B<--help>) help.
1461 =item -t, --target=I<target>
1463 This option serves as a user configurable shortcut for other novaboot
1464 options. The effect of this option is the same as specifying the
1465 options stored in the C<%targets> configuration variable under key
1466 I<target>. See also L</"CONFIGURATION FILES">.
1468 When this option is not given, novaboot tries to determine the target
1469 to use from either B<NOVABOOT_TARGET> environment variable or
1470 B<$default_target> configuration file variable.
1472 =item --ssh=I<user@hostname>
1474 Configures novaboot to control the target via C<novaboot-shell>
1475 running remotely via SSH.
1477 Using this option is the same as specifying B<--remote-cmd>,
1478 B<--remote-expect>, B<--server> B<--rsync-flags>, B<--prefix> and
1479 B<--reset-cmd> manually in a way compatible with V<novaboot-shell>.
1481 Currently, this in an initial experimental implementation. We plan to
1482 change/extend this feature soon!
1486 =head2 Script preprocessing phase
1488 This phase allows modifying the parsed novaboot script before it is
1489 used in the later phases.
1493 =item -a, --append=I<parameters>
1495 Append a string to the first C<load> line in the novaboot script. This option
1496 can be used to append parameters to the kernel's or root task's
1497 command line. This option can appear multiple times.
1501 Use L<Bender|https://github.com/TUD-OS/morbo/blob/master/standalone/bender.c>
1502 chainloader. Bender scans the PCI bus for PCI serial ports and stores
1503 the information about them in the BIOS data area for use by the
1506 =item --chainloader=I<chainloader>
1508 Specifies a chainloader that is loaded before the kernel and other
1509 files specified in the novaboot script. E.g. 'bin/boot/bender
1514 Print the modules to boot and their parameters, after this phase
1515 finishes. Then exit. This is useful for seeing the effect of other
1516 options in this section.
1518 =item -k, --kernel=F<file>
1520 Replace the first word on the first C<load> line in the novaboot
1521 script with F<file>.
1523 =item --scriptmod=I<Perl expression>
1525 When novaboot reads the script, I<Perl expression> is executed for every
1526 line (in $_ variable). For example, C<novaboot
1527 --scriptmod=s/sigma0/omega6/g> replaces every occurrence of I<sigma0>
1528 in the script with I<omega6>.
1530 When this option is present, it overrides I<$script_modifier> variable
1531 from the configuration file, which has the same effect. If this option
1532 is given multiple times all expressions are evaluated in the command
1537 =head2 File generation phase
1539 In this phase, files needed for booting are generated in a so-called
1540 I<build directory> (see L</--build-dir>). In most cases configuration
1541 for a bootloader is generated automatically by novaboot. It is also
1542 possible to generate other files using I<heredoc> or I<"<"> syntax in
1543 novaboot scripts. Finally, novaboot can generate binaries in this phases by
1544 running C<scons> or C<make>.
1548 =item --build-dir=I<directory>
1550 Overrides the default build directory location.
1552 The default build directory location is determined as follows: If the
1553 configuration file defines the C<$builddir> variable, its value is
1554 used. Otherwise, it is the directory that contains the first processed
1557 See also L</BUILDDIR> variable.
1559 =item -g, --grub[=I<filename>]
1561 Generates grub bootloader menu file. If the I<filename> is not
1562 specified, F<menu.lst> is used. The I<filename> is relative to the
1563 build directory (see B<--build-dir>).
1565 =item --grub-preamble=I<prefix>
1567 Specifies the I<preamble> that is at the beginning of the generated
1568 GRUB or GRUB2 config files. This is useful for specifying GRUB's
1571 =item --prefix=I<prefix>
1573 Specifies I<prefix> (e.g. F</srv/tftp>) that is put in front of every
1574 filename in generated bootloader configuration files (or in U-Boot
1577 If the I<prefix> contains string $NAME, it will be replaced with the
1578 name of the novaboot script (see also B<--name>).
1580 If the I<prefix> contains string $BUILDDIR, it will be replaced with
1581 the build directory (see also B<--build-dir>).
1585 Alias for B<--prefix>.
1587 =item --grub2[=I<filename>]
1589 Generate GRUB2 menu entry in I<filename>. If I<filename> is not
1590 specified F<grub.cfg> is used. The content of the menu entry can be
1591 customized with B<--grub-preamble>, B<--grub2-prolog> or
1592 B<--grub_prefix> options.
1594 To use the generated menu entry on your development
1595 machine that uses GRUB2, append the following snippet to
1596 F</etc/grub.d/40_custom> file and regenerate your grub configuration,
1597 i.e. run update-grub on Debian/Ubuntu.
1599 if [ -f /path/to/nul/build/grub.cfg ]; then
1600 source /path/to/nul/build/grub.cfg
1603 =item --grub2-prolog=I<prolog>
1605 Specifies the text that novaboot puts at the beginning of the GRUB2 menu entry.
1607 =item -m, --make[=make command]
1609 Runs C<make> to build files that are not generated by novaboot itself.
1611 =item --name=I<string>
1613 Use the name I<string> instead of the name of the novaboot script.
1614 This name is used for things like a title of grub menu or for the
1615 server directory where the boot files are copied to.
1619 Do not run external commands to generate files (i.e. "<" syntax and
1620 C<run> keyword). This switch does not influence the generation of files
1621 specified with "<<WORD" syntax.
1623 =item -p, --pulsar[=mac]
1625 Generates pulsar bootloader configuration file named F<config-I<mac>>
1626 The I<mac> string is typically a MAC address and defaults to
1629 =item --scons[=scons command]
1631 Runs C<scons> to build files that are not generated by novaboot
1636 Strip I<rom://> prefix from command lines and generated config files.
1637 The I<rom://> prefix is used by NUL. For NRE, it has to be stripped.
1641 Exit novaboot after file generation phase.
1645 =head2 Target connection check
1647 In this phase novaboot connects to target's serial port (if it has
1648 one). If another novaboot user/instance occupies the target, novaboot
1649 exits here with an error message.
1653 =item --amt=I<"[user[:password]@]host[:port]>
1655 Use Intel AMT technology to control the target machine. WS management
1656 is used to powercycle it and Serial-Over-Lan (SOL) for input/output.
1657 The hostname or (IP address) is given by the I<host> parameter. If the
1658 I<password> is not specified, environment variable AMT_PASSWORD is
1659 used. The I<port> specifies a TCP port for SOL. If not specified, the
1660 default is 16992. The default I<user> is admin.
1662 =item --iprelay=I<addr[:port]>
1664 Use TCP/IP relay and serial port to access the target's serial port
1665 and powercycle it. The I<addr> parameter specifies the IP address of
1666 the relay. If I<port> is not specified, it defaults to 23.
1668 Note: This option is supposed to work with HWG-ER02a IP relays.
1670 =item --iprelay-cmd=I<command>
1672 Similar to B<--iprelay> but uses I<command> to talk to the iprelay
1673 rather than direct network connection.
1675 =item -s, --serial[=device]
1677 Target's serial line is connected to host's serial line (device). The
1678 default value for device is F</dev/ttyUSB0>.
1680 The value of this option is exported in NB_NOVABOOT environment
1681 variable to all subprocesses run by C<novaboot>.
1683 =item --stty=I<settings>
1685 Specifies settings passed to C<stty> invoked on the serial line
1686 specified with B<--serial> option. If this option is not given,
1687 C<stty> is called with C<raw -crtscts -onlcr 115200> settings.
1689 =item --remote-cmd=I<cmd>
1691 Command that mediates connection to the target's serial line. For
1692 example C<ssh server 'cu -l /dev/ttyS0'>.
1694 =item --remote-expect=I<string>
1696 Wait for reception of I<string> after establishing the remote
1697 connection. This option is needed when novaboot should wait for
1698 confirmation before deploying files to the target, e.g. to not
1699 overwrite other user's files when they are using the target.
1701 =item --remote-expect-silent=I<string>
1703 The same as B<--remote-expect> except that the remote output is not
1704 echoed to stdout while waiting for the I<string>. Everything after the
1705 matched string is printed to stdout, so you may want to include line
1706 end characters in the I<string> as well.
1708 =item --remote-expect-timeout=I<seconds>
1710 Timeout in seconds for B<--remote-expect> or
1711 B<--remote-expect-seconds>. When negative, waits forever. The default
1716 =head2 File deployment phase
1718 In some setups, it is necessary to copy the files needed for booting
1719 to a particular location, e.g. to a TFTP boot server or to the
1724 =item -d, --dhcp-tftp
1726 Turns your workstation into a DHCP and TFTP server so that the OS can
1727 be booted via PXE BIOS (or similar mechanism) on the test machine
1728 directly connected by a plain Ethernet cable to your workstation.
1730 The DHCP and TFTP servers require root privileges and C<novaboot>
1731 uses C<sudo> command to obtain those. You can put the following to
1732 I</etc/sudoers> to allow running the necessary commands without asking
1735 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
1736 your_login ALL=NOPASSWD: NOVABOOT
1740 Starts a TFTP server on your workstation. This is similar to
1741 B<--dhcp-tftp> except that DHCP server is not started.
1743 The TFTP server requires root privileges and C<novaboot> uses C<sudo>
1744 command to obtain those. You can put the following to I</etc/sudoers>
1745 to allow running the necessary commands without asking for a password.
1747 Cmnd_Alias NOVABOOT = /usr/sbin/in.tftpd --listen --secure -v -v -v --pidfile tftpd.pid *, /usr/bin/pkill --pidfile=tftpd.pid
1748 your_login ALL=NOPASSWD: NOVABOOT
1750 =item --tftp-port=I<port>
1752 Port to run the TFTP server on. Implies B<--tftp>.
1754 =item --netif=I<network interface>
1756 Network interface used to deploy files to the target. The default value is
1757 I<eth0>. This option influences the configuration of the DHCP server started
1758 by B<--dhcp-tftp> and the value that B<$NB_MYIP> get replaced with during
1759 U-Boot conversation.
1761 =item --iso[=filename]
1763 Generates the ISO image that boots NOVA system via GRUB. If no filename
1764 is given, the image is stored under I<NAME>.iso, where I<NAME> is the name
1765 of the novaboot script (see also B<--name>).
1767 =item --server[=[[user@]server:]path]
1769 Copy all files needed for booting to another location. The files will
1770 be copied (by B<rsync> tool) to the directory I<path>. If the I<path>
1771 contains string $NAME, it will be replaced with the name of the
1772 novaboot script (see also B<--name>).
1774 =item --rsync-flags=I<flags>
1776 Specifies I<flags> to append to F<rsync> command line when
1777 copying files as a result of I<--server> option.
1781 If B<--server> is used and its value ends with $NAME, then after
1782 copying the files, a new bootloader configuration file (e.g. menu.lst)
1783 is created at I<path-wo-name>, i.e. the path specified by B<--server>
1784 with $NAME part removed. The content of the file is created by
1785 concatenating all files of the same name from all subdirectories of
1786 I<path-wo-name> found on the "server".
1790 Use Intel AMT technology for IDE redirection. This allows the target
1791 machine to boot from novaboot created ISO image. Implies B<--iso>.
1793 The experimental C<amtider> utility needed by this option can be
1794 obtained from https://github.com/wentasah/amtterm.
1798 =head2 Target power-on and reset phase
1800 At this point, the target is reset (or switched on/off). There are
1801 several ways how this can be accomplished. Resetting a physical target
1802 can currently be accomplished by the following options: B<--amt>,
1803 B<--iprelay>, B<--reset-cmd> and B<--reset-send>.
1809 Switch on/off the target machine and exit. The script (if any) is
1810 completely ignored. Currently, it works only with the following
1811 options: B<--iprelay>, B<--amt>, B<--ssh>.
1813 =item -Q, --qemu[=I<qemu-binary>]
1815 Boot the configuration in qemu. Optionally, the name of qemu binary
1816 can be specified as a parameter.
1818 =item --qemu-append=I<flags>
1820 Append I<flags> to the default qemu flags (QEMU_FLAGS variable or
1821 C<-cpu coreduo -smp 2>).
1823 =item -q, --qemu-flags=I<flags>
1825 Replace the default qemu flags (QEMU_FLAGS variable or C<-cpu coreduo
1826 -smp 2>) with I<flags> specified here.
1828 =item --reset-cmd=I<cmd>
1830 Runs command I<cmd> to reset the target.
1832 =item --reset-send=I<string>
1834 Reset the target by sending the given I<string> to the remote serial
1835 line. "\n" sequences are replaced with the newline character.
1837 =item --no-reset, --reset
1839 Disable/enable resetting of the target.
1843 =head2 Interaction with the bootloader on the target
1847 =item --uboot[=I<prompt>]
1849 Interact with U-Boot bootloader to boot the thing described in the
1850 novaboot script. I<prompt> specifies the U-Boot's prompt (default is
1851 "=> ", other common prompts are "U-Boot> " or "U-Boot# ").
1855 Disable U-Boot interaction previously enabled with B<--uboot>.
1859 Command(s) to send the U-Boot bootloader before loading the images and
1860 booting them. This option can be given multiple times. After sending
1861 commands from each option novaboot waits for U-Boot I<prompt>.
1863 If the command contains string I<$NB_MYIP> then this string is
1864 replaced by IPv4 address of eth0 interface (see also B<--netif>).
1865 Similarly, I<$NB_PREFIX> is replaced with prefix given by B<--prefix>.
1867 See also C<uboot> keyword in L</"NOVABOOT SCRIPT SYNTAX">).
1869 =item --uboot-addr I<name>=I<address>
1871 Load address of U-Boot's C<tftpboot> command for loading I<name>,
1872 where name is one of I<kernel>, I<ramdisk> or I<fdt> (flattened device
1875 The default addresses are ${I<name>_addr_r}, i.e. U-Boot environment
1876 variables used by convention for this purpose.
1878 =item --uboot-cmd=I<command>
1880 Specifies U-Boot command used to execute the OS. If the command
1881 contains strings C<$kernel_addr>, C<$ramdisk_addr>, C<$fdt_addr>,
1882 these are replaced with the addresses configured with B<--uboot-addr>.
1884 The default value is
1886 bootm $kernel_addr $ramdisk_addr $fdt_addr
1888 or the C<UBOOT_CMD> variable if defined in the novaboot script.
1892 =head2 Target interaction phase
1894 In this phase, target's serial output is redirected to stdout and if
1895 stdin is a TTY, it is redirected to the target's serial input allowing
1896 interactive work with the target.
1900 =item --exiton=I<string>
1902 When the I<string> is sent by the target, novaboot exits. This option can
1903 be specified multiple times, in which case novaboot exits whenever
1904 either of the specified strings is sent.
1906 If the I<string> is C<-re>, then the next B<--exiton>'s I<string> is
1907 treated as a regular expression. For example:
1909 --exiton -re --exiton 'error:.*failed'
1911 =item --exiton-re=I<regex>
1913 The same as --exiton -re --exiton I<regex>.
1915 =item --exiton-timeout=I<seconds>
1917 By default B<--exiton> waits for the string match forever. When this
1918 option is specified, "exiton" timeouts after the specifies the number of
1919 seconds and novaboot returns non-zero exit code.
1921 =item -i, --interactive
1923 Setup things for the interactive use of the target. Your terminal will
1924 be switched to raw mode. In raw mode, your local terminal does not
1925 process input in any way (no echoing of entered characters, no
1926 interpretation of special characters). This, among others, means that
1927 Ctrl-C is passed to the target and does not interrupt novaboot. To
1928 exit from novaboot interactive mode type "~~.".
1930 =item --no-interaction, --interaction
1932 Skip resp. force target interaction phase. When skipped, novaboot exits
1933 immediately after the boot is initiated.
1935 =item --expect=I<string>
1937 When the I<string> is received from the target, send the string specified
1938 with the subsequent B<--send*> option to the target.
1940 =item --expect-re=I<regex>
1942 When target's output matches regular expression I<regex>, send the
1943 string specified with the subsequent B<--send*> option to the target.
1945 =item --expect-raw=I<perl-code>
1947 Provides direct control over Perl's Expect module.
1949 =item --send=I<string>
1951 Send I<string> to the target after the previously specified
1952 B<--expect*> was matched in the target's output. The I<string> may
1953 contain escape sequences such as "\n".
1955 Note that I<string> is actually interpreted by Perl, so it can contain
1956 much more that escape sequences. This behavior may change in the
1959 Example: C<--expect='login: ' --send='root\n'>
1961 =item --sendcont=I<string>
1963 Similar to B<--send> but continue expecting more input.
1965 Example: C<--expect='Continue?' --sendcont='yes\n'>
1967 =item --final-eol, --no-final-eol
1969 By default, B<novaboot> always prints an end-of-line character at the
1970 end of its execution in order to ensure that the output of programs
1971 started after novaboot appears at the beginning of the line. When this
1972 is not desired B<--no-final-eol> option can be used to override this
1977 =head1 NOVABOOT SCRIPT SYNTAX
1979 The syntax tries to mimic POSIX shell syntax. The syntax is defined
1980 by the following rules.
1982 Lines starting with "#" and empty lines are ignored.
1984 Lines that end with "\" are concatenated with the following line after
1985 removal of the final "\" and leading whitespace of the following line.
1987 Lines of the form I<VARIABLE=...> (i.e. matching '^[A-Z_]+=' regular
1988 expression) assign values to internal variables. See L</VARIABLES>
1991 Otherwise, the first word on the line defines the meaning of the line.
1992 The following keywords are supported:
1998 These lines represent modules to boot. The
1999 word after C<load> is a file name (relative to the build directory
2000 (see B<--build-dir>) of the module to load and the remaining words are
2001 passed to it as the command line parameters.
2003 When the C<load> line ends with "<<WORD" then the subsequent lines
2004 until the line containing solely WORD are copied literally to the file
2005 named on that line. This is similar to shell's heredoc feature.
2007 When the C<load> line ends with "< CMD" then command CMD is executed
2008 with F</bin/sh> and its standard output is stored in the file named on
2009 that line. The SRCDIR variable in CMD's environment is set to the
2010 absolute path of the directory containing the interpreted novaboot
2015 These lines are similar to C<load> lines. The
2016 file mentioned there is copied to the same place as in the case of C<load>
2017 (e.g. tftp server), but the file is not used in the bootloader
2018 configuration. Such a file can be used by the target for other
2019 purposes than booting, e.g. at OS runtime or for firmware update.
2023 Chainload another bootloader. Instead of loading multiboot modules
2024 identified with C<load> keyword, run another bootloader. This is
2025 currently supported only by pulsar and can be used to load e.g. Grub
2026 as in the example below:
2028 chld boot/grub/i386-pc/core.0
2033 Lines starting with C<run> keyword contain shell commands that are run
2034 during file generation phase. This is the same as the "< CMD" syntax
2035 for C<load> keyboard except that the command's output is not
2036 redirected to a file. The ordering of commands is the same as they
2037 appear in the novaboot script.
2041 These lines represent U-Boot commands that are sent to the target if
2042 B<--uboot> option is given. Having a U-Boot line in the novaboot
2043 script is the same as giving B<--uboot-init> option to novaboot. The
2044 following syntax variants are supported:
2047 uboot[:<timeout>] <string> [> <file>]
2048 uboot[:<timeout>] < <shell> [> <file>]
2050 C<string> is the literal U-Boot command.
2052 The C<uboot> keyword can be suffixed with timeout specification. The
2053 syntax is C<uboot:Ns>, where C<N> is the whole number of seconds. If
2054 the U-Boot command prompt does not appear before the timeout, novaboot
2055 fails. The default timeout is 10 seconds.
2057 In the second variant with the C<<> character the shell code is
2058 executed and its standard output is sent to U-Boot. Example:
2060 uboot < printf "mmc write \$loadaddr 1 %x" $(($(/usr/bin/stat -c%s rootfs.ext4) / 512))
2062 When C<E<gt> file> part is present, the output of the U-Boot command
2063 is written into the given file.
2069 #!/usr/bin/env novaboot
2070 load bzImage console=ttyS0,115200
2071 run make -C buildroot
2072 load rootfs.cpio < gen_cpio buildroot/images/rootfs.cpio "myapp->/etc/init.d/S99myapp"
2074 Example (NOVA User Land - NUL):
2076 #!/usr/bin/env novaboot
2077 WVDESC=Example program
2078 load bin/apps/sigma0.nul S0_DEFAULT script_start:1,1 \
2079 verbose hostkeyb:0,0x60,1,12,2
2080 load bin/apps/hello.nul
2081 load hello.nulconfig <<EOF
2082 sigma0::mem:16 name::/s0/log name::/s0/timer name::/s0/fs/rom ||
2083 rom://bin/apps/hello.nul
2086 This example will load three modules: F<sigma0.nul>, F<hello.nul> and
2087 F<hello.nulconfig>. sigma0 receives some command line parameters and
2088 F<hello.nulconfig> file is generated on the fly from the lines between
2089 C<<<EOF> and C<EOF>.
2091 Example (Zynq system update via U-Boot):
2093 #!/usr/bin/env novaboot
2097 # Write kernel to FAT filesystem on the 1st SD card partition
2098 run mkimage -f uboot-image.its image.ub
2100 uboot:60s tftpboot ${loadaddr} $NB_PREFIX/image.ub
2101 uboot fatwrite mmc 0:1 ${loadaddr} image.ub $filesize
2102 uboot set bootargs console=ttyPS0,115200 root=/dev/mmcblk0p2
2104 # Write root FS image to the 2nd SD card partition
2105 copy rootfs/images/rootfs.ext4
2106 uboot:60s tftpboot ${loadaddr} $NB_PREFIX/rootfs/images/rootfs.ext4
2107 uboot mmc part > mmc-part.txt
2108 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))
2115 The following variables are interpreted in the novaboot script:
2121 Novaboot chdir()s to this directory before file generation phase. The
2122 directory name specified here is relative to the build directory
2123 specified by other means (see L</--build-dir>).
2127 Assigning this variable has the same effect as specifying L</--exiton>
2130 =item HYPERVISOR_PARAMS
2132 Parameters passed to the hypervisor. The default value is "serial", unless
2133 overridden in the configuration file.
2137 The kernel to use instead of the hypervisor specified in the
2138 configuration file with the C<$hypervisor> variable. The value should
2139 contain the name of the kernel image as well as its command line
2140 parameters. If this variable is defined and non-empty, the variable
2141 HYPERVISOR_PARAMS is not used.
2145 If this variable is 1, the system is not booted. This is currently
2146 only implemented for U-Boot bootloader where it is useful for
2147 interacting with the bootloader without booting the system - e.g. for
2152 Use a specific qemu binary (can be overridden with B<-Q>) and flags
2153 when booting this script under qemu. If QEMU_FLAGS variable is also
2154 specified flags specified in QEMU variable are replaced by those in
2159 Use specific qemu flags (can be overridden with B<-q>).
2163 See L</--uboot-cmd>.
2167 Description of the WvTest-compliant program.
2169 =item WVTEST_TIMEOUT
2171 The timeout in seconds for WvTest harness. If no complete line appears
2172 in the test output within the time specified here, the test fails. It
2173 is necessary to specify this for long running tests that produce no
2174 intermediate output.
2178 =head1 CONFIGURATION FILES
2180 Novaboot can read its configuration from one or more files. By
2181 default, novaboot looks for files in F</etc/novaboot.d>, file
2182 F<~/.config/novaboot> and files named F<.novaboot> as described in
2183 L</Configuration reading phase>. Alternatively, configuration file
2184 location can be specified with the B<-c> switch or with the
2185 NOVABOOT_CONFIG environment variable. The configuration file has Perl
2186 syntax (i.e. it is better to put C<1;> as the last line) and should set
2187 values of certain Perl variables. The current configuration can be
2188 dumped with the B<--dump-config> switch. Some configuration variables
2189 can be overridden by environment variables (see below) or by command
2192 Supported configuration variables include:
2198 Build directory location relative to the location of the configuration
2201 =item $default_target
2203 Default target (see below) to use when no target is explicitly
2204 specified with the B<--target> command line option or
2205 B<NOVABOOT_TARGET> environment variable.
2209 Hash of target definitions to be used with the B<--target> option. The
2210 key is the identifier of the target, the value is the string with
2211 command line options. For instance, if the configuration file contains:
2213 $targets{'mybox'} = '--server=boot:/tftproot --serial=/dev/ttyUSB0 --grub',
2215 then the following two commands are equivalent:
2217 ./myos --server=boot:/tftproot --serial=/dev/ttyUSB0 --grub
2222 =head1 ENVIRONMENT VARIABLES
2224 Some options can be specified not only via config file or command line
2225 but also through environment variables. Environment variables override
2226 the values from the configuration file and command line parameters
2227 override the environment variables.
2231 =item NOVABOOT_CONFIG
2233 Name of the novaboot configuration file to use instead of the default
2236 =item NOVABOOT_CONFIG_DIR
2238 Name of the novaboot configuration directory. When not specified
2239 F</etc/novaboot.d> is used.
2241 =item NOVABOOT_TARGET
2243 Name of the novaboot target to use. This overrides the value of
2244 B<$default_target> from the configuration file and can be overridden
2245 with the B<--target> command line option.
2247 =item NOVABOOT_BENDER
2249 Defining this variable has the same effect as using B<--bender>
2256 Michal Sojka <sojka@os.inf.tu-dresden.de>
2258 Latest novaboot version can be found at
2259 L<https://github.com/wentasah/novaboot>.
2263 # LocalWords: novaboot Novaboot NOVABOOT TFTP PXE DHCP filename stty
2264 # LocalWords: chainloader stdout Qemu qemu preprocessing ISOLINUX bootable
2265 # LocalWords: config subprocesses sudo sudoers tftp dhcp IDE stdin
2266 # LocalWords: subdirectories TTY whitespace heredoc POSIX WvTest