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);
32 use LWP::Authen::Digest;
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 -cpu coreduo -smp 2';
46 $CFG::default_target = 'qemu';
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=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',
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 "ryuglab" => '--server=pc-sojkam.felk.cvut.cz:/srv/tftp --uboot --uboot-init="mw f0000b00 \${psc_cfg}; sleep 1" --remote-cmd="ssh -t pc-sojkam.felk.cvut.cz \"cu -l /dev/ttyUSB0 -s 115200\"" --remote-expect="Connected." --reset-cmd="ssh -t pc-sojkam.felk.cvut.cz \"dtrrts /dev/ttyUSB0 1 1\""',
53 "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"',
56 chomp(my $nproc = `nproc`);
57 $CFG::scons = "scons -j$nproc";
58 $CFG::make = "make -j$nproc";
65 package CFG; # Put config data into a separate namespace
70 die("ERROR: Failure compiling '$cfg' - $@");
71 } elsif (! defined($rc)) {
72 die("ERROR: Failure reading '$cfg' - $!");
74 die("ERROR: Failure processing '$cfg'");
77 $builddir = File::Spec->rel2abs($CFG::builddir, dirname($cfg)) if defined $CFG::builddir;
78 print STDERR "novaboot: Read $cfg\n";
83 # We don't use $0 here, because it points to the novaboot itself and
84 # not to the novaboot script. The problem with this approach is that
85 # when a script is run as "novaboot <options> <script>" then $ARGV[0]
86 # contains the first option. Hence the -f check.
87 my $dir = File::Spec->rel2abs($ARGV[0] && -f $ARGV[0] ? dirname($ARGV[0]) : '', $invocation_dir);
88 while ((-d $dir || -l $dir ) && $dir ne "/") {
89 push @cfgs, "$dir/.novaboot" if -r "$dir/.novaboot";
90 my @dirs = File::Spec->splitdir($dir);
91 $dir = File::Spec->catdir(@dirs[0..$#dirs-1]);
94 my $cfg = $ENV{'NOVABOOT_CONFIG'};
95 Getopt::Long::Configure(qw/no_ignore_case pass_through/);
96 GetOptions ("config|c=s" => \$cfg);
97 read_config($_) foreach $cfg or reverse @cfgs;
99 ## Command line handling
102 GetOptions ("target|t=s" => \$explicit_target);
104 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, $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, $uboot, $uboot_init);
107 $rom_prefix = 'rom://';
108 $stty = 'raw -crtscts -onlcr 115200';
110 my @expect_seen = ();
114 push(@expect_seen, '-re') if $n eq "expect-re";
115 push(@expect_seen, $v);
121 unless (@expect_seen) { die("No --expect before --send"); }
122 my $ret = ($n eq "sendcont") ? exp_continue : 0;
123 unshift(@expect_raw, sub { shift->send(eval("\"$v\"")); $ret; });
124 unshift(@expect_raw, @expect_seen);
128 Getopt::Long::Configure(qw/no_ignore_case no_pass_through/);
132 "append|a=s" => \@append,
133 "bender|b" => \$bender,
134 "build-dir=s" => sub { my ($n, $v) = @_; $builddir = File::Spec->rel2abs($v); },
135 "concat" => \$concat,
136 "chainloader=s" => \@chainloaders,
137 "dhcp-tftp|d" => \$dhcp_tftp,
138 "dump" => \$dump_opt,
139 "dump-config" => \$dump_config,
140 "exiton=s" => \@exiton,
141 "expect=s" => \&handle_expect,
142 "expect-re=s" => \&handle_expect,
143 "expect-raw=s" => sub { my ($n, $v) = @_; unshift(@expect_raw, eval($v)); },
144 "gen-only" => \$gen_only,
145 "grub|g:s" => \$grub_config,
146 "grub-preamble=s"=> \$grub_preamble,
147 "grub-prefix=s" => \$grub_prefix,
148 "grub2:s" => \$grub2_config,
149 "grub2-prolog=s" => \$grub2_prolog,
150 "iprelay=s" => \$iprelay,
151 "iso:s" => \$iso_image,
152 "kernel|k=s" => \$kernel_opt,
153 "interactive|i" => \$interactive,
154 "name=s" => \$config_name_opt,
155 "make|m:s" => \$make,
156 "no-file-gen" => \$no_file_gen,
159 "pulsar|p:s" => \$pulsar,
160 "pulsar-root=s" => \$pulsar_root,
161 "qemu|Q:s" => \$qemu,
162 "qemu-append=s" => \$qemu_append,
163 "qemu-flags|q=s" => \$qemu_flags_cmd,
164 "remote-cmd=s" => \$remote_cmd,
165 "remote-expect=s"=> \$remote_expect,
166 "reset-cmd=s" => \$reset_cmd,
167 "rsync-flags=s" => \$rsync_flags,
168 "scons:s" => \$scons,
169 "scriptmod=s" => \@scriptmod,
170 "send=s" => \&handle_send,
171 "sendcont=s" => \&handle_send,
172 "serial|s:s" => \$serial,
173 "server:s" => \$server,
174 "strip-rom" => sub { $rom_prefix = ''; },
177 "uboot-init=s" => \$uboot_init,
182 # First process target options
184 my $t = defined($explicit_target) ? $explicit_target : $CFG::default_target;
186 exists $CFG::targets{$t} or die("Unknown target '$t' (valid targets are: ".join(", ", sort keys(%CFG::targets)).")");
187 GetOptionsFromString($CFG::targets{$t}, %opt_spec);
191 # Then process other command line options - some of them may override
192 # what was specified by the target
193 GetOptions %opt_spec or die("Error in command line arguments");
194 pod2usage(1) if $help;
195 pod2usage(-exitstatus => 0, -verbose => 2) if $man;
197 ### Dump sanitized configuration (if requested)
201 $Data::Dumper::Indent=1;
202 print "# This file is in perl syntax.\n";
203 foreach my $key(sort(keys(%CFG::))) { # See "Symbol Tables" in perlmod(1)
204 if (defined ${$CFG::{$key}}) { print Data::Dumper->Dump([${$CFG::{$key}}], ["*$key"]); }
205 if ( @{$CFG::{$key}}) { print Data::Dumper->Dump([\@{$CFG::{$key}}], ["*$key"]); }
206 if ( %{$CFG::{$key}}) { print Data::Dumper->Dump([\%{$CFG::{$key}}], ["*$key"]); }
212 ### Sanitize configuration
214 if ($interactive && !-t STDIN) {
215 die("novaboot: Interactive mode not supported when not on terminal");
218 if (defined $config_name_opt && scalar(@ARGV) > 1) { die "You cannot use --name with multiple scripts"; }
221 if (defined $serial) {
222 $serial ||= "/dev/ttyUSB0";
223 $ENV{NB_SERIAL} = $serial;
225 if (defined $grub_config) { $grub_config ||= "menu.lst"; }
226 if (defined $grub2_config) { $grub2_config ||= "grub.cfg"; }
228 ## Parse the novaboot script(s)
233 my ($modules, $variables, $generated, $continuation);
235 if ($ARGV ne $last_fn) { # New script
236 die "Missing EOF in $last_fn" if $file;
237 die "Unfinished line in $last_fn" if $continuation;
239 push @scripts, { 'filename' => $ARGV,
240 'modules' => $modules = [],
241 'variables' => $variables = {},
242 'generated' => $generated = []};
246 next if /^#/ || /^\s*$/; # Skip comments and empty lines
248 $_ =~ s/^[[:space:]]*// if ($continuation);
250 if (/\\$/) { # Line continuation
251 $continuation .= substr($_, 0, length($_)-1);
255 if ($continuation) { # Last continuation line
256 $_ = $continuation . $_;
260 foreach my $mod(@scriptmod) { eval $mod; }
262 if ($file && $_ eq $EOF) { # Heredoc end
266 if ($file) { # Heredoc content
267 push @{$file}, "$_\n";
270 if (/^([A-Z_]+)=(.*)$/) { # Internal variable
271 $$variables{$1} = $2;
272 push(@exiton, $2) if ($1 eq "EXITON");
275 if (s/^load *//) { # Load line
276 die("novaboot: '$last_fn' line $.: Missing file name\n") unless /^[^ <]+/;
277 if (/^([^ ]*)(.*?)[[:space:]]*<<([^ ]*)$/) { # Heredoc start
278 push @$modules, "$1$2";
280 push @$generated, {filename => $1, content => $file};
284 if (/^([^ ]*)(.*?)[[:space:]]*< ?(.*)$/) { # Command substitution
285 push @$modules, "$1$2";
286 push @$generated, {filename => $1, command => $3};
293 die("novaboot: Cannot parse script '$last_fn' line $.. Didn't you forget 'load' keyword?\n");
296 # print Dumper(\@scripts);
298 foreach my $script (@scripts) {
299 $modules = $$script{modules};
300 @$modules[0] =~ s/^[^ ]*/$kernel_opt/ if $kernel_opt;
301 @$modules[0] .= ' ' . join(' ', @append) if @append;
304 if (exists $variables->{KERNEL}) {
305 $kernel = $variables->{KERNEL};
307 if ($CFG::hypervisor) {
308 $kernel = $CFG::hypervisor . " ";
309 if (exists $variables->{HYPERVISOR_PARAMS}) {
310 $kernel .= $variables->{HYPERVISOR_PARAMS};
312 $kernel .= $CFG::hypervisor_params;
316 @$modules = ($kernel, @$modules) if $kernel;
317 @$modules = (@chainloaders, @$modules);
318 @$modules = ("bin/boot/bender", @$modules) if ($bender || defined $ENV{'NOVABOOT_BENDER'});
322 foreach my $script (@scripts) {
323 print join("\n", @{$$script{modules}})."\n";
330 sub generate_configs($$$) {
331 my ($base, $generated, $filename) = @_;
332 if ($base) { $base = "$base/"; };
333 foreach my $g(@$generated) {
334 if (exists $$g{content}) {
335 my $config = $$g{content};
336 my $fn = $$g{filename};
337 open(my $f, '>', $fn) || die("$fn: $!");
338 map { s|\brom://([^ ]*)|$rom_prefix$base$1|g; print $f "$_"; } @{$config};
340 print "novaboot: Created $fn\n";
341 } elsif (exists $$g{command} && ! $no_file_gen) {
342 $ENV{SRCDIR} = dirname(File::Spec->rel2abs( $filename, $invocation_dir ));
343 system_verbose("( $$g{command} ) > $$g{filename}");
348 sub generate_grub_config($$$$;$)
350 my ($filename, $title, $base, $modules_ref, $preamble) = @_;
351 if ($base) { $base = "$base/"; };
352 open(my $fg, '>', $filename) or die "$filename: $!";
353 print $fg "$preamble\n" if $preamble;
354 print $fg "title $title\n" if $title;
355 #print $fg "root $base\n"; # root doesn't really work for (nd)
357 foreach (@$modules_ref) {
360 my ($kbin, $kcmd) = split(' ', $_, 2);
361 $kcmd = '' if !defined $kcmd;
362 print $fg "kernel ${base}$kbin $kcmd\n";
364 s|\brom://([^ ]*)|$rom_prefix$base$1|g; # Translate rom:// files - needed for vdisk parameter of sigma0
365 print $fg "module $base$_\n";
369 print("novaboot: Created $builddir/$filename\n");
373 sub generate_grub2_config($$$$;$$)
375 my ($filename, $title, $base, $modules_ref, $preamble, $prolog) = @_;
376 if ($base && substr($base,-1,1) ne '/') { $base = "$base/"; };
377 open(my $fg, '>', $filename) or die "$filename: $!";
378 print $fg "$preamble\n" if $preamble;
379 $title ||= 'novaboot';
380 print $fg "menuentry $title {\n";
381 print $fg "$prolog\n" if $prolog;
383 foreach (@$modules_ref) {
386 my ($kbin, $kcmd) = split(' ', $_, 2);
387 $kcmd = '' if !defined $kcmd;
388 print $fg " multiboot ${base}$kbin $kcmd\n";
391 # GRUB2 doesn't pass filename in multiboot info so we have to duplicate it here
392 $_ = join(' ', ($args[0], @args));
393 s|\brom://|$rom_prefix|g; # We do not need to translate path for GRUB2
394 print $fg " module $base$_\n";
399 print("novaboot: Created $builddir/$filename\n");
403 sub generate_pulsar_config($$)
405 my ($filename, $modules_ref) = @_;
406 open(my $fg, '>', $filename) or die "$filename: $!";
407 print $fg "root $pulsar_root\n" if defined $pulsar_root;
410 foreach (@$modules_ref) {
413 ($kbin, $kcmd) = split(' ', $_, 2);
414 $kcmd = '' if !defined $kcmd;
417 s|\brom://|$rom_prefix|g;
418 print $fg "load $_\n";
421 # Put kernel as last - this is needed for booting Linux and has no influence on non-Linux OSes
422 print $fg "exec $kbin $kcmd\n";
424 print("novaboot: Created $builddir/$filename\n");
428 sub shell_cmd_string(@)
430 return join(' ', map((/^[-_=a-zA-Z0-9\/\.\+]+$/ ? "$_" : "'$_'"), @_));
435 print "novaboot: Running: ".shell_cmd_string(@_)."\n";
439 sub system_verbose($)
442 print "novaboot: Running: $cmd\n";
443 my $ret = system($cmd);
444 if ($ret & 0x007f) { die("Command terminated by a signal"); }
445 if ($ret & 0xff00) {die("Command exit with non-zero exit code"); }
446 if ($ret) { die("Command failure $ret"); }
451 if (exists $variables->{WVDESC}) {
452 print "Testing \"$variables->{WVDESC}\" in $last_fn:\n";
453 } elsif ($last_fn =~ /\.wv$/) {
454 print "Testing \"all\" in $last_fn:\n";
457 ## Connect to the target and check whether is not occupied
459 # We have to do this before file generation phase, because file
460 # generation is intermixed with file deployment phase and we want to
461 # check whether the target is not used by somebody else before
462 # deploying files. Otherwise, we may rewrite other user's files on a
465 my $exp; # Expect object to communicate with the target over serial line
467 my ($target_reset, $target_power_on, $target_power_off);
469 if (defined $iprelay) {
471 $iprelay =~ /([.0-9]+)(:([0-9]+))?/;
474 my $paddr = sockaddr_in($port, inet_aton($addr));
475 my $proto = getprotobyname('tcp');
476 socket($IPRELAY, PF_INET, SOCK_STREAM, $proto) || die "socket: $!";
477 print "novaboot: Connecting to IP relay... ";
478 connect($IPRELAY, $paddr) || die "connect: $!";
480 $exp = Expect->init(\*$IPRELAY);
484 print $exp "\xFF\xF6"; # AYT
485 my $connected = $exp->expect(20, # Timeout in seconds
486 '<iprelayd: connected>',
487 '-re', '<WEB51 HW[^>]*>');
492 my ($relay, $onoff) = @_;
493 die unless ($relay == 1 || $relay == 2);
495 my $cmd = ($relay == 1 ? 0x5 : 0x6) | ($onoff ? 0x20 : 0x10);
496 return "\xFF\xFA\x2C\x32".chr($cmd)."\xFF\xF0";
500 my ($relay, $onoff) = @_;
501 die unless ($relay == 1 || $relay == 2);
502 my $cmd = ($relay == 1 ? 0xdf : 0xbf) | ($onoff ? 0x00 : 0xff);
503 return "\xFF\xFA\x2C\x97".chr($cmd)."\xFF\xF0";
507 my ($relay, $onoff, $can_giveup) = @_;
508 my $confirmation = '';
510 print $exp relaycmd($relay, $onoff);
511 my $confirmed = $exp->expect(20, # Timeout in seconds
512 relayconf($relay, $onoff));
515 print("Relay confirmation timeout - ignoring\n");
517 die "Relay confirmation timeout";
523 $target_reset = sub {
524 relay(2, 1, 1); # Reset the machine
529 $target_power_off = sub {
530 relay(1, 1); # Press power button
531 usleep(6000000); # Long press to switch off
535 $target_power_on = sub {
536 relay(1, 1); # Press power button
537 usleep(100000); # Short press
543 system_verbose("stty -F $serial $stty");
544 open($CONN, "+<", $serial) || die "open $serial: $!";
545 $exp = Expect->init(\*$CONN);
546 } elsif ($remote_cmd) {
547 print "novaboot: Running: $remote_cmd\n";
548 $exp = Expect->spawn($remote_cmd);
549 } elsif (defined $amt){
550 sub genXML { #HOST, username, password, schema, className, pstate
551 my ($host, $username, $password, $schema, $className, $pstate) = @_;
552 #AMT numbers for PowerStateChange: 2 on, 4 standby, 7 hibernate, 8 off,
553 #10 reset, 11 MNI interupt(on windows->bluescreen;-))
554 my %pstates = ("on", 2,
561 <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">
562 <s:Header><a:To>http://$host:16992/wsman</a:To>
563 <w:ResourceURI s:mustUnderstand="true">$schema</w:ResourceURI>
564 <a:ReplyTo><a:Address s:mustUnderstand="true">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address></a:ReplyTo>
565 <a:Action s:mustUnderstand="true">$schema$className</a:Action>
566 <w:MaxEnvelopeSize s:mustUnderstand="true">153600</w:MaxEnvelopeSize>
567 <a:MessageID>uuid:709072C9-609C-4B43-B301-075004043C7C</a:MessageID>
568 <w:Locale xml:lang="en-US" s:mustUnderstand="false" />
569 <w:OperationTimeout>PT60.000S</w:OperationTimeout>
570 <w:SelectorSet><w:Selector Name="Name">Intel(r) AMT Power Management Service</w:Selector></w:SelectorSet>
572 <p:RequestPowerStateChange_INPUT xmlns:p="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_PowerManagementService">
573 <p:PowerState>$pstates{$pstate}</p:PowerState>
574 <p:ManagedElement><a:Address>http://$host:16992/wsman</a:Address>
575 <a:ReferenceParameters><w:ResourceURI>http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ComputerSystem</w:ResourceURI>
576 <w:SelectorSet><w:Selector Name="Name">ManagedSystem</w:Selector></w:SelectorSet>
577 </a:ReferenceParameters></p:ManagedElement>
578 </p:RequestPowerStateChange_INPUT>
579 </s:Body></s:Envelope>
584 sub sendPOST{ #ip, username, password, content
585 my ($host, $username, $password, $content)=@_;+
586 my $req = HTTP::Request->new(POST => "http://$host:16992/wsman");
587 my $ua = LWP::UserAgent->new();
588 my $res = $ua->request($req);
589 die ($res->status_line) unless $res->code==401;
590 my $header = $res->header("WWW-Authenticate");
591 my $digRealm = "Digest realm=\"";
592 my $posRealm = index($header,$digRealm)+length($digRealm);
593 my $realm = substr($header,$posRealm,index($header,"\"",$posRealm)-$posRealm);
595 $ua = LWP::UserAgent->new();
596 $ua->agent("novaboot");
597 $ua->credentials("$host:16992",$realm, $username => $password);
599 $req = HTTP::Request->new(POST => "http://$host:16992/wsman");
600 $req->content_type('application/x-www-form-urlencoded');
601 $req->content($content);
602 $res = $ua->request($req);
603 die ($res->status_line) unless $res->is_success;
604 $res->content() =~ /<g:ReturnValue>(\d+)<\/g:ReturnValue>/;
608 sub powerChange {#IP, username, password, pstate
609 my ($host, $username, $password, $pstate)=@_;
610 my $schema="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_PowerManagementService";
611 my $className="/RequestPowerStateChange";
612 my $content = genXML($host, $username, $password ,$schema, $className, $pstate);
613 return sendPOST($host, $username, $password, $content);
616 my (@amt_info, $len, $amt_password, $result, $host, $port, $user);
617 ($user,$amt_password,$host,$port) = ($amt =~ /(?:(.*?)(?::(.*))?@)?([^:]*)(?::([0-9]*))?/);;
619 die "AMT host not specified" unless $host;
622 $amt_password ||= $ENV{'AMT_PASSWORD'} || die "AMT password not specified";
623 $target_power_off = sub {
624 $result = powerChange($host,$user,$amt_password, "off");
625 die "Cannot turn off the computer, somebody is connected or computer is already off" unless $result==0;
628 $target_power_on = sub {
629 $result = powerChange($host,$user,$amt_password, "on");
632 $target_reset = sub {
633 $result = powerChange($host,$user,$amt_password, "off");
634 print "Warning: host was off or somebody is connected to it." unless $result==0;
635 $result = powerChange($host,$user,$amt_password, "on");
636 select(undef, undef, undef, 2);
637 #if not using pause, connection to AMT machine is initiated before reset and fails after restart
638 $exp = Expect->spawn("amtterm -u $user -p $amt_password $host $port");
639 #expect must be here because AMT doesnt allow to reset/on/off computer when somebody is connected
643 if ($remote_expect) {
644 $exp->expect(10, $remote_expect) || die "Expect for '$remote_expect' timed out";
647 if (defined $reset_cmd) {
648 $target_reset = sub {
649 system_verbose($reset_cmd);
653 if (defined $on_opt && defined $target_power_on) {
657 if (defined $off_opt && defined $target_power_off) {
658 print "novaboot: Switching the target off...\n";
659 &$target_power_off();
663 $builddir ||= dirname(File::Spec->rel2abs( ${$scripts[0]}{filename})) if scalar @scripts;
664 if (defined $builddir) {
665 chdir($builddir) or die "Can't change directory to $builddir: $!";
666 print "novaboot: Entering directory `$builddir'\n";
669 ## File generation phase
670 my (%files_iso, $menu_iso, $filename);
671 my $config_name = '';
673 foreach my $script (@scripts) {
674 $filename = $$script{filename};
675 $modules = $$script{modules};
676 $generated = $$script{generated};
677 $variables = $$script{variables};
679 ($config_name = $filename) =~ s#.*/##;
680 $config_name = $config_name_opt if (defined $config_name_opt);
682 if (exists $variables->{BUILDDIR}) {
683 $builddir = File::Spec->rel2abs($variables->{BUILDDIR});
684 chdir($builddir) or die "Can't change directory to $builddir: $!";
685 print "novaboot: Entering directory `$builddir'\n";
689 ($prefix = $grub_prefix) =~ s/\$NAME/$config_name/ if defined $grub_prefix;
690 $prefix ||= $builddir;
691 # TODO: use $grub_prefix as first parameter if some switch is given
692 generate_configs('', $generated, $filename);
694 ### Generate bootloader configuration files
695 my @bootloader_configs;
696 push @bootloader_configs, generate_grub_config($grub_config, $config_name, $prefix, $modules, $grub_preamble) if (defined $grub_config);
697 push @bootloader_configs, generate_grub2_config($grub2_config, $config_name, $prefix, $modules, $grub_preamble, $grub2_prolog) if (defined $grub2_config);
698 push @bootloader_configs, generate_pulsar_config('config-'.($pulsar||'novaboot'), $modules) if (defined $pulsar);
700 ### Run scons or make
702 my @files = map({ ($file) = m/([^ ]*)/; $file; } @$modules);
703 # Filter-out generated files
704 my @to_build = grep({ my $file = $_; !scalar(grep($file eq $$_{filename}, @$generated)) } @files);
706 system_verbose($scons || $CFG::scons." ".join(" ", @to_build)) if (defined $scons);
707 system_verbose($make || $CFG::make ." ".join(" ", @to_build)) if (defined $make);
710 ### Copy files (using rsync)
711 if (defined $server && !defined($gen_only)) {
712 (my $real_server = $server) =~ s/\$NAME/$config_name/;
714 my ($hostname, $path) = split(":", $real_server, 2);
715 if (! defined $path) {
719 my $files = join(" ", map({ ($file) = m/([^ ]*)/; $file; } ( @$modules, @bootloader_configs)));
720 map({ my $file = (split)[0]; die "$file: $!" if ! -f $file; } @$modules);
721 my $istty = -t STDOUT && ($ENV{'TERM'} || 'dumb') ne 'dumb';
722 my $progress = $istty ? "--progress" : "";
723 system_verbose("rsync $progress -RLp $rsync_flags $files $real_server");
724 if ($server =~ m|/\$NAME$| && $concat) {
725 my $cmd = join("; ", map { "( cd $path/.. && cat */$_ > $_ )" } @bootloader_configs);
726 system_verbose($hostname ? "ssh $hostname '$cmd'" : $cmd);
730 ### Prepare ISO image generation
731 if (defined $iso_image) {
732 generate_configs("(cd)", $generated, $filename);
734 generate_grub_config(\$menu, $config_name, "(cd)", $modules);
735 $menu_iso .= "$menu\n";
736 map { ($file,undef) = split; $files_iso{$file} = 1; } @$modules;
740 ## Generate ISO image
741 if (defined $iso_image) {
742 open(my $fh, ">menu-iso.lst");
743 print $fh "timeout 5\n\n$menu_iso";
745 my $files = "boot/grub/menu.lst=menu-iso.lst " . join(" ", map("$_=$_", keys(%files_iso)));
746 $iso_image ||= "$config_name.iso";
747 system_verbose("$CFG::genisoimage -R -b stage2_eltorito -no-emul-boot -boot-load-size 4 -boot-info-table -hide-rr-moved -J -joliet-long -o $iso_image -graft-points bin/boot/grub/ $files");
748 print("ISO image created: $builddir/$iso_image\n");
751 exit(0) if defined $gen_only;
753 ## Boot the system using various methods and send serial output to stdout
755 if (scalar(@scripts) > 1 && ( defined $dhcp_tftp || defined $serial || defined $iprelay)) {
756 die "You cannot do this with multiple scripts simultaneously";
759 if ($variables->{WVTEST_TIMEOUT}) {
760 print "wvtest: timeout ", $variables->{WVTEST_TIMEOUT}, "\n";
765 $str =~ s/^\s+|\s+$//g;
773 $qemu ||= $variables->{QEMU} || $CFG::qemu;
774 my @qemu_flags = split(" ", $qemu);
775 $qemu = shift(@qemu_flags);
777 @qemu_flags = split(/ +/, trim($variables->{QEMU_FLAGS})) if exists $variables->{QEMU_FLAGS};
778 @qemu_flags = split(/ +/, trim($qemu_flags_cmd)) if $qemu_flags_cmd;
779 push(@qemu_flags, split(/ +/, trim($qemu_append || '')));
781 if (defined $iso_image) {
782 # Boot NOVA with grub (and test the iso image)
783 push(@qemu_flags, ('-cdrom', "$config_name.iso"));
785 # Boot NOVA without GRUB
787 # Non-patched qemu doesn't like commas, but NUL can live with pluses instead of commans
788 foreach (@$modules) {s/,/+/g;}
789 generate_configs("", $generated, $filename);
791 if (scalar @$modules) {
792 my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);
793 $kcmd = '' if !defined $kcmd;
795 @$modules = map { if (/\.dtb$/) { $dtb=$_; (); } else { $_ } } @$modules;
796 my $initrd = join ",", @$modules;
798 push(@qemu_flags, ('-kernel', $kbin, '-append', $kcmd));
799 push(@qemu_flags, ('-initrd', $initrd)) if $initrd;
800 push(@qemu_flags, ('-dtb', $dtb)) if $dtb;
803 push(@qemu_flags, qw(-serial stdio)); # Redirect serial output (for collecting test restuls)
804 unshift(@qemu_flags, ('-name', $config_name));
805 print "novaboot: Running: ".shell_cmd_string($qemu, @qemu_flags)."\n";
806 $exp = Expect->spawn(($qemu, @qemu_flags)) || die("exec() failed: $!");
809 ### Local DHCPD and TFTPD
811 my ($dhcpd_pid, $tftpd_pid);
813 if (defined $dhcp_tftp)
815 generate_configs("(nd)", $generated, $filename);
816 system_verbose('mkdir -p tftpboot');
817 generate_grub_config("tftpboot/os-menu.lst", $config_name, "(nd)", \@$modules, "timeout 0");
818 open(my $fh, '>', 'dhcpd.conf');
819 my $mac = `cat /sys/class/net/eth0/address`;
821 print $fh "subnet 10.23.23.0 netmask 255.255.255.0 {
822 range 10.23.23.10 10.23.23.100;
823 filename \"bin/boot/grub/pxegrub.pxe\";
824 next-server 10.23.23.1;
827 hardware ethernet $mac;
828 fixed-address 10.23.23.1;
831 system_verbose("sudo ip a add 10.23.23.1/24 dev eth0;
832 sudo ip l set dev eth0 up;
833 sudo touch dhcpd.leases");
835 # We run servers by forking ourselves, because the servers end up
836 # in our process group and get killed by signals sent to the
837 # process group (e.g. Ctrl-C on terminal).
839 exec_verbose("sudo dhcpd -d -cf dhcpd.conf -lf dhcpd.leases -pf dhcpd.pid") if ($dhcpd_pid == 0);
841 exec_verbose("sudo in.tftpd --foreground --secure -v -v -v --pidfile tftpd.pid $builddir") if ($tftpd_pid == 0);
843 # Kill server when we die
844 $SIG{__DIE__} = sub { system_verbose('sudo pkill --pidfile=dhcpd.pid');
845 system_verbose('sudo pkill --pidfile=tftpd.pid'); };
848 ### Serial line or IP relay
850 if (defined $target_reset) {
851 print "novaboot: Reseting the test box... ";
856 if (defined $uboot) {
857 print "novaboot: Waiting for uBoot prompt...\n";
859 #$exp->exp_internal(1);
861 [qr/Hit any key to stop autoboot:/, sub { $exp->send("\n"); exp_continue; }],
862 '=> ') || die "No uBoot prompt deteceted";
863 $exp->send("$uboot_init\n") if $uboot_init;
864 $exp->expect(10, '=> ') || die "uBoot prompt timeout";
866 my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);
868 @$modules = map { if (/\.dtb$/) { $dtb=$_; (); } else { $_ } } @$modules;
869 my $initrd = shift @$modules;
871 my $kern_addr = '800000';
872 my $initrd_addr = '-';
875 $exp->send("tftp $kern_addr $kbin\n");
877 [qr/#/, sub { exp_continue; }],
878 '=> ') || die "Kernel load failed";
880 $dtb_addr = '7f0000';
881 $exp->send("tftp $dtb_addr $dtb\n");
883 [qr/#/, sub { exp_continue; }],
884 '=> ') || die "Device tree load failed";
886 if (defined $initrd) {
887 $initrd_addr = 'b00000';
888 $exp->send("tftp $initrd_addr $initrd\n");
890 [qr/#/, sub { exp_continue; }],
891 '=> ') || die "Initrd load failed";
893 $exp->send("set bootargs '$kcmd'\n");
894 $exp->expect(5, '=> ') || die "uBoot prompt timeout";
895 $exp->send("bootm $kern_addr $initrd_addr $dtb_addr\n");
896 $exp->expect(5, "\n") || die "uBoot command timeout";
900 # Serial line of the target is available
901 my $interrupt = 'Ctrl-C';
902 if ($interactive && !@exiton) {
903 $interrupt = '"~~."';
905 print "novaboot: Serial line interaction (press $interrupt to interrupt)...\n";
908 $exp->expect(undef, @expect_raw, @exiton);
911 if (-t STDIN) { # Set up bi-directional communication if we run on terminal
912 my $infile = new IO::File;
913 $infile->IO::File::fdopen(*STDIN,'r');
914 my $in_object = Expect->exp_init($infile);
915 $in_object->set_group($exp);
918 $in_object->set_seq('~~\.', sub { print "novaboot: Escape sequence detected\r\n"; undef; });
919 $in_object->manual_stty(0); # Use raw terminal mode
921 $in_object->manual_stty(1); # Do not modify terminal settings
923 push(@inputs, $in_object);
926 #print Dumper(\@expect_raw);
927 $exp->expect(undef, @expect_raw) if @expect_raw;
928 Expect::interconnect(@inputs) unless defined($exp->exitstatus);
932 ## Kill dhcpc or tftpd
933 if (defined $dhcp_tftp) {
934 die("novaboot: This should kill servers on background\n");
941 novaboot - A tool for booting various operating systems on various hardware or in qemu
947 B<novaboot> [option]... [--] script...
949 B<./script> [option]...
953 This program makes booting of an operating system (e.g. NOVA or Linux)
954 as simple as running a local program. It facilitates booting on local
955 or remote hosts or in emulators such as qemu. Novaboot operation is
956 controlled by command line options and by a so called novaboot script,
957 which can be thought as a generalization of bootloader configuration
958 files (see L</"NOVABOOT SCRIPT SYNTAX">). Based on this input,
959 novaboot setups everything for the target host to boot the desired
960 configuration, i.e. it generates the bootloader configuration file in
961 the proper format, deploy the binaries and other needed files to
962 required locations, perhaps on a remote boot server and reset the
963 target host. Finally, target host's serial output is redirected to
964 standard output if that is possible.
966 Typical way of using novaboot is to make the novaboot script
967 executable and set its first line to I<#!/usr/bin/env novaboot>. Then,
968 booting a particular OS configuration becomes the same as executing a
969 local program - the novaboot script.
971 For example, with C<novaboot> you can:
977 Run an OS in Qemu. This is the default action when no other action is
978 specified by command line switches. Thus running C<novaboot ./script>
979 (or C<./script> as described above) will run Qemu and make it boot the
980 configuration specified in the F<script>.
984 Create a bootloader configuration file (currently supported
985 bootloaders are GRUB, GRUB2, Pulsar and U-Boot) and copy it with all
986 other files needed for booting to a remote boot server.
988 ./script --server=192.168.1.1:/tftp --iprelay=192.168.1.2
990 This command copies files to the TFTP server and uses
991 TCP/IP-controlled relay to reset the target host and receive its
996 Run DHCP and TFTP server on developer's machine to PXE-boot the target
1001 When a PXE-bootable machine is connected via Ethernet to developer's
1002 machine, it will boot the configuration described in I<script>.
1006 Create bootable ISO images. E.g.
1008 novaboot --iso -- script1 script2
1010 The created ISO image will have GRUB bootloader installed on it and
1011 the boot menu will allow selecting between I<script1> and I<script2>
1016 Note that the options needed for a specific target can be stored in a
1017 L</"CONFIGURATION FILE">. Then it is sufficient to use only the B<-t>
1018 option to specify the name of the target.
1020 =head1 PHASES AND OPTIONS
1022 Novaboot performs its work in several phases. Each phase can be
1023 influenced by several options, certain phases can be skipped. The list
1024 of phases (in the execution order) and the corresponding options
1027 =head2 Configuration reading phase
1029 After starting, novaboot reads configuration files. By default, it
1030 searches for files named F<.novaboot> starting from the directory of
1031 the novaboot script (or working directory, see bellow) and continuing
1032 upwards up to the root directory. The configuration files are read in
1033 order from the root directory downwards with latter files overriding
1034 settings from the former ones.
1036 In certain cases, the location of the novaboot script cannot be
1037 determined in this early phase. This happens either when the script is
1038 read from the standard input or when novaboot is invoked explicitly
1039 and options precede the script name, as in the example L</"4."> above.
1040 In this case the current working directory is used as a starting point
1041 for configuration file search.
1045 =item -c, --config=I<filename>
1047 Use the specified configuration file instead of the default one(s).
1051 =head2 Command line processing phase
1057 Dump the current configuration to stdout end exits. Useful as an
1058 initial template for a configuration file.
1062 Print short (B<-h>) or long (B<--help>) help.
1064 =item -t, --target=I<target>
1066 This option serves as a user configurable shortcut for other novaboot
1067 options. The effect of this option is the same as the options stored
1068 in the C<%targets> configuration variable under key I<target>. See
1069 also L</"CONFIGURATION FILE">.
1073 =head2 Script preprocessing phase
1075 This phases allows to modify the parsed novaboot script before it is
1076 used in the later phases.
1080 =item -a, --append=I<parameters>
1082 Append a string to the first C<load> line in the novaboot script. This
1083 can be used to append parameters to the kernel's or root task's
1084 command line. Can appear multiple times.
1088 Use F<bender> chainloader. Bender scans the PCI bus for PCI serial
1089 ports and stores the information about them in the BIOS data area for
1092 =item --chainloader=I<chainloader>
1094 Chainloader that is loaded before the kernel and other files specified
1095 in the novaboot script. E.g. 'bin/boot/bender promisc'.
1099 Print the modules to boot and their parameters after this phase
1100 finishes. Then exit. This is useful for seeing the effect of other
1101 options in this section.
1103 =item -k, --kernel=F<file>
1105 Replace the first word on the first C<load> line in the novaboot
1106 script with F<file>.
1108 =item --scriptmod=I<perl expression>
1110 When novaboot script is read, I<perl expression> is executed for every
1111 line (in $_ variable). For example, C<novaboot
1112 --scriptmod=s/sigma0/omega6/g> replaces every occurrence of I<sigma0>
1113 in the script with I<omega6>.
1115 When this option is present, it overrides I<$script_modifier> variable
1116 from the configuration file, which has the same effect. If this option
1117 is given multiple times all expressions are evaluated in the command
1122 =head2 File generation phase
1124 In this phase, files needed for booting are generated in a so called
1125 I<build directory> (see L</--build-dir>). In most cases configuration
1126 for a bootloader is generated automatically by novaboot. It is also
1127 possible to generate other files using I<heredoc> or I<"<"> syntax in
1128 novaboot scripts. Finally, binaries can be generated in this phases by
1129 running C<scons> or C<make>.
1133 =item --build-dir=I<directory>
1135 Overrides the default build directory location.
1137 The default build directory location is determined as follows: If the
1138 configuration file defines the C<$builddir> variable, its value is
1139 used. Otherwise, it is the directory that contains the first processed
1142 See also L</BUILDDIR> variable.
1144 =item -g, --grub[=I<filename>]
1146 Generates grub bootloader menu file. If the I<filename> is not
1147 specified, F<menu.lst> is used. The I<filename> is relative to the
1148 build directory (see B<--build-dir>).
1150 =item --grub-preamble=I<prefix>
1152 Specifies the I<preable> that is at the beginning of the generated
1153 GRUB or GRUB2 config files. This is useful for specifying GRUB's
1156 =item --grub-prefix=I<prefix>
1158 Specifies I<prefix> that is put in front of every file name in GRUB's
1159 F<menu.lst>. The default value is the absolute path to the build directory.
1161 If the I<prefix> contains string $NAME, it will be replaced with the
1162 name of the novaboot script (see also B<--name>).
1164 =item --grub2[=I<filename>]
1166 Generate GRUB2 menuentry in I<filename>. If I<filename> is not
1167 specified F<grub.cfg> is used. The content of the menuentry can be
1168 customized with B<--grub-preable>, B<--grub2-prolog> or
1169 B<--grub_prefix> options.
1171 In order to use the the generated menuentry on your development
1172 machine that uses GRUB2, append the following snippet to
1173 F</etc/grub.d/40_custom> file and regenerate your grub configuration,
1174 i.e. run update-grub on Debian/Ubuntu.
1176 if [ -f /path/to/nul/build/grub.cfg ]; then
1177 source /path/to/nul/build/grub.cfg
1180 =item --grub2-prolog=I<prolog>
1182 Specifies text I<preable> that is put at the beginning of the entry
1185 =item -m, --make[=make command]
1187 Runs C<make> to build files that are not generated by novaboot itself.
1189 =item --name=I<string>
1191 Use the name I<string> instead of the name of the novaboot script.
1192 This name is used for things like a title of grub menu or for the
1193 server directory where the boot files are copied to.
1197 Do not generate files on the fly (i.e. "<" syntax) except for the
1198 files generated via "<<WORD" syntax.
1200 =item -p, --pulsar[=mac]
1202 Generates pulsar bootloader configuration file named F<config-I<mac>>
1203 The I<mac> string is typically a MAC address and defaults to
1206 =item --scons[=scons command]
1208 Runs C<scons> to build files that are not generated by novaboot
1213 Strip I<rom://> prefix from command lines and generated config files.
1214 The I<rom://> prefix is used by NUL. For NRE, it has to be stripped.
1218 Exit novaboot after file generation phase.
1222 =head2 Target connection check
1224 If supported by the target, the connection to it is made and it is
1225 checked whether the target is not occupied by another novaboot
1230 =item --amt=I<"[user[:password]@]host[:port]>
1232 Use Intel AMT to connect to target. Using SOL redirection and WS management
1233 to powercycle it. The IP address or FQDN of the PC is given by I<host>
1234 parameter. If I<password> is not specified, environment variable
1235 AMT_PASSWORD is used. I<port> is defining port number for SOL,
1236 if not specified, default is 16992. Default I<user> is admin.
1238 =item --iprelay=I<addr[:port]>
1240 Use TCP/IP relay and serial port to access the target's serial port
1241 and powercycle it. The IP address of the relay is given by I<addr>
1242 parameter. If I<port> is not specified, it default to 23.
1244 Note: This option is supposed to work with HWG-ER02a IP relays.
1246 =item -s, --serial[=device]
1248 Target's serial line is connected to host's serial line (device). The
1249 default value for device is F</dev/ttyUSB0>.
1251 The value of this option is exported in NB_NOVABOOT environment
1252 variable to all subprocesses run by C<novaboot>.
1254 =item --stty=I<settings>
1256 Specifies settings passed to C<stty> invoked on the serial line
1257 specified with B<--serial> option. If this option is not given,
1258 C<stty> is called with C<raw -crtscts -onlcr 115200> settings.
1260 =item --remote-cmd=I<cmd>
1262 Command that mediates connection to the target's serial line. For
1263 example C<ssh server 'cu -l /dev/ttyS0'>.
1265 =item --remote-expect=I<string>
1267 Wait for reception of I<string> after establishing the the remote
1268 connection before continuing.
1273 =head2 File deployment phase
1275 In some setups, it is necessary to copy the files needed for booting
1276 to a particular location, e.g. to a TFTP boot server or to the
1281 =item -d, --dhcp-tftp
1283 Turns your workstation into a DHCP and TFTP server so that the OS can
1284 be booted via PXE BIOS (or similar mechanism) on the test machine
1285 directly connected by a plain Ethernet cable to your workstation.
1287 The DHCP and TFTP servers require root privileges and C<novaboot>
1288 uses C<sudo> command to obtain those. You can put the following to
1289 I</etc/sudoers> to allow running the necessary commands without
1290 asking for password.
1292 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 --foreground --secure -v -v -v --pidfile tftpd.pid *, /usr/bin/touch dhcpd.leases, /usr/bin/pkill --pidfile=dhcpd.pid, /usr/bin/pkill --pidfile=tftpd.pid
1293 your_login ALL=NOPASSWD: NOVABOOT
1295 =item --iso[=filename]
1297 Generates the ISO image that boots NOVA system via GRUB. If no filename
1298 is given, the image is stored under I<NAME>.iso, where I<NAME> is the name
1299 of the novaboot script (see also B<--name>).
1301 =item --server[=[[user@]server:]path]
1303 Copy all files needed for booting to another location (implies B<-g>
1304 unless B<--grub2> is given). The files will be copied (by B<rsync>
1305 tool) to the directory I<path>. If the I<path> contains string $NAME,
1306 it will be replaced with the name of the novaboot script (see also
1311 If B<--server> is used and its value ends with $NAME, then after
1312 copying the files, a new bootloader configuration file (e.g. menu.lst)
1313 is created at I<path-wo-name>, i.e. the path specified by B<--server>
1314 with $NAME part removed. The content of the file is created by
1315 concatenating all files of the same name from all subdirectories of
1316 I<path-wo-name> found on the "server".
1318 =item --rsync-flags=I<flags>
1320 Specifies which I<flags> are appended to F<rsync> command line when
1321 copying files as a result of I<--server> option.
1325 =head2 Target power-on and reset phase
1331 Switch on/off the target machine. Currently works only with
1334 =item -Q, --qemu[=I<qemu-binary>]
1336 Boot the configuration in qemu. Optionally, the name of qemu binary
1337 can be specified as a parameter.
1339 =item --qemu-append=I<flags>
1341 Append I<flags> to the default qemu flags (QEMU_FLAGS variable or
1342 C<-cpu coreduo -smp 2>).
1344 =item -q, --qemu-flags=I<flags>
1346 Replace the default qemu flags (QEMU_FLAGS variable or C<-cpu coreduo
1347 -smp 2>) with I<flags> specified here.
1349 =item --reset-cmd=I<cmd>
1351 Command that resets the target.
1355 =head2 Interaction with the bootloader on the target
1361 Interact with uBoot bootloader to boot the thing described in the
1362 novaboot script. Implementation of this option is currently tied to a
1363 particular board that we use. It may be subject to changes in the
1368 Command(s) to send the U-Boot bootloader before loading the images and
1373 =head2 Target interaction phase
1375 In this phase, target's serial output is redirected to stdout and if
1376 stdin is a TTY, it is redirected to the target's serial input allowing
1377 interactive work with the target.
1381 =item --exiton=I<string>
1383 When I<string> is sent by the target, novaboot exits. This option can
1384 be specified multiple times.
1386 If I<string> is C<-re>, then the next B<--exiton>'s I<string> is
1387 treated as regular expression. For example:
1389 --exiton -re --exiton 'error:.*failed'
1391 =item -i, --interactive
1393 Setup things for interactive use of target. Your terminal will be
1394 switched to raw mode. In raw mode, your system does not process input
1395 in any way (no echoing of entered characters, no interpretation
1396 special characters). This, among others, means that Ctrl-C is passed
1397 to the target and does no longer interrupt novaboot. Use "~~."
1398 sequence to exit novaboot.
1400 =item --expect=I<string>
1402 When I<string> is received from the target, send the string specified
1403 with the subsequent B<--send*> option to the target.
1405 =item --expect-re=I<regex>
1407 When target's output matches regular expression I<regex>, send the
1408 string specified with the subsequent B<--send*> option to the target.
1410 =item --expect-raw=I<perl-code>
1412 Provides direct control over Perl's Expect module.
1414 =item --send=I<string>
1416 Send I<string> to the target after the previously specified
1417 B<--expect*> was matched in the target's output. The I<string> may
1418 contain escape sequences such as "\n".
1420 Note that I<string> is actually interpreted by Perl, so it can contain
1421 much more that escape sequences. This behavior may change in the
1424 Example: C<--expect='login: ' --send='root\n'>
1426 =item --sendcont=I<string>
1428 Similar to B<--send> but continue expecting more input.
1430 Example: C<--expect='Continue?' --sendcont='yes\n'>
1434 =head1 NOVABOOT SCRIPT SYNTAX
1436 The syntax tries to mimic POSIX shell syntax. The syntax is defined
1437 with the following rules.
1439 Lines starting with "#" and empty lines are ignored.
1441 Lines that end with "\" are concatenated with the following line after
1442 removal of the final "\" and leading whitespace of the following line.
1444 Lines of the form I<VARIABLE=...> (i.e. matching '^[A-Z_]+=' regular
1445 expression) assign values to internal variables. See L</VARIABLES>
1448 Lines starting with C<load> keyword represent modules to boot. The
1449 word after C<load> is a file name (relative to the build directory
1450 (see B<--build-dir>) of the module to load and the remaining words are
1451 passed to it as the command line parameters.
1453 When the C<load> line ends with "<<WORD" then the subsequent lines
1454 until the line containing solely WORD are copied literally to the file
1455 named on that line. This is similar to shell's heredoc feature.
1457 When the C<load> line ends with "< CMD" then command CMD is executed
1458 with F</bin/sh> and its standard output is stored in the file named on
1459 that line. The SRCDIR variable in CMD's environment is set to the
1460 absolute path of the directory containing the interpreted novaboot
1465 #!/usr/bin/env novaboot
1466 load bzImage console=ttyS0,115200
1467 load rootfs.cpio < gen_cpio buildroot/images/rootfs.cpio "myapp->/etc/init.d/S99myapp"
1469 Example (NOVA User Land - NUL):
1471 #!/usr/bin/env novaboot
1472 WVDESC=Example program
1473 load bin/apps/sigma0.nul S0_DEFAULT script_start:1,1 \
1474 verbose hostkeyb:0,0x60,1,12,2
1475 load bin/apps/hello.nul
1476 load hello.nulconfig <<EOF
1477 sigma0::mem:16 name::/s0/log name::/s0/timer name::/s0/fs/rom ||
1478 rom://bin/apps/hello.nul
1481 This example will load three modules: F<sigma0.nul>, F<hello.nul> and
1482 F<hello.nulconfig>. sigma0 receives some command line parameters and
1483 F<hello.nulconfig> file is generated on the fly from the lines between
1484 C<<<EOF> and C<EOF>.
1488 The following variables are interpreted in the novaboot script:
1494 Novaboot chdir()s to this directory before file generation phase. The
1495 directory name specified here is relative to the build directory
1496 specified by other means (see L</--build-dir>).
1500 Assigning this variable has the same effect as specifying L</--exiton>
1503 =item HYPERVISOR_PARAMS
1505 Parameters passed to hypervisor. The default value is "serial", unless
1506 overridden in configuration file.
1510 The kernel to use instead of the hypervisor specified in the
1511 configuration file with the C<$hypervisor> variable. The value should
1512 contain the name of the kernel image as well as its command line
1513 parameters. If this variable is defined and non-empty, the variable
1514 HYPERVISOR_PARAMS is not used.
1518 Use a specific qemu binary (can be overridden with B<-Q>) and flags
1519 when booting this script under qemu. If QEMU_FLAGS variable is also
1520 specified flags specified in QEMU variable are replaced by those in
1525 Use specific qemu flags (can be overridden with B<-q>).
1529 Description of the wvtest-compliant program.
1531 =item WVTEST_TIMEOUT
1533 The timeout in seconds for WvTest harness. If no complete line appears
1534 in the test output within the time specified here, the test fails. It
1535 is necessary to specify this for long running tests that produce no
1536 intermediate output.
1540 =head1 CONFIGURATION FILE
1542 Novaboot can read its configuration from one or more files. By
1543 default, novaboot looks for files named F<.novaboot> as described in
1544 L</Configuration reading phase>. Alternatively, its location can be
1545 specified with the B<-c> switch or with the NOVABOOT_CONFIG
1546 environment variable. The configuration file has perl syntax and
1547 should set values of certain Perl variables. The current configuration
1548 can be dumped with the B<--dump-config> switch. Some configuration
1549 variables can be overridden by environment variables (see below) or by
1550 command line switches.
1552 Supported configuration variables include:
1558 Build directory location relative to the location of the configuration
1561 =item $default_target
1563 Default target (see below) to use when no target is explicitly
1564 specified on command line with the B<--target> option.
1568 Hash of shortcuts to be used with the B<--target> option. If the hash
1569 contains, for instance, the following pair of values
1571 'mybox' => '--server=boot:/tftproot --serial=/dev/ttyUSB0 --grub',
1573 then the following two commands are equivalent:
1575 ./script --server=boot:/tftproot --serial=/dev/ttyUSB0 --grub
1580 =head1 ENVIRONMENT VARIABLES
1582 Some options can be specified not only via config file or command line
1583 but also through environment variables. Environment variables override
1584 the values from configuration file and command line parameters
1585 override the environment variables.
1589 =item NOVABOOT_CONFIG
1591 Name of the novaboot configuration file to use instead of the default
1594 =item NOVABOOT_BENDER
1596 Defining this variable has the same meaning as B<--bender> option.
1602 Michal Sojka <sojka@os.inf.tu-dresden.de>