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/>.
18 use warnings (exists $ENV{NOVABOOT_TEST} ? (FATAL => 'all') : ());
19 use Getopt::Long qw(GetOptionsFromString);
24 use Time::HiRes("usleep");
28 use POSIX qw(:errno_h);
29 use Cwd qw(getcwd abs_path);
35 my $invocation_dir = $ENV{PWD} || getcwd();
37 ## Configuration file handling
39 # Default configuration
40 $CFG::hypervisor = "";
41 $CFG::hypervisor_params = "serial";
42 $CFG::genisoimage = "genisoimage";
43 $CFG::qemu = 'qemu-system-i386 -cpu coreduo -smp 2';
44 $CFG::default_target = 'qemu';
47 "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',
48 "novabox" => '--server=rtime.felk.cvut.cz:/srv/tftp/novaboot --rsync-flags="--chmod=Dg+s,ug+w,o-w,+rX --rsync-path=\"umask 002 && rsync\"" --pulsar --iprelay=147.32.86.92:2324',
49 "localhost" => '--scriptmod=s/console=tty[A-Z0-9,]+// --server=/boot/novaboot/$NAME --grub2 --grub-prefix=/boot/novaboot/$NAME --grub2-prolog=" set root=\'(hd0,msdos1)\'"',
50 "ryuglab" => '--server=pc-sojkam.felk.cvut.cz:/srv/tftp --uboot --uboot-init="mw f0000b00 \${psc_cfg}; sleep 1" --remote-cmd="ssh -tt pc-sojkam.felk.cvut.cz \"sterm -d -s 115200 /dev/ttyUSB0\""',
51 "ryulocal" => '--dhcp-tftp --serial --uboot --uboot-init="dhcp; mw f0000b00 \${psc_cfg}; sleep 1" --reset-cmd="if which dtrrts; then dtrrts $NB_SERIAL 0 1; sleep 0.1; dtrrts $NB_SERIAL 1 1; fi"',
54 chomp(my $nproc = `nproc`);
55 $CFG::scons = "scons -j$nproc";
56 $CFG::make = "make -j$nproc";
63 package CFG; # Put config data into a separate namespace
68 die("ERROR: Failure compiling '$cfg' - $@");
69 } elsif (! defined($rc)) {
70 die("ERROR: Failure reading '$cfg' - $!");
72 die("ERROR: Failure processing '$cfg'");
75 $builddir = File::Spec->rel2abs($CFG::builddir, dirname($cfg)) if defined $CFG::builddir;
76 print STDERR "novaboot: Read $cfg\n";
81 # We don't use $0 here, because it points to the novaboot itself and
82 # not to the novaboot script. The problem with this approach is that
83 # when a script is run as "novaboot <options> <script>" then $ARGV[0]
84 # contains the first option. Hence the -f check.
85 my $dir = File::Spec->rel2abs($ARGV[0] && -f $ARGV[0] ? dirname($ARGV[0]) : '', $invocation_dir);
86 while ((-d $dir || -l $dir ) && $dir ne "/") {
87 push @cfgs, "$dir/.novaboot" if -r "$dir/.novaboot";
88 my @dirs = File::Spec->splitdir($dir);
89 $dir = File::Spec->catdir(@dirs[0..$#dirs-1]);
92 my $cfg = $ENV{'NOVABOOT_CONFIG'};
93 Getopt::Long::Configure(qw/no_ignore_case pass_through/);
94 GetOptions ("config|c=s" => \$cfg);
95 read_config($_) foreach $cfg or reverse @cfgs;
97 ## Command line handling
100 GetOptions ("target|t=s" => \$explicit_target);
102 my ($amt, @append, $bender, @chainloaders, $concat, $config_name_opt, $dhcp_tftp, $dump_opt, $dump_config, @exiton, @expect_raw, $gen_only, $grub_config, $grub_prefix, $grub_preamble, $grub2_prolog, $grub2_config, $help, $ider, $iprelay, $iso_image, $interactive, $kernel_opt, $make, $man, $no_file_gen, $off_opt, $on_opt, $pulsar, $pulsar_root, $qemu, $qemu_append, $qemu_flags_cmd, $remote_cmd, $remote_expect, $reset_cmd, $rom_prefix, $rsync_flags, @scriptmod, $scons, $serial, $server, $stty, $tftp, $tftp_port, $uboot, @uboot_init);
105 $rom_prefix = 'rom://';
106 $stty = 'raw -crtscts -onlcr 115200';
108 my @expect_seen = ();
112 push(@expect_seen, '-re') if $n eq "expect-re";
113 push(@expect_seen, $v);
119 unless (@expect_seen) { die("No --expect before --send"); }
120 my $ret = ($n eq "sendcont") ? exp_continue : 0;
121 unshift(@expect_raw, sub { shift->send(eval("\"$v\"")); $ret; });
122 unshift(@expect_raw, @expect_seen);
126 Getopt::Long::Configure(qw/no_ignore_case no_pass_through/);
130 "append|a=s" => \@append,
131 "bender|b" => \$bender,
132 "build-dir=s" => sub { my ($n, $v) = @_; $builddir = File::Spec->rel2abs($v); },
133 "concat" => \$concat,
134 "chainloader=s" => \@chainloaders,
135 "dhcp-tftp|d" => \$dhcp_tftp,
136 "dump" => \$dump_opt,
137 "dump-config" => \$dump_config,
138 "exiton=s" => \@exiton,
139 "expect=s" => \&handle_expect,
140 "expect-re=s" => \&handle_expect,
141 "expect-raw=s" => sub { my ($n, $v) = @_; unshift(@expect_raw, eval($v)); },
142 "gen-only" => \$gen_only,
143 "grub|g:s" => \$grub_config,
144 "grub-preamble=s"=> \$grub_preamble,
145 "grub-prefix=s" => \$grub_prefix,
146 "grub2:s" => \$grub2_config,
147 "grub2-prolog=s" => \$grub2_prolog,
149 "iprelay=s" => \$iprelay,
150 "iso:s" => \$iso_image,
151 "kernel|k=s" => \$kernel_opt,
152 "interactive|i" => \$interactive,
153 "name=s" => \$config_name_opt,
154 "make|m:s" => \$make,
155 "no-file-gen" => \$no_file_gen,
158 "pulsar|p:s" => \$pulsar,
159 "pulsar-root=s" => \$pulsar_root,
160 "qemu|Q:s" => \$qemu,
161 "qemu-append=s" => \$qemu_append,
162 "qemu-flags|q=s" => \$qemu_flags_cmd,
163 "remote-cmd=s" => \$remote_cmd,
164 "remote-expect=s"=> \$remote_expect,
165 "reset-cmd=s" => \$reset_cmd,
166 "rsync-flags=s" => \$rsync_flags,
167 "scons:s" => \$scons,
168 "scriptmod=s" => \@scriptmod,
169 "send=s" => \&handle_send,
170 "sendcont=s" => \&handle_send,
171 "serial|s:s" => \$serial,
172 "server:s" => \$server,
173 "strip-rom" => sub { $rom_prefix = ''; },
176 "tftp-port=i" => \$tftp_port,
177 "uboot:s" => \$uboot,
178 "uboot-init=s" => \@uboot_init,
183 # First process target options
185 my $t = defined($explicit_target) ? $explicit_target : $CFG::default_target;
187 exists $CFG::targets{$t} or die("Unknown target '$t' (valid targets are: ".join(", ", sort keys(%CFG::targets)).")");
188 GetOptionsFromString($CFG::targets{$t}, %opt_spec);
192 # Then process other command line options - some of them may override
193 # what was specified by the target
194 GetOptions %opt_spec or die("Error in command line arguments");
195 pod2usage(1) if $help;
196 pod2usage(-exitstatus => 0, -verbose => 2) if $man;
198 ### Dump sanitized configuration (if requested)
202 $Data::Dumper::Indent=1;
203 print "# This file is in perl syntax.\n";
204 foreach my $key(sort(keys(%CFG::))) { # See "Symbol Tables" in perlmod(1)
205 if (defined ${$CFG::{$key}}) { print Data::Dumper->Dump([${$CFG::{$key}}], ["*$key"]); }
206 if ( @{$CFG::{$key}}) { print Data::Dumper->Dump([\@{$CFG::{$key}}], ["*$key"]); }
207 if ( %{$CFG::{$key}}) { print Data::Dumper->Dump([\%{$CFG::{$key}}], ["*$key"]); }
213 ### Sanitize configuration
215 if ($interactive && !-t STDIN) {
216 die("novaboot: Interactive mode not supported when not on terminal");
219 if (defined $config_name_opt && scalar(@ARGV) > 1) { die "You cannot use --name with multiple scripts"; }
222 $iso_image //= ''; # IDE-R needs an ISO image
223 if (!defined $amt) { die "Error: --ider requires --amt"; }
227 if (defined $serial) {
228 $serial ||= "/dev/ttyUSB0";
229 $ENV{NB_SERIAL} = $serial;
231 if (defined $grub_config) { $grub_config ||= "menu.lst"; }
232 if (defined $grub2_config) { $grub2_config ||= "grub.cfg"; }
234 ## Parse the novaboot script(s)
239 my ($modules, $variables, $generated, $continuation);
240 my $skip_reading = defined($on_opt) || defined($off_opt);
241 while (!$skip_reading && ($_ = <>)) {
242 if ($ARGV ne $last_fn) { # New script
243 die "Missing EOF in $last_fn" if $file;
244 die "Unfinished line in $last_fn" if $continuation;
246 push @scripts, { 'filename' => $ARGV,
247 'modules' => $modules = [],
248 'variables' => $variables = {},
249 'generated' => $generated = []};
253 next if /^#/ || /^\s*$/; # Skip comments and empty lines
255 $_ =~ s/^[[:space:]]*// if ($continuation);
257 if (/\\$/) { # Line continuation
258 $continuation .= substr($_, 0, length($_)-1);
262 if ($continuation) { # Last continuation line
263 $_ = $continuation . $_;
267 foreach my $mod(@scriptmod) { eval $mod; }
269 if ($file && $_ eq $EOF) { # Heredoc end
273 if ($file) { # Heredoc content
274 push @{$file}, "$_\n";
277 if (/^([A-Z_]+)=(.*)$/) { # Internal variable
278 $$variables{$1} = $2;
279 push(@exiton, $2) if ($1 eq "EXITON");
282 if (s/^load *//) { # Load line
283 die("novaboot: '$last_fn' line $.: Missing file name\n") unless /^[^ <]+/;
284 if (/^([^ ]*)(.*?)[[:space:]]*<<([^ ]*)$/) { # Heredoc start
285 push @$modules, "$1$2";
287 push @$generated, {filename => $1, content => $file};
291 if (/^([^ ]*)(.*?)[[:space:]]*< ?(.*)$/) { # Command substitution
292 push @$modules, "$1$2";
293 push @$generated, {filename => $1, command => $3};
299 if (/^run (.*)/) { # run line
300 push @$generated, {command => $1};
304 die("novaboot: Cannot parse script '$last_fn' line $.. Didn't you forget 'load' keyword?\n");
307 # print Dumper(\@scripts);
309 foreach my $script (@scripts) {
310 $modules = $$script{modules};
311 @$modules[0] =~ s/^[^ ]*/$kernel_opt/ if $kernel_opt;
312 @$modules[0] .= ' ' . join(' ', @append) if @append;
315 if (exists $variables->{KERNEL}) {
316 $kernel = $variables->{KERNEL};
318 if ($CFG::hypervisor) {
319 $kernel = $CFG::hypervisor . " ";
320 if (exists $variables->{HYPERVISOR_PARAMS}) {
321 $kernel .= $variables->{HYPERVISOR_PARAMS};
323 $kernel .= $CFG::hypervisor_params;
327 @$modules = ($kernel, @$modules) if $kernel;
328 @$modules = (@chainloaders, @$modules);
329 @$modules = ("bin/boot/bender", @$modules) if ($bender || defined $ENV{'NOVABOOT_BENDER'});
333 foreach my $script (@scripts) {
334 print join("\n", @{$$script{modules}})."\n";
341 sub generate_configs($$$) {
342 my ($base, $generated, $filename) = @_;
343 if ($base) { $base = "$base/"; };
344 foreach my $g(@$generated) {
345 if (exists $$g{content}) {
346 my $config = $$g{content};
347 my $fn = $$g{filename};
348 open(my $f, '>', $fn) || die("$fn: $!");
349 map { s|\brom://([^ ]*)|$rom_prefix$base$1|g; print $f "$_"; } @{$config};
351 print "novaboot: Created $fn\n";
352 } elsif (exists $$g{command} && ! $no_file_gen) {
353 $ENV{SRCDIR} = dirname(File::Spec->rel2abs( $filename, $invocation_dir ));
354 if (exists $$g{filename}) {
355 system_verbose("( $$g{command} ) > $$g{filename}");
357 system_verbose($$g{command});
363 sub generate_grub_config($$$$;$)
365 my ($filename, $title, $base, $modules_ref, $preamble) = @_;
366 if ($base) { $base = "$base/"; };
367 open(my $fg, '>', $filename) or die "$filename: $!";
368 print $fg "$preamble\n" if $preamble;
369 print $fg "title $title\n" if $title;
370 #print $fg "root $base\n"; # root doesn't really work for (nd)
372 foreach (@$modules_ref) {
375 my ($kbin, $kcmd) = split(' ', $_, 2);
376 $kcmd = '' if !defined $kcmd;
377 print $fg "kernel ${base}$kbin $kcmd\n";
379 s|\brom://([^ ]*)|$rom_prefix$base$1|g; # Translate rom:// files - needed for vdisk parameter of sigma0
380 print $fg "module $base$_\n";
384 print("novaboot: Created $builddir/$filename\n");
388 sub generate_syslinux_config($$$$)
390 my ($filename, $title, $base, $modules_ref) = @_;
391 if ($base && $base !~ /\/$/) { $base = "$base/"; };
392 open(my $fg, '>', $filename) or die "$filename: $!";
393 print $fg "LABEL $title\n";
394 #TODO print $fg "MENU LABEL $human_readable_title\n";
396 my ($kbin, $kcmd) = split(' ', @$modules_ref[0], 2);
398 if (system("file $kbin|grep 'Linux kernel'") == 0) {
399 my $initrd = @$modules_ref[1];
400 die('To many "load" lines for Linux kernel') if (scalar @$modules_ref > 2);
401 print $fg "LINUX $base$kbin\n";
402 print $fg "APPEND $kcmd\n";
403 print $fg "INITRD $base$initrd\n";
405 print $fg "KERNEL mboot.c32\n";
407 foreach (@$modules_ref) {
408 s|\brom://([^ ]*)|$rom_prefix$base$1|g; # Translate rom:// files - needed for vdisk parameter of sigma0
409 push @append, "$base$_";
410 print $fg "APPEND ".join(' --- ', @append)."\n";
413 #TODO print $fg "TEXT HELP\n";
414 #TODO print $fg "some help here\n";
415 #TODO print $fg "ENDTEXT\n";
417 print("novaboot: Created $builddir/$filename\n");
421 sub generate_grub2_config($$$$;$$)
423 my ($filename, $title, $base, $modules_ref, $preamble, $prolog) = @_;
424 if ($base && substr($base,-1,1) ne '/') { $base = "$base/"; };
425 open(my $fg, '>', $filename) or die "$filename: $!";
426 print $fg "$preamble\n" if $preamble;
427 $title ||= 'novaboot';
428 print $fg "menuentry $title {\n";
429 print $fg "$prolog\n" if $prolog;
431 foreach (@$modules_ref) {
434 my ($kbin, $kcmd) = split(' ', $_, 2);
435 $kcmd = '' if !defined $kcmd;
436 print $fg " multiboot ${base}$kbin $kcmd\n";
439 # GRUB2 doesn't pass filename in multiboot info so we have to duplicate it here
440 $_ = join(' ', ($args[0], @args));
441 s|\brom://|$rom_prefix|g; # We do not need to translate path for GRUB2
442 print $fg " module $base$_\n";
447 print("novaboot: Created $builddir/$filename\n");
451 sub generate_pulsar_config($$)
453 my ($filename, $modules_ref) = @_;
454 open(my $fg, '>', $filename) or die "$filename: $!";
455 print $fg "root $pulsar_root\n" if defined $pulsar_root;
458 foreach (@$modules_ref) {
461 ($kbin, $kcmd) = split(' ', $_, 2);
462 $kcmd = '' if !defined $kcmd;
465 s|\brom://|$rom_prefix|g;
466 print $fg "load $_\n";
469 # Put kernel as last - this is needed for booting Linux and has no influence on non-Linux OSes
470 print $fg "exec $kbin $kcmd\n";
472 print("novaboot: Created $builddir/$filename\n");
476 sub shell_cmd_string(@)
478 return join(' ', map((/^[-_=a-zA-Z0-9\/\.\+]+$/ ? "$_" : "'$_'"), @_));
483 print "novaboot: Running: ".shell_cmd_string(@_)."\n";
485 exit(1); # should not be reached
488 sub system_verbose($)
491 print "novaboot: Running: $cmd\n";
492 my $ret = system($cmd);
493 if ($ret & 0x007f) { die("Command terminated by a signal"); }
494 if ($ret & 0xff00) {die("Command exit with non-zero exit code"); }
495 if ($ret) { die("Command failure $ret"); }
500 if (exists $variables->{WVDESC}) {
501 print "Testing \"$variables->{WVDESC}\" in $last_fn:\n";
502 } elsif ($last_fn =~ /\.wv$/) {
503 print "Testing \"all\" in $last_fn:\n";
506 ## Connect to the target and check whether it is not occupied
508 # We have to do this before file generation phase, because file
509 # generation is intermixed with file deployment phase and we want to
510 # check whether the target is not used by somebody else before
511 # deploying files. Otherwise, we may rewrite other user's files on a
514 my $exp; # Expect object to communicate with the target over serial line
516 my ($target_reset, $target_power_on, $target_power_off);
517 my ($amt_user, $amt_password, $amt_host, $amt_port);
519 if (defined $iprelay) {
521 $iprelay =~ /([.0-9]+)(:([0-9]+))?/;
524 my $paddr = sockaddr_in($port, inet_aton($addr));
525 my $proto = getprotobyname('tcp');
526 socket($IPRELAY, PF_INET, SOCK_STREAM, $proto) || die "socket: $!";
527 print "novaboot: Connecting to IP relay... ";
528 connect($IPRELAY, $paddr) || die "connect: $!";
530 $exp = Expect->init(\*$IPRELAY);
534 print $exp "\xFF\xF6"; # AYT
535 my $connected = $exp->expect(20, # Timeout in seconds
536 '<iprelayd: connected>',
537 '-re', '<WEB51 HW[^>]*>');
542 my ($relay, $onoff) = @_;
543 die unless ($relay == 1 || $relay == 2);
545 my $cmd = ($relay == 1 ? 0x5 : 0x6) | ($onoff ? 0x20 : 0x10);
546 return "\xFF\xFA\x2C\x32".chr($cmd)."\xFF\xF0";
550 my ($relay, $onoff) = @_;
551 die unless ($relay == 1 || $relay == 2);
552 my $cmd = ($relay == 1 ? 0xdf : 0xbf) | ($onoff ? 0x00 : 0xff);
553 return "\xFF\xFA\x2C\x97".chr($cmd)."\xFF\xF0";
557 my ($relay, $onoff, $can_giveup) = @_;
558 my $confirmation = '';
560 print $exp relaycmd($relay, $onoff);
561 my $confirmed = $exp->expect(20, # Timeout in seconds
562 relayconf($relay, $onoff));
565 print("Relay confirmation timeout - ignoring\n");
567 die "Relay confirmation timeout";
573 $target_reset = sub {
574 relay(2, 1, 1); # Reset the machine
579 $target_power_off = sub {
580 relay(1, 1); # Press power button
581 usleep(6000000); # Long press to switch off
585 $target_power_on = sub {
586 relay(1, 1); # Press power button
587 usleep(100000); # Short press
593 system_verbose("stty -F $serial $stty");
594 open($CONN, "+<", $serial) || die "open $serial: $!";
595 $exp = Expect->init(\*$CONN);
597 elsif ($remote_cmd) {
598 print "novaboot: Running: $remote_cmd\n";
599 $exp = Expect->spawn($remote_cmd);
601 elsif (defined $amt) {
602 require LWP::UserAgent;
603 require LWP::Authen::Digest;
606 my ($host, $username, $password, $schema, $className, $pstate) = @_;
607 #AMT numbers for PowerStateChange (MNI => bluescreen on windows;-)
608 my %pstates = ("on" => 2,
615 <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">
616 <s:Header><a:To>http://$host:16992/wsman</a:To>
617 <w:ResourceURI s:mustUnderstand="true">$schema</w:ResourceURI>
618 <a:ReplyTo><a:Address s:mustUnderstand="true">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address></a:ReplyTo>
619 <a:Action s:mustUnderstand="true">$schema$className</a:Action>
620 <w:MaxEnvelopeSize s:mustUnderstand="true">153600</w:MaxEnvelopeSize>
621 <a:MessageID>uuid:709072C9-609C-4B43-B301-075004043C7C</a:MessageID>
622 <w:Locale xml:lang="en-US" s:mustUnderstand="false" />
623 <w:OperationTimeout>PT60.000S</w:OperationTimeout>
624 <w:SelectorSet><w:Selector Name="Name">Intel(r) AMT Power Management Service</w:Selector></w:SelectorSet>
626 <p:RequestPowerStateChange_INPUT xmlns:p="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_PowerManagementService">
627 <p:PowerState>$pstates{$pstate}</p:PowerState>
628 <p:ManagedElement><a:Address>http://$host:16992/wsman</a:Address>
629 <a:ReferenceParameters><w:ResourceURI>http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ComputerSystem</w:ResourceURI>
630 <w:SelectorSet><w:Selector Name="Name">ManagedSystem</w:Selector></w:SelectorSet>
631 </a:ReferenceParameters></p:ManagedElement>
632 </p:RequestPowerStateChange_INPUT>
633 </s:Body></s:Envelope>
638 my ($host, $username, $password, $content) = @_;
640 my $ua = LWP::UserAgent->new();
641 $ua->agent("novaboot");
643 my $req = HTTP::Request->new(POST => "http://$host:16992/wsman");
644 my $res = $ua->request($req);
645 die ("Unexpected AMT response: " . $res->status_line) unless $res->code == 401;
647 my ($realm) = $res->header("WWW-Authenticate") =~ /Digest realm="(.*?)"/;
648 $ua->credentials("$host:16992", $realm, $username => $password);
651 $req = HTTP::Request->new(POST => "http://$host:16992/wsman");
652 $req->content_type('application/x-www-form-urlencoded');
653 $req->content($content);
654 $res = $ua->request($req);
655 die ("AMT power change request failed: " . $res->status_line) unless $res->is_success;
656 $res->content() =~ /<g:ReturnValue>(\d+)<\/g:ReturnValue>/;
661 my ($host, $username, $password, $pstate)=@_;
662 my $schema="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_PowerManagementService";
663 my $className="/RequestPowerStateChange";
664 my $content = genXML($host, $username, $password ,$schema, $className, $pstate);
665 return sendPOST($host, $username, $password, $content);
668 ($amt_user,$amt_password,$amt_host,$amt_port) = ($amt =~ /(?:(.*?)(?::(.*))?@)?([^:]*)(?::([0-9]*))?/);;
669 $amt_user ||= "admin";
670 $amt_password ||= $ENV{'AMT_PASSWORD'} || die "AMT password not specified";
671 $amt_host || die "AMT host not specified";
675 $target_power_off = sub {
677 my $result = powerChange($amt_host,$amt_user,$amt_password, "off");
678 die "AMT power off failed (ReturnValue $result)" if $result != 0;
681 $target_power_on = sub {
682 my $result = powerChange($amt_host,$amt_user,$amt_password, "on");
683 die "AMT power on failed (ReturnValue $result)" if $result != 0;
686 $target_reset = sub {
687 my $result = powerChange($amt_host,$amt_user,$amt_password, "reset");
689 print STDERR "Warning: Cannot reset $amt_host, trying power on. ";
690 $result = powerChange($amt_host,$amt_user,$amt_password, "on");
692 die "AMT reset failed (ReturnValue $result)" if $result != 0;
695 my $cmd = "amtterm -u $amt_user -p $amt_password $amt_host $amt_port";
696 print "novaboot: Running: $cmd\n" =~ s/\Q$amt_password\E/???/r;
697 $exp = Expect->spawn($cmd);
698 $exp->expect(10, "RUN_SOL") || die "Expect for 'RUN_SOL' timed out";
703 if ($remote_expect) {
704 $exp->expect(10, $remote_expect) || die "Expect for '$remote_expect' timed out";
707 if (defined $reset_cmd) {
708 $target_reset = sub {
709 system_verbose($reset_cmd);
713 if (defined $on_opt && defined $target_power_on) {
717 if (defined $off_opt && defined $target_power_off) {
718 print "novaboot: Switching the target off...\n";
719 &$target_power_off();
723 $builddir ||= dirname(File::Spec->rel2abs( ${$scripts[0]}{filename})) if scalar @scripts;
724 if (defined $builddir) {
725 chdir($builddir) or die "Can't change directory to $builddir: $!";
726 print "novaboot: Entering directory `$builddir'\n";
729 ## File generation phase
730 my (%files_iso, $menu_iso, $filename);
731 my $config_name = '';
733 foreach my $script (@scripts) {
734 $filename = $$script{filename};
735 $modules = $$script{modules};
736 $generated = $$script{generated};
737 $variables = $$script{variables};
739 ($config_name = $filename) =~ s#.*/##;
740 $config_name = $config_name_opt if (defined $config_name_opt);
742 if (exists $variables->{BUILDDIR}) {
743 $builddir = File::Spec->rel2abs($variables->{BUILDDIR});
744 chdir($builddir) or die "Can't change directory to $builddir: $!";
745 print "novaboot: Entering directory `$builddir'\n";
749 ($prefix = $grub_prefix) =~ s/\$NAME/$config_name/ if defined $grub_prefix;
750 $prefix ||= $builddir;
751 # TODO: use $grub_prefix as first parameter if some switch is given
752 generate_configs('', $generated, $filename);
754 ### Generate bootloader configuration files
755 my @bootloader_configs;
756 push @bootloader_configs, generate_grub_config($grub_config, $config_name, $prefix, $modules, $grub_preamble) if (defined $grub_config);
757 push @bootloader_configs, generate_grub2_config($grub2_config, $config_name, $prefix, $modules, $grub_preamble, $grub2_prolog) if (defined $grub2_config);
758 push @bootloader_configs, generate_pulsar_config('config-'.($pulsar||'novaboot'), $modules) if (defined $pulsar);
760 ### Run scons or make
762 my @files = map({ ($file) = m/([^ ]*)/; $file; } @$modules);
763 # Filter-out generated files
764 my @to_build = grep({ my $file = $_; !scalar(grep($file eq ($$_{filename} || ''), @$generated)) } @files);
766 system_verbose($scons || $CFG::scons." ".join(" ", @to_build)) if (defined $scons);
767 system_verbose($make || $CFG::make ." ".join(" ", @to_build)) if (defined $make);
770 ### Copy files (using rsync)
771 if (defined $server && !defined($gen_only)) {
772 (my $real_server = $server) =~ s/\$NAME/$config_name/;
774 my ($hostname, $path) = split(":", $real_server, 2);
775 if (! defined $path) {
779 my $files = join(" ", map({ ($file) = m/([^ ]*)/; $file; } ( @$modules, @bootloader_configs)));
780 map({ my $file = (split)[0]; die "$file: $!" if ! -f $file; } @$modules);
781 my $istty = -t STDOUT && ($ENV{'TERM'} || 'dumb') ne 'dumb';
782 my $progress = $istty ? "--progress" : "";
783 system_verbose("rsync $progress -RLp $rsync_flags $files $real_server");
784 if ($server =~ m|/\$NAME$| && $concat) {
785 my $cmd = join("; ", map { "( cd $path/.. && cat */$_ > $_ )" } @bootloader_configs);
786 system_verbose($hostname ? "ssh $hostname '$cmd'" : $cmd);
790 ### Prepare ISO image generation
791 if (defined $iso_image) {
792 generate_configs("(cd)", $generated, $filename);
794 generate_syslinux_config(\$menu, $config_name, "/", $modules);
795 $menu_iso .= "$menu\n";
796 map { ($file,undef) = split; $files_iso{$file} = 1; } @$modules;
800 ## Generate ISO image
801 if (defined $iso_image) {
802 system_verbose("mkdir -p isolinux");
803 # FIXME: Does somebody need support for older syslinux (without ldlinux etc.)?
804 system_verbose('cp /usr/lib/ISOLINUX/isolinux.bin /usr/lib/syslinux/modules/bios/mboot.c32 /usr/lib/syslinux/modules/bios/menu.c32 /usr/lib/syslinux/modules/bios/ldlinux.c32 isolinux');
805 open(my $fh, ">isolinux/isolinux.cfg");
807 print $fh "TIMEOUT 50\n";
808 print $fh "DEFAULT menu\n";
810 print $fh "DEFAULT $config_name\n";
812 print $fh "$menu_iso";
815 my $files = join(" ", map("$_=$_", (keys(%files_iso), 'isolinux/isolinux.bin', 'isolinux/isolinux.cfg', 'isolinux/mboot.c32', 'isolinux/ldlinux.c32', 'isolinux/menu.c32')));
816 $iso_image ||= "$config_name.iso";
818 # Note: We use -U flag below to "Allow 'untranslated' filenames,
819 # completely violating the ISO9660 standards". Without this
820 # option, isolinux is not able to read files names for example
822 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");
823 print("ISO image created: $builddir/$iso_image\n");
826 exit(0) if defined $gen_only;
828 ## Boot the system using various methods and send serial output to stdout
830 if (scalar(@scripts) > 1 && ( defined $dhcp_tftp || defined $serial || defined $iprelay)) {
831 die "You cannot do this with multiple scripts simultaneously";
834 if ($variables->{WVTEST_TIMEOUT}) {
835 print "wvtest: timeout ", $variables->{WVTEST_TIMEOUT}, "\n";
840 $str =~ s/^\s+|\s+$//g;
848 $qemu ||= $variables->{QEMU} || $CFG::qemu;
849 my @qemu_flags = split(" ", $qemu);
850 $qemu = shift(@qemu_flags);
852 @qemu_flags = split(/ +/, trim($variables->{QEMU_FLAGS})) if exists $variables->{QEMU_FLAGS};
853 @qemu_flags = split(/ +/, trim($qemu_flags_cmd)) if $qemu_flags_cmd;
854 push(@qemu_flags, split(/ +/, trim($qemu_append || '')));
856 if (defined $iso_image) {
857 # Boot NOVA with grub (and test the iso image)
858 push(@qemu_flags, ('-cdrom', $iso_image));
860 # Boot NOVA without GRUB
862 # Non-patched qemu doesn't like commas, but NUL can live with pluses instead of commans
863 foreach (@$modules) {s/,/+/g;}
864 generate_configs("", $generated, $filename);
866 if (scalar @$modules) {
867 my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);
868 $kcmd = '' if !defined $kcmd;
870 @$modules = map { if (/\.dtb$/) { $dtb=$_; (); } else { $_ } } @$modules;
871 my $initrd = join ",", @$modules;
873 push(@qemu_flags, ('-kernel', $kbin, '-append', $kcmd));
874 push(@qemu_flags, ('-initrd', $initrd)) if $initrd;
875 push(@qemu_flags, ('-dtb', $dtb)) if $dtb;
878 push(@qemu_flags, qw(-serial stdio)); # Redirect serial output (for collecting test restuls)
879 unshift(@qemu_flags, ('-name', $config_name));
880 print "novaboot: Running: ".shell_cmd_string($qemu, @qemu_flags)."\n";
881 $exp = Expect->spawn(($qemu, @qemu_flags)) || die("exec() failed: $!");
884 ### Local DHCPD and TFTPD
886 my ($dhcpd_pid, $tftpd_pid);
888 $tftp=1 if $tftp_port;
890 if (defined $dhcp_tftp)
892 generate_configs("(nd)", $generated, $filename);
893 system_verbose('mkdir -p tftpboot');
894 generate_grub_config("tftpboot/os-menu.lst", $config_name, "(nd)", \@$modules, "timeout 0");
895 open(my $fh, '>', 'dhcpd.conf');
896 my $mac = `cat /sys/class/net/eth0/address`;
898 print $fh "subnet 10.23.23.0 netmask 255.255.255.0 {
899 range 10.23.23.10 10.23.23.100;
900 filename \"bin/boot/grub/pxegrub.pxe\";
901 next-server 10.23.23.1;
904 hardware ethernet $mac;
905 fixed-address 10.23.23.1;
908 system_verbose("sudo ip a add 10.23.23.1/24 dev eth0;
909 sudo ip l set dev eth0 up;
910 sudo touch dhcpd.leases");
912 # We run servers by forking ourselves, because the servers end up
913 # in our process group and get killed by signals sent to the
914 # process group (e.g. Ctrl-C on terminal).
916 exec_verbose("sudo dhcpd -d -cf dhcpd.conf -lf dhcpd.leases -pf dhcpd.pid") if ($dhcpd_pid == 0);
919 if (defined $dhcp_tftp || defined $tftp) {
921 # Unfortunately, tftpd requires root privileges even with
922 # non-privileged (>1023) port due to initgroups().
923 system_verbose("sudo in.tftpd --listen --secure -v -v -v --pidfile tftpd.pid --address :$tftp_port $builddir");
925 # Kill server when we die
926 $SIG{__DIE__} = sub { system_verbose('sudo pkill --pidfile=dhcpd.pid') if (defined $dhcp_tftp);
927 system_verbose('sudo pkill --pidfile=tftpd.pid'); };
929 # We have to kill tftpd explicitely, because it is not in our process group
930 $SIG{INT} = sub { system_verbose('sudo pkill --pidfile=tftpd.pid'); exit(0); };
935 my $ider_cmd= "amtider -c $iso_image -u $amt_user -p $amt_password $amt_host $amt_port" ;
936 print "novaboot: Running: $ider_cmd\n" =~ s/\Q$amt_password\E/???/r;
937 my $ider_pid = fork();
938 if ($ider_pid == 0) {
940 die "IDE redirection failed";
942 # FIXME: This collides with --tftp option. Hopefully, nobody needs
943 # to use both simultaneously.
944 $SIG{__DIE__} = sub { system_verbose('kill $ider_pid'); };
947 ### Reset target (IP relay, AMT, ...)
949 if (defined $target_reset) {
950 print "novaboot: Reseting the test box... ";
955 ### U-boot conversation
956 if (defined $uboot) {
957 my $uboot_prompt = $uboot || '=> ';
958 print "novaboot: Waiting for uBoot prompt...\n";
960 #$exp->exp_internal(1);
962 [qr/Hit any key to stop autoboot:/, sub { $exp->send("\n"); exp_continue; }],
963 $uboot_prompt) || die "No uBoot prompt deteceted";
964 while (@uboot_init) {
965 my $cmd = shift @uboot_init;
966 $exp->send("$cmd\n");
967 $exp->expect(10, $uboot_prompt) || die "uBoot prompt timeout";
970 # Boot the system if there are some load lines in the script
971 if (scalar(@$modules) > 0) {
972 my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);
974 @$modules = map { if (/\.dtb$/) { $dtb=$_; (); } else { $_ } } @$modules;
975 my $initrd = shift @$modules;
977 my $kern_addr = '800000';
978 my $initrd_addr = '-';
981 $exp->send("tftp $kern_addr $kbin\n");
983 [qr/#/, sub { exp_continue; }],
984 $uboot_prompt) || die "Kernel load failed";
986 $dtb_addr = '7f0000';
987 $exp->send("tftp $dtb_addr $dtb\n");
989 [qr/#/, sub { exp_continue; }],
990 $uboot_prompt) || die "Device tree load failed";
992 if (defined $initrd) {
993 $initrd_addr = 'b00000';
994 $exp->send("tftp $initrd_addr $initrd\n");
996 [qr/#/, sub { exp_continue; }],
997 $uboot_prompt) || die "Initrd load failed";
999 $exp->send("set bootargs '$kcmd'\n");
1000 $exp->expect(5, $uboot_prompt) || die "uBoot prompt timeout";
1001 $exp->send("bootm $kern_addr $initrd_addr $dtb_addr\n");
1002 $exp->expect(5, "\n") || die "uBoot command timeout";
1006 ### Serial line interaction
1008 # Serial line of the target is available
1009 my $interrupt = 'Ctrl-C';
1010 if ($interactive && !@exiton) {
1011 $interrupt = '"~~."';
1013 my $note = (-t STDIN) ? '' : '- only target->host ';
1014 print "novaboot: Serial line interaction $note(press $interrupt to interrupt)...\n";
1015 $exp->log_stdout(1);
1017 $exp->expect(undef, @expect_raw, @exiton);
1019 my @inputs = ($exp);
1020 if (-t STDIN) { # Set up bi-directional communication if we run on terminal
1021 my $infile = new IO::File;
1022 $infile->IO::File::fdopen(*STDIN,'r');
1023 my $in_object = Expect->exp_init($infile);
1024 $in_object->set_group($exp);
1027 $in_object->set_seq('~~\.', sub { print "novaboot: Escape sequence detected\r\n"; undef; });
1028 $in_object->manual_stty(0); # Use raw terminal mode
1030 $in_object->manual_stty(1); # Do not modify terminal settings
1032 push(@inputs, $in_object);
1035 #print Dumper(\@expect_raw);
1036 $exp->expect(undef, @expect_raw) if @expect_raw;
1037 Expect::interconnect(@inputs) unless defined($exp->exitstatus);
1041 ## Kill dhcpc or tftpd
1042 if (defined $dhcp_tftp || defined $tftp) {
1043 die("novaboot: This should kill servers on background\n");
1050 novaboot - A tool for booting various operating systems on various hardware or in qemu
1056 B<novaboot> [option]... [--] script...
1058 B<./script> [option]...
1062 This program makes booting of an operating system (e.g. NOVA or Linux)
1063 as simple as running a local program. It facilitates booting on local
1064 or remote hosts or in emulators such as qemu. Novaboot operation is
1065 controlled by command line options and by a so called novaboot script,
1066 which can be thought as a generalization of bootloader configuration
1067 files (see L</"NOVABOOT SCRIPT SYNTAX">). Based on this input,
1068 novaboot setups everything for the target host to boot the desired
1069 configuration, i.e. it generates the bootloader configuration file in
1070 the proper format, deploy the binaries and other needed files to
1071 required locations, perhaps on a remote boot server and reset the
1072 target host. Finally, target host's serial output is redirected to
1073 standard output if that is possible.
1075 Typical way of using novaboot is to make the novaboot script
1076 executable and set its first line to I<#!/usr/bin/env novaboot>. Then,
1077 booting a particular OS configuration becomes the same as executing a
1078 local program - the novaboot script.
1080 For example, with C<novaboot> you can:
1086 Run an OS in Qemu. This is the default action when no other action is
1087 specified by command line switches. Thus running C<novaboot ./script>
1088 (or C<./script> as described above) will run Qemu and make it boot the
1089 configuration specified in the F<script>.
1093 Create a bootloader configuration file (currently supported
1094 bootloaders are GRUB, GRUB2, Pulsar and U-Boot) and copy it with all
1095 other files needed for booting to a remote boot server.
1097 ./script --server=192.168.1.1:/tftp --iprelay=192.168.1.2
1099 This command copies files to the TFTP server and uses
1100 TCP/IP-controlled relay to reset the target host and receive its
1105 Run DHCP and TFTP server on developer's machine to PXE-boot the target
1108 ./script --dhcp-tftp
1110 When a PXE-bootable machine is connected via Ethernet to developer's
1111 machine, it will boot the configuration described in I<script>.
1115 Create bootable ISO images. E.g.
1117 novaboot --iso -- script1 script2
1119 The created ISO image will have GRUB bootloader installed on it and
1120 the boot menu will allow selecting between I<script1> and I<script2>
1125 Note that the options needed for a specific target can be stored in a
1126 L</"CONFIGURATION FILE">. Then it is sufficient to use only the B<-t>
1127 option to specify the name of the target.
1129 =head1 PHASES AND OPTIONS
1131 Novaboot performs its work in several phases. Each phase can be
1132 influenced by several options, certain phases can be skipped. The list
1133 of phases (in the execution order) and the corresponding options
1136 =head2 Configuration reading phase
1138 After starting, novaboot reads configuration files. By default, it
1139 searches for files named F<.novaboot> starting from the directory of
1140 the novaboot script (or working directory, see bellow) and continuing
1141 upwards up to the root directory. The configuration files are read in
1142 order from the root directory downwards with latter files overriding
1143 settings from the former ones.
1145 In certain cases, the location of the novaboot script cannot be
1146 determined in this early phase. This happens either when the script is
1147 read from the standard input or when novaboot is invoked explicitly
1148 and options precede the script name, as in the example L</"4."> above.
1149 In this case the current working directory is used as a starting point
1150 for configuration file search.
1154 =item -c, --config=I<filename>
1156 Use the specified configuration file instead of the default one(s).
1160 =head2 Command line processing phase
1166 Dump the current configuration to stdout end exits. Useful as an
1167 initial template for a configuration file.
1171 Print short (B<-h>) or long (B<--help>) help.
1173 =item -t, --target=I<target>
1175 This option serves as a user configurable shortcut for other novaboot
1176 options. The effect of this option is the same as the options stored
1177 in the C<%targets> configuration variable under key I<target>. See
1178 also L</"CONFIGURATION FILE">.
1182 =head2 Script preprocessing phase
1184 This phases allows to modify the parsed novaboot script before it is
1185 used in the later phases.
1189 =item -a, --append=I<parameters>
1191 Append a string to the first C<load> line in the novaboot script. This
1192 can be used to append parameters to the kernel's or root task's
1193 command line. Can appear multiple times.
1197 Use F<bender> chainloader. Bender scans the PCI bus for PCI serial
1198 ports and stores the information about them in the BIOS data area for
1201 =item --chainloader=I<chainloader>
1203 Chainloader that is loaded before the kernel and other files specified
1204 in the novaboot script. E.g. 'bin/boot/bender promisc'.
1208 Print the modules to boot and their parameters after this phase
1209 finishes. Then exit. This is useful for seeing the effect of other
1210 options in this section.
1212 =item -k, --kernel=F<file>
1214 Replace the first word on the first C<load> line in the novaboot
1215 script with F<file>.
1217 =item --scriptmod=I<perl expression>
1219 When novaboot script is read, I<perl expression> is executed for every
1220 line (in $_ variable). For example, C<novaboot
1221 --scriptmod=s/sigma0/omega6/g> replaces every occurrence of I<sigma0>
1222 in the script with I<omega6>.
1224 When this option is present, it overrides I<$script_modifier> variable
1225 from the configuration file, which has the same effect. If this option
1226 is given multiple times all expressions are evaluated in the command
1231 =head2 File generation phase
1233 In this phase, files needed for booting are generated in a so called
1234 I<build directory> (see L</--build-dir>). In most cases configuration
1235 for a bootloader is generated automatically by novaboot. It is also
1236 possible to generate other files using I<heredoc> or I<"<"> syntax in
1237 novaboot scripts. Finally, binaries can be generated in this phases by
1238 running C<scons> or C<make>.
1242 =item --build-dir=I<directory>
1244 Overrides the default build directory location.
1246 The default build directory location is determined as follows: If the
1247 configuration file defines the C<$builddir> variable, its value is
1248 used. Otherwise, it is the directory that contains the first processed
1251 See also L</BUILDDIR> variable.
1253 =item -g, --grub[=I<filename>]
1255 Generates grub bootloader menu file. If the I<filename> is not
1256 specified, F<menu.lst> is used. The I<filename> is relative to the
1257 build directory (see B<--build-dir>).
1259 =item --grub-preamble=I<prefix>
1261 Specifies the I<preable> that is at the beginning of the generated
1262 GRUB or GRUB2 config files. This is useful for specifying GRUB's
1265 =item --grub-prefix=I<prefix>
1267 Specifies I<prefix> that is put in front of every file name in GRUB's
1268 F<menu.lst>. The default value is the absolute path to the build directory.
1270 If the I<prefix> contains string $NAME, it will be replaced with the
1271 name of the novaboot script (see also B<--name>).
1273 =item --grub2[=I<filename>]
1275 Generate GRUB2 menuentry in I<filename>. If I<filename> is not
1276 specified F<grub.cfg> is used. The content of the menuentry can be
1277 customized with B<--grub-preable>, B<--grub2-prolog> or
1278 B<--grub_prefix> options.
1280 In order to use the the generated menuentry on your development
1281 machine that uses GRUB2, append the following snippet to
1282 F</etc/grub.d/40_custom> file and regenerate your grub configuration,
1283 i.e. run update-grub on Debian/Ubuntu.
1285 if [ -f /path/to/nul/build/grub.cfg ]; then
1286 source /path/to/nul/build/grub.cfg
1289 =item --grub2-prolog=I<prolog>
1291 Specifies text I<preable> that is put at the beginning of the entry
1294 =item -m, --make[=make command]
1296 Runs C<make> to build files that are not generated by novaboot itself.
1298 =item --name=I<string>
1300 Use the name I<string> instead of the name of the novaboot script.
1301 This name is used for things like a title of grub menu or for the
1302 server directory where the boot files are copied to.
1306 Do not run external commands to generate files (i.e. "<" syntax and
1307 C<run> keyword). This switch does not influence generation of files
1308 specified with "<<WORD" syntax.
1310 =item -p, --pulsar[=mac]
1312 Generates pulsar bootloader configuration file named F<config-I<mac>>
1313 The I<mac> string is typically a MAC address and defaults to
1316 =item --scons[=scons command]
1318 Runs C<scons> to build files that are not generated by novaboot
1323 Strip I<rom://> prefix from command lines and generated config files.
1324 The I<rom://> prefix is used by NUL. For NRE, it has to be stripped.
1328 Exit novaboot after file generation phase.
1332 =head2 Target connection check
1334 If supported by the target, the connection to it is made and it is
1335 checked whether the target is not occupied by another novaboot
1340 =item --amt=I<"[user[:password]@]host[:port]>
1342 Use Intel AMT technology to control the target machine. WS management
1343 is used to powercycle it and Serial-Over-Lan (SOL) for input/output.
1344 The hostname or (IP address) is given by the I<host> parameter. If
1345 I<password> is not specified, environment variable AMT_PASSWORD is
1346 used. The I<port> specifies a TCP port for SOL. If not specified, the
1347 default is 16992. Default I<user> is admin.
1351 Use Intel AMT technology for IDE redirection. This allows the target
1352 machine to boot from nonvaboot created ISO image.
1354 The experimental I<amtider> utility needed by this option can be
1355 obtained from https://github.com/wentasah/amtterm.
1357 =item --iprelay=I<addr[:port]>
1359 Use TCP/IP relay and serial port to access the target's serial port
1360 and powercycle it. The IP address of the relay is given by I<addr>
1361 parameter. If I<port> is not specified, it default to 23.
1363 Note: This option is supposed to work with HWG-ER02a IP relays.
1365 =item -s, --serial[=device]
1367 Target's serial line is connected to host's serial line (device). The
1368 default value for device is F</dev/ttyUSB0>.
1370 The value of this option is exported in NB_NOVABOOT environment
1371 variable to all subprocesses run by C<novaboot>.
1373 =item --stty=I<settings>
1375 Specifies settings passed to C<stty> invoked on the serial line
1376 specified with B<--serial> option. If this option is not given,
1377 C<stty> is called with C<raw -crtscts -onlcr 115200> settings.
1379 =item --remote-cmd=I<cmd>
1381 Command that mediates connection to the target's serial line. For
1382 example C<ssh server 'cu -l /dev/ttyS0'>.
1384 =item --remote-expect=I<string>
1386 Wait for reception of I<string> after establishing the the remote
1387 connection before continuing.
1392 =head2 File deployment phase
1394 In some setups, it is necessary to copy the files needed for booting
1395 to a particular location, e.g. to a TFTP boot server or to the
1400 =item -d, --dhcp-tftp
1402 Turns your workstation into a DHCP and TFTP server so that the OS can
1403 be booted via PXE BIOS (or similar mechanism) on the test machine
1404 directly connected by a plain Ethernet cable to your workstation.
1406 The DHCP and TFTP servers requires root privileges and C<novaboot>
1407 uses C<sudo> command to obtain those. You can put the following to
1408 I</etc/sudoers> to allow running the necessary commands without asking
1411 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
1412 your_login ALL=NOPASSWD: NOVABOOT
1416 Starts a TFTP server on your workstation. This is similar to
1417 B<--dhcp-tftp> except that DHCP server is not started.
1419 The TFTP server require root privileges and C<novaboot> uses C<sudo>
1420 command to obtain those. You can put the following to I</etc/sudoers>
1421 to allow running the necessary commands without asking for password.
1423 Cmnd_Alias NOVABOOT = /usr/sbin/in.tftpd --listen --secure -v -v -v --pidfile tftpd.pid *, /usr/bin/pkill --pidfile=tftpd.pid
1424 your_login ALL=NOPASSWD: NOVABOOT
1426 =item --tftp-port=I<port>
1428 Port to run the TFTP server on. Implies B<--tftp>.
1430 =item --iso[=filename]
1432 Generates the ISO image that boots NOVA system via GRUB. If no filename
1433 is given, the image is stored under I<NAME>.iso, where I<NAME> is the name
1434 of the novaboot script (see also B<--name>).
1436 =item --server[=[[user@]server:]path]
1438 Copy all files needed for booting to another location (implies B<-g>
1439 unless B<--grub2> is given). The files will be copied (by B<rsync>
1440 tool) to the directory I<path>. If the I<path> contains string $NAME,
1441 it will be replaced with the name of the novaboot script (see also
1446 If B<--server> is used and its value ends with $NAME, then after
1447 copying the files, a new bootloader configuration file (e.g. menu.lst)
1448 is created at I<path-wo-name>, i.e. the path specified by B<--server>
1449 with $NAME part removed. The content of the file is created by
1450 concatenating all files of the same name from all subdirectories of
1451 I<path-wo-name> found on the "server".
1453 =item --rsync-flags=I<flags>
1455 Specifies which I<flags> are appended to F<rsync> command line when
1456 copying files as a result of I<--server> option.
1460 =head2 Target power-on and reset phase
1466 Switch on/off the target machine and exit. The script (if any) is
1467 completely ignored. Currently it works only with B<--iprelay> or
1470 =item -Q, --qemu[=I<qemu-binary>]
1472 Boot the configuration in qemu. Optionally, the name of qemu binary
1473 can be specified as a parameter.
1475 =item --qemu-append=I<flags>
1477 Append I<flags> to the default qemu flags (QEMU_FLAGS variable or
1478 C<-cpu coreduo -smp 2>).
1480 =item -q, --qemu-flags=I<flags>
1482 Replace the default qemu flags (QEMU_FLAGS variable or C<-cpu coreduo
1483 -smp 2>) with I<flags> specified here.
1485 =item --reset-cmd=I<cmd>
1487 Command that resets the target.
1491 =head2 Interaction with the bootloader on the target
1495 =item --uboot[=I<prompt>]
1497 Interact with uBoot bootloader to boot the thing described in the
1498 novaboot script. I<prompt> specifies the U-Boot's prompt (default is
1499 "=> ", other common prompts are "U-Boot> " or "U-Boot# ").
1500 Implementation of this option is currently tied to a particular board
1501 that we use. It may be subject to changes in the future!
1505 Command(s) to send the U-Boot bootloader before loading the images and
1506 booting them. This option can be given multiple times. After sending
1507 commands from each option novaboot waits for U-Boot I<prompt>.
1511 =head2 Target interaction phase
1513 In this phase, target's serial output is redirected to stdout and if
1514 stdin is a TTY, it is redirected to the target's serial input allowing
1515 interactive work with the target.
1519 =item --exiton=I<string>
1521 When I<string> is sent by the target, novaboot exits. This option can
1522 be specified multiple times.
1524 If I<string> is C<-re>, then the next B<--exiton>'s I<string> is
1525 treated as regular expression. For example:
1527 --exiton -re --exiton 'error:.*failed'
1529 =item -i, --interactive
1531 Setup things for interactive use of target. Your terminal will be
1532 switched to raw mode. In raw mode, your system does not process input
1533 in any way (no echoing of entered characters, no interpretation
1534 special characters). This, among others, means that Ctrl-C is passed
1535 to the target and does no longer interrupt novaboot. Use "~~."
1536 sequence to exit novaboot.
1538 =item --expect=I<string>
1540 When I<string> is received from the target, send the string specified
1541 with the subsequent B<--send*> option to the target.
1543 =item --expect-re=I<regex>
1545 When target's output matches regular expression I<regex>, send the
1546 string specified with the subsequent B<--send*> option to the target.
1548 =item --expect-raw=I<perl-code>
1550 Provides direct control over Perl's Expect module.
1552 =item --send=I<string>
1554 Send I<string> to the target after the previously specified
1555 B<--expect*> was matched in the target's output. The I<string> may
1556 contain escape sequences such as "\n".
1558 Note that I<string> is actually interpreted by Perl, so it can contain
1559 much more that escape sequences. This behavior may change in the
1562 Example: C<--expect='login: ' --send='root\n'>
1564 =item --sendcont=I<string>
1566 Similar to B<--send> but continue expecting more input.
1568 Example: C<--expect='Continue?' --sendcont='yes\n'>
1572 =head1 NOVABOOT SCRIPT SYNTAX
1574 The syntax tries to mimic POSIX shell syntax. The syntax is defined
1575 with the following rules.
1577 Lines starting with "#" and empty lines are ignored.
1579 Lines that end with "\" are concatenated with the following line after
1580 removal of the final "\" and leading whitespace of the following line.
1582 Lines of the form I<VARIABLE=...> (i.e. matching '^[A-Z_]+=' regular
1583 expression) assign values to internal variables. See L</VARIABLES>
1586 Lines starting with C<load> keyword represent modules to boot. The
1587 word after C<load> is a file name (relative to the build directory
1588 (see B<--build-dir>) of the module to load and the remaining words are
1589 passed to it as the command line parameters.
1591 When the C<load> line ends with "<<WORD" then the subsequent lines
1592 until the line containing solely WORD are copied literally to the file
1593 named on that line. This is similar to shell's heredoc feature.
1595 When the C<load> line ends with "< CMD" then command CMD is executed
1596 with F</bin/sh> and its standard output is stored in the file named on
1597 that line. The SRCDIR variable in CMD's environment is set to the
1598 absolute path of the directory containing the interpreted novaboot
1601 Lines starting with C<run> keyword contain shell commands that are run
1602 during file generation phase. This is the same as the "< CMD" syntax
1603 for C<load> keyboard except that the command's output is not
1604 redirected to a file. The ordering of commands is the same as they
1605 appear in the novaboot script.
1609 #!/usr/bin/env novaboot
1610 load bzImage console=ttyS0,115200
1611 run make -C buildroot
1612 load rootfs.cpio < gen_cpio buildroot/images/rootfs.cpio "myapp->/etc/init.d/S99myapp"
1614 Example (NOVA User Land - NUL):
1616 #!/usr/bin/env novaboot
1617 WVDESC=Example program
1618 load bin/apps/sigma0.nul S0_DEFAULT script_start:1,1 \
1619 verbose hostkeyb:0,0x60,1,12,2
1620 load bin/apps/hello.nul
1621 load hello.nulconfig <<EOF
1622 sigma0::mem:16 name::/s0/log name::/s0/timer name::/s0/fs/rom ||
1623 rom://bin/apps/hello.nul
1626 This example will load three modules: F<sigma0.nul>, F<hello.nul> and
1627 F<hello.nulconfig>. sigma0 receives some command line parameters and
1628 F<hello.nulconfig> file is generated on the fly from the lines between
1629 C<<<EOF> and C<EOF>.
1633 The following variables are interpreted in the novaboot script:
1639 Novaboot chdir()s to this directory before file generation phase. The
1640 directory name specified here is relative to the build directory
1641 specified by other means (see L</--build-dir>).
1645 Assigning this variable has the same effect as specifying L</--exiton>
1648 =item HYPERVISOR_PARAMS
1650 Parameters passed to hypervisor. The default value is "serial", unless
1651 overridden in configuration file.
1655 The kernel to use instead of the hypervisor specified in the
1656 configuration file with the C<$hypervisor> variable. The value should
1657 contain the name of the kernel image as well as its command line
1658 parameters. If this variable is defined and non-empty, the variable
1659 HYPERVISOR_PARAMS is not used.
1663 Use a specific qemu binary (can be overridden with B<-Q>) and flags
1664 when booting this script under qemu. If QEMU_FLAGS variable is also
1665 specified flags specified in QEMU variable are replaced by those in
1670 Use specific qemu flags (can be overridden with B<-q>).
1674 Description of the wvtest-compliant program.
1676 =item WVTEST_TIMEOUT
1678 The timeout in seconds for WvTest harness. If no complete line appears
1679 in the test output within the time specified here, the test fails. It
1680 is necessary to specify this for long running tests that produce no
1681 intermediate output.
1685 =head1 CONFIGURATION FILE
1687 Novaboot can read its configuration from one or more files. By
1688 default, novaboot looks for files named F<.novaboot> as described in
1689 L</Configuration reading phase>. Alternatively, its location can be
1690 specified with the B<-c> switch or with the NOVABOOT_CONFIG
1691 environment variable. The configuration file has perl syntax and
1692 should set values of certain Perl variables. The current configuration
1693 can be dumped with the B<--dump-config> switch. Some configuration
1694 variables can be overridden by environment variables (see below) or by
1695 command line switches.
1697 Supported configuration variables include:
1703 Build directory location relative to the location of the configuration
1706 =item $default_target
1708 Default target (see below) to use when no target is explicitly
1709 specified on command line with the B<--target> option.
1713 Hash of shortcuts to be used with the B<--target> option. If the hash
1714 contains, for instance, the following pair of values
1716 'mybox' => '--server=boot:/tftproot --serial=/dev/ttyUSB0 --grub',
1718 then the following two commands are equivalent:
1720 ./script --server=boot:/tftproot --serial=/dev/ttyUSB0 --grub
1725 =head1 ENVIRONMENT VARIABLES
1727 Some options can be specified not only via config file or command line
1728 but also through environment variables. Environment variables override
1729 the values from configuration file and command line parameters
1730 override the environment variables.
1734 =item NOVABOOT_CONFIG
1736 Name of the novaboot configuration file to use instead of the default
1739 =item NOVABOOT_BENDER
1741 Defining this variable has the same meaning as B<--bender> option.
1747 Michal Sojka <sojka@os.inf.tu-dresden.de>