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 GetOptionsFromArray);
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 "ryu" => '--uboot --uboot-init="mw f0000b00 \${psc_cfg}; sleep 1" --uboot-addr kernel=800000 --uboot-addr ramdisk=b00000 --uboot-addr fdt=7f0000',
51 "ryuglab" => '--target ryu --server=pc-sojkam.felk.cvut.cz:/srv/tftp --remote-cmd="ssh -tt pc-sojkam.felk.cvut.cz \"sterm -d -s 115200 /dev/ttyUSB0\""',
52 "ryulocal" => '--target ryu --dhcp-tftp --serial --reset-cmd="if which dtrrts; then dtrrts $NB_SERIAL 0 1; sleep 0.1; dtrrts $NB_SERIAL 1 1; fi"',
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]);
91 @cfgs = reverse @cfgs;
92 $dir = $ENV{'NOVABOOT_CONFIG_DIR'} || '/etc/novaboot.d';
93 if (opendir(my $dh, $dir)) {
94 my @etccfg = map { "$dir/$_" } grep { /^[-_a-zA-Z0-9]+$/ && -f "$dir/$_" } readdir($dh);
96 @etccfg = sort(@etccfg);
97 @cfgs = ( @etccfg, @cfgs );
100 my $cfg = $ENV{'NOVABOOT_CONFIG'};
101 Getopt::Long::Configure(qw/no_ignore_case pass_through/);
102 GetOptions ("config|c=s" => \$cfg);
103 read_config($_) foreach $cfg or @cfgs;
105 ## Command line handling
108 GetOptions ("target|t=s" => \$explicit_target);
110 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, $reset_cmd, $rom_prefix, $rsync_flags, @scriptmod, $scons, $serial, $server, $stty, $tftp, $tftp_port, $uboot, %uboot_addr, @uboot_init);
113 $rom_prefix = 'rom://';
114 $stty = 'raw -crtscts -onlcr 115200';
115 $reset = 1; # Reset target by default
117 my @expect_seen = ();
121 push(@expect_seen, '-re') if $n eq "expect-re";
122 push(@expect_seen, $v);
128 unless (@expect_seen) { die("No --expect before --send"); }
129 my $ret = ($n eq "sendcont") ? exp_continue : 0;
130 unshift(@expect_raw, sub { shift->send(eval("\"$v\"")); $ret; });
131 unshift(@expect_raw, @expect_seen);
138 "append|a=s" => \@append,
139 "bender|b" => \$bender,
140 "build-dir=s" => sub { my ($n, $v) = @_; $builddir = File::Spec->rel2abs($v); },
141 "concat" => \$concat,
142 "chainloader=s" => \@chainloaders,
143 "dhcp-tftp|d" => \$dhcp_tftp,
144 "dump" => \$dump_opt,
145 "dump-config" => \$dump_config,
146 "exiton=s" => \@exiton,
147 "expect=s" => \&handle_expect,
148 "expect-re=s" => \&handle_expect,
149 "expect-raw=s" => sub { my ($n, $v) = @_; unshift(@expect_raw, eval($v)); },
150 "gen-only" => \$gen_only,
151 "grub|g:s" => \$grub_config,
152 "grub-preamble=s"=> \$grub_preamble,
153 "prefix|grub-prefix=s" => \$grub_prefix,
154 "grub2:s" => \$grub2_config,
155 "grub2-prolog=s" => \$grub2_prolog,
157 "iprelay=s" => \$iprelay,
158 "iso:s" => \$iso_image,
159 "kernel|k=s" => \$kernel_opt,
160 "interactive|i" => \$interactive,
161 "name=s" => \$config_name_opt,
162 "make|m:s" => \$make,
163 "no-file-gen" => \$no_file_gen,
166 "pulsar|p:s" => \$pulsar,
167 "pulsar-root=s" => \$pulsar_root,
168 "qemu|Q:s" => \$qemu,
169 "qemu-append=s" => \$qemu_append,
170 "qemu-flags|q=s" => \$qemu_flags_cmd,
171 "remote-cmd=s" => \$remote_cmd,
172 "remote-expect=s"=> \$remote_expect,
174 "reset-cmd=s" => \$reset_cmd,
175 "rsync-flags=s" => \$rsync_flags,
176 "scons:s" => \$scons,
177 "scriptmod=s" => \@scriptmod,
178 "send=s" => \&handle_send,
179 "sendcont=s" => \&handle_send,
180 "serial|s:s" => \$serial,
181 "server:s" => \$server,
182 "strip-rom" => sub { $rom_prefix = ''; },
185 "tftp-port=i" => \$tftp_port,
186 "uboot:s" => \$uboot,
187 "uboot-addr=s" => \%uboot_addr,
188 "uboot-init=s" => \@uboot_init,
193 # First process target options
195 my $t = defined($explicit_target) ? $explicit_target : $CFG::default_target;
197 Getopt::Long::Configure(qw/no_ignore_case pass_through/);
199 exists $CFG::targets{$t} or die("Unknown target '$t' (valid targets are: ".join(", ", sort keys(%CFG::targets)).")");
201 undef $explicit_target;
202 my ($ret, $remaining_args) = GetOptionsFromString ($CFG::targets{$t}, ("target|t=s" => \$explicit_target));
203 if (!$ret) { die "Error parsing target $t option"; }
204 push(@target_expanded, @$remaining_args);
205 $t = $explicit_target;
207 Getopt::Long::Configure(qw/no_ignore_case no_pass_through/);
208 GetOptionsFromArray(\@target_expanded, %opt_spec) or die ("Error in target definition");
211 # Then process other command line options - some of them may override
212 # what was specified by the target
213 GetOptions %opt_spec or die("Error in command line arguments");
214 pod2usage(1) if $help;
215 pod2usage(-exitstatus => 0, -verbose => 2) if $man;
217 ### Dump sanitized configuration (if requested)
221 $Data::Dumper::Indent=1;
222 print "# This file is in perl syntax.\n";
223 foreach my $key(sort(keys(%CFG::))) { # See "Symbol Tables" in perlmod(1)
224 if (defined ${$CFG::{$key}}) { print Data::Dumper->Dump([${$CFG::{$key}}], ["*$key"]); }
225 if ( @{$CFG::{$key}}) { print Data::Dumper->Dump([\@{$CFG::{$key}}], ["*$key"]); }
226 if ( %{$CFG::{$key}}) { print Data::Dumper->Dump([\%{$CFG::{$key}}], ["*$key"]); }
232 ### Sanitize configuration
234 if ($interactive && !-t STDIN) {
235 die("novaboot: Interactive mode not supported when not on terminal");
238 if (defined $config_name_opt && scalar(@ARGV) > 1) { die "You cannot use --name with multiple scripts"; }
241 $iso_image //= ''; # IDE-R needs an ISO image
242 if (!defined $amt) { die "Error: --ider requires --amt"; }
246 if (defined $serial) {
247 $serial ||= "/dev/ttyUSB0";
248 $ENV{NB_SERIAL} = $serial;
250 if (defined $grub_config) { $grub_config ||= "menu.lst"; }
251 if (defined $grub2_config) { $grub2_config ||= "grub.cfg"; }
253 ## Parse the novaboot script(s)
258 my ($modules, $variables, $generated, $continuation) = ([], {}, []);
259 my $skip_reading = defined($on_opt) || defined($off_opt);
260 while (!$skip_reading && ($_ = <>)) {
261 if ($ARGV ne $last_fn) { # New script
262 die "Missing EOF in $last_fn" if $file;
263 die "Unfinished line in $last_fn" if $continuation;
265 push @scripts, { 'filename' => $ARGV,
266 'modules' => $modules = [],
267 'variables' => $variables = {},
268 'generated' => $generated = []};
272 next if /^#/ || /^\s*$/; # Skip comments and empty lines
274 $_ =~ s/^[[:space:]]*// if ($continuation);
276 if (/\\$/) { # Line continuation
277 $continuation .= substr($_, 0, length($_)-1);
281 if ($continuation) { # Last continuation line
282 $_ = $continuation . $_;
286 foreach my $mod(@scriptmod) { eval $mod; }
288 if ($file && $_ eq $EOF) { # Heredoc end
292 if ($file) { # Heredoc content
293 push @{$file}, "$_\n";
296 if (/^([A-Z_]+)=(.*)$/) { # Internal variable
297 $$variables{$1} = $2;
298 push(@exiton, $2) if ($1 eq "EXITON");
301 if (s/^load *//) { # Load line
302 die("novaboot: '$last_fn' line $.: Missing file name\n") unless /^[^ <]+/;
303 if (/^([^ ]*)(.*?)[[:space:]]*<<([^ ]*)$/) { # Heredoc start
304 push @$modules, "$1$2";
306 push @$generated, {filename => $1, content => $file};
310 if (/^([^ ]*)(.*?)[[:space:]]*< ?(.*)$/) { # Command substitution
311 push @$modules, "$1$2";
312 push @$generated, {filename => $1, command => $3};
318 if (/^run (.*)/) { # run line
319 push @$generated, {command => $1};
323 die("novaboot: Cannot parse script '$last_fn' line $.. Didn't you forget 'load' keyword?\n");
326 # print Dumper(\@scripts);
328 foreach my $script (@scripts) {
329 $modules = $$script{modules};
330 @$modules[0] =~ s/^[^ ]*/$kernel_opt/ if $kernel_opt;
331 @$modules[0] .= ' ' . join(' ', @append) if @append;
334 if (exists $variables->{KERNEL}) {
335 $kernel = $variables->{KERNEL};
337 if ($CFG::hypervisor) {
338 $kernel = $CFG::hypervisor . " ";
339 if (exists $variables->{HYPERVISOR_PARAMS}) {
340 $kernel .= $variables->{HYPERVISOR_PARAMS};
342 $kernel .= $CFG::hypervisor_params;
346 @$modules = ($kernel, @$modules) if $kernel;
347 @$modules = (@chainloaders, @$modules);
348 @$modules = ("bin/boot/bender", @$modules) if ($bender || defined $ENV{'NOVABOOT_BENDER'});
352 foreach my $script (@scripts) {
353 print join("\n", @{$$script{modules}})."\n";
360 sub generate_configs($$$) {
361 my ($base, $generated, $filename) = @_;
362 if ($base) { $base = "$base/"; };
363 foreach my $g(@$generated) {
364 if (exists $$g{content}) {
365 my $config = $$g{content};
366 my $fn = $$g{filename};
367 open(my $f, '>', $fn) || die("$fn: $!");
368 map { s|\brom://([^ ]*)|$rom_prefix$base$1|g; print $f "$_"; } @{$config};
370 print "novaboot: Created $fn\n";
371 } elsif (exists $$g{command} && ! $no_file_gen) {
372 $ENV{SRCDIR} = dirname(File::Spec->rel2abs( $filename, $invocation_dir ));
373 if (exists $$g{filename}) {
374 system_verbose("( $$g{command} ) > $$g{filename}");
376 system_verbose($$g{command});
382 sub generate_grub_config($$$$;$)
384 my ($filename, $title, $base, $modules_ref, $preamble) = @_;
385 if ($base) { $base = "$base/"; };
386 open(my $fg, '>', $filename) or die "$filename: $!";
387 print $fg "$preamble\n" if $preamble;
388 print $fg "title $title\n" if $title;
389 #print $fg "root $base\n"; # root doesn't really work for (nd)
391 foreach (@$modules_ref) {
394 my ($kbin, $kcmd) = split(' ', $_, 2);
395 $kcmd = '' if !defined $kcmd;
396 print $fg "kernel ${base}$kbin $kcmd\n";
398 s|\brom://([^ ]*)|$rom_prefix$base$1|g; # Translate rom:// files - needed for vdisk parameter of sigma0
399 print $fg "module $base$_\n";
403 print("novaboot: Created $builddir/$filename\n");
407 sub generate_syslinux_config($$$$)
409 my ($filename, $title, $base, $modules_ref) = @_;
410 if ($base && $base !~ /\/$/) { $base = "$base/"; };
411 open(my $fg, '>', $filename) or die "$filename: $!";
412 print $fg "LABEL $title\n";
413 #TODO print $fg "MENU LABEL $human_readable_title\n";
415 my ($kbin, $kcmd) = split(' ', @$modules_ref[0], 2);
417 if (system("file $kbin|grep 'Linux kernel'") == 0) {
418 my $initrd = @$modules_ref[1];
419 die('To many "load" lines for Linux kernel') if (scalar @$modules_ref > 2);
420 print $fg "LINUX $base$kbin\n";
421 print $fg "APPEND $kcmd\n";
422 print $fg "INITRD $base$initrd\n";
424 print $fg "KERNEL mboot.c32\n";
426 foreach (@$modules_ref) {
427 s|\brom://([^ ]*)|$rom_prefix$base$1|g; # Translate rom:// files - needed for vdisk parameter of sigma0
428 push @append, "$base$_";
429 print $fg "APPEND ".join(' --- ', @append)."\n";
432 #TODO print $fg "TEXT HELP\n";
433 #TODO print $fg "some help here\n";
434 #TODO print $fg "ENDTEXT\n";
436 print("novaboot: Created $builddir/$filename\n");
440 sub generate_grub2_config($$$$;$$)
442 my ($filename, $title, $base, $modules_ref, $preamble, $prolog) = @_;
443 if ($base && substr($base,-1,1) ne '/') { $base = "$base/"; };
444 open(my $fg, '>', $filename) or die "$filename: $!";
445 print $fg "$preamble\n" if $preamble;
446 $title ||= 'novaboot';
447 print $fg "menuentry $title {\n";
448 print $fg "$prolog\n" if $prolog;
450 foreach (@$modules_ref) {
453 my ($kbin, $kcmd) = split(' ', $_, 2);
454 $kcmd = '' if !defined $kcmd;
455 print $fg " multiboot ${base}$kbin $kcmd\n";
458 # GRUB2 doesn't pass filename in multiboot info so we have to duplicate it here
459 $_ = join(' ', ($args[0], @args));
460 s|\brom://|$rom_prefix|g; # We do not need to translate path for GRUB2
461 print $fg " module $base$_\n";
466 print("novaboot: Created $builddir/$filename\n");
470 sub generate_pulsar_config($$)
472 my ($filename, $modules_ref) = @_;
473 open(my $fg, '>', $filename) or die "$filename: $!";
474 print $fg "root $pulsar_root\n" if defined $pulsar_root;
477 foreach (@$modules_ref) {
480 ($kbin, $kcmd) = split(' ', $_, 2);
481 $kcmd = '' if !defined $kcmd;
484 s|\brom://|$rom_prefix|g;
485 print $fg "load $_\n";
488 # Put kernel as last - this is needed for booting Linux and has no influence on non-Linux OSes
489 print $fg "exec $kbin $kcmd\n";
491 print("novaboot: Created $builddir/$filename\n");
495 sub shell_cmd_string(@)
497 return join(' ', map((/^[-_=a-zA-Z0-9\/\.\+]+$/ ? "$_" : "'$_'"), @_));
502 print "novaboot: Running: ".shell_cmd_string(@_)."\n";
504 exit(1); # should not be reached
507 sub system_verbose($)
510 print "novaboot: Running: $cmd\n";
511 my $ret = system($cmd);
512 if ($ret & 0x007f) { die("Command terminated by a signal"); }
513 if ($ret & 0xff00) {die("Command exit with non-zero exit code"); }
514 if ($ret) { die("Command failure $ret"); }
519 if (exists $variables->{WVDESC}) {
520 print "Testing \"$variables->{WVDESC}\" in $last_fn:\n";
521 } elsif ($last_fn =~ /\.wv$/) {
522 print "Testing \"all\" in $last_fn:\n";
525 ## Connect to the target and check whether it is not occupied
527 # We have to do this before file generation phase, because file
528 # generation is intermixed with file deployment phase and we want to
529 # check whether the target is not used by somebody else before
530 # deploying files. Otherwise, we may rewrite other user's files on a
533 my $exp; # Expect object to communicate with the target over serial line
535 my ($target_reset, $target_power_on, $target_power_off);
536 my ($amt_user, $amt_password, $amt_host, $amt_port);
538 if (defined $iprelay) {
540 $iprelay =~ /([.0-9]+)(:([0-9]+))?/;
543 my $paddr = sockaddr_in($port, inet_aton($addr));
544 my $proto = getprotobyname('tcp');
545 socket($IPRELAY, PF_INET, SOCK_STREAM, $proto) || die "socket: $!";
546 print "novaboot: Connecting to IP relay... ";
547 connect($IPRELAY, $paddr) || die "connect: $!";
549 $exp = Expect->init(\*$IPRELAY);
553 print $exp "\xFF\xF6"; # AYT
554 my $connected = $exp->expect(20, # Timeout in seconds
555 '<iprelayd: connected>',
556 '-re', '<WEB51 HW[^>]*>');
561 my ($relay, $onoff) = @_;
562 die unless ($relay == 1 || $relay == 2);
564 my $cmd = ($relay == 1 ? 0x5 : 0x6) | ($onoff ? 0x20 : 0x10);
565 return "\xFF\xFA\x2C\x32".chr($cmd)."\xFF\xF0";
569 my ($relay, $onoff) = @_;
570 die unless ($relay == 1 || $relay == 2);
571 my $cmd = ($relay == 1 ? 0xdf : 0xbf) | ($onoff ? 0x00 : 0xff);
572 return "\xFF\xFA\x2C\x97".chr($cmd)."\xFF\xF0";
576 my ($relay, $onoff, $can_giveup) = @_;
577 my $confirmation = '';
579 print $exp relaycmd($relay, $onoff);
580 my $confirmed = $exp->expect(20, # Timeout in seconds
581 relayconf($relay, $onoff));
584 print("Relay confirmation timeout - ignoring\n");
586 die "Relay confirmation timeout";
592 $target_reset = sub {
593 relay(2, 1, 1); # Reset the machine
598 $target_power_off = sub {
599 relay(1, 1); # Press power button
600 usleep(6000000); # Long press to switch off
604 $target_power_on = sub {
605 relay(1, 1); # Press power button
606 usleep(100000); # Short press
612 system_verbose("stty -F $serial $stty");
613 open($CONN, "+<", $serial) || die "open $serial: $!";
614 $exp = Expect->init(\*$CONN);
616 elsif ($remote_cmd) {
617 print "novaboot: Running: $remote_cmd\n";
618 $exp = Expect->spawn($remote_cmd);
620 elsif (defined $amt) {
621 require LWP::UserAgent;
622 require LWP::Authen::Digest;
625 my ($host, $username, $password, $schema, $className, $pstate) = @_;
626 #AMT numbers for PowerStateChange (MNI => bluescreen on windows;-)
627 my %pstates = ("on" => 2,
634 <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">
635 <s:Header><a:To>http://$host:16992/wsman</a:To>
636 <w:ResourceURI s:mustUnderstand="true">$schema</w:ResourceURI>
637 <a:ReplyTo><a:Address s:mustUnderstand="true">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address></a:ReplyTo>
638 <a:Action s:mustUnderstand="true">$schema$className</a:Action>
639 <w:MaxEnvelopeSize s:mustUnderstand="true">153600</w:MaxEnvelopeSize>
640 <a:MessageID>uuid:709072C9-609C-4B43-B301-075004043C7C</a:MessageID>
641 <w:Locale xml:lang="en-US" s:mustUnderstand="false" />
642 <w:OperationTimeout>PT60.000S</w:OperationTimeout>
643 <w:SelectorSet><w:Selector Name="Name">Intel(r) AMT Power Management Service</w:Selector></w:SelectorSet>
645 <p:RequestPowerStateChange_INPUT xmlns:p="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_PowerManagementService">
646 <p:PowerState>$pstates{$pstate}</p:PowerState>
647 <p:ManagedElement><a:Address>http://$host:16992/wsman</a:Address>
648 <a:ReferenceParameters><w:ResourceURI>http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ComputerSystem</w:ResourceURI>
649 <w:SelectorSet><w:Selector Name="Name">ManagedSystem</w:Selector></w:SelectorSet>
650 </a:ReferenceParameters></p:ManagedElement>
651 </p:RequestPowerStateChange_INPUT>
652 </s:Body></s:Envelope>
657 my ($host, $username, $password, $content) = @_;
659 my $ua = LWP::UserAgent->new();
660 $ua->agent("novaboot");
662 my $req = HTTP::Request->new(POST => "http://$host:16992/wsman");
663 my $res = $ua->request($req);
664 die ("Unexpected AMT response: " . $res->status_line) unless $res->code == 401;
666 my ($realm) = $res->header("WWW-Authenticate") =~ /Digest realm="(.*?)"/;
667 $ua->credentials("$host:16992", $realm, $username => $password);
670 $req = HTTP::Request->new(POST => "http://$host:16992/wsman");
671 $req->content_type('application/x-www-form-urlencoded');
672 $req->content($content);
673 $res = $ua->request($req);
674 die ("AMT power change request failed: " . $res->status_line) unless $res->is_success;
675 $res->content() =~ /<g:ReturnValue>(\d+)<\/g:ReturnValue>/;
680 my ($host, $username, $password, $pstate)=@_;
681 my $schema="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_PowerManagementService";
682 my $className="/RequestPowerStateChange";
683 my $content = genXML($host, $username, $password ,$schema, $className, $pstate);
684 return sendPOST($host, $username, $password, $content);
687 ($amt_user,$amt_password,$amt_host,$amt_port) = ($amt =~ /(?:(.*?)(?::(.*))?@)?([^:]*)(?::([0-9]*))?/);;
688 $amt_user ||= "admin";
689 $amt_password ||= $ENV{'AMT_PASSWORD'} || die "AMT password not specified";
690 $amt_host || die "AMT host not specified";
694 $target_power_off = sub {
696 my $result = powerChange($amt_host,$amt_user,$amt_password, "off");
697 die "AMT power off failed (ReturnValue $result)" if $result != 0;
700 $target_power_on = sub {
701 my $result = powerChange($amt_host,$amt_user,$amt_password, "on");
702 die "AMT power on failed (ReturnValue $result)" if $result != 0;
705 $target_reset = sub {
706 my $result = powerChange($amt_host,$amt_user,$amt_password, "reset");
708 print STDERR "Warning: Cannot reset $amt_host, trying power on. ";
709 $result = powerChange($amt_host,$amt_user,$amt_password, "on");
711 die "AMT reset failed (ReturnValue $result)" if $result != 0;
714 my $cmd = "amtterm -u $amt_user -p $amt_password $amt_host $amt_port";
715 print "novaboot: Running: $cmd\n" =~ s/\Q$amt_password\E/???/r;
716 $exp = Expect->spawn($cmd);
717 $exp->expect(10, "RUN_SOL") || die "Expect for 'RUN_SOL' timed out";
722 if ($remote_expect) {
723 $exp->expect(10, $remote_expect) || die "Expect for '$remote_expect' timed out";
726 if (defined $reset_cmd) {
727 $target_reset = sub {
728 system_verbose($reset_cmd);
732 if (defined $on_opt && defined $target_power_on) {
736 if (defined $off_opt && defined $target_power_off) {
737 print "novaboot: Switching the target off...\n";
738 &$target_power_off();
742 $builddir ||= dirname(File::Spec->rel2abs( ${$scripts[0]}{filename})) if scalar @scripts;
743 if (defined $builddir) {
744 chdir($builddir) or die "Can't change directory to $builddir: $!";
745 print "novaboot: Entering directory `$builddir'\n";
747 $builddir = $invocation_dir;
750 ## File generation phase
751 my (%files_iso, $menu_iso, $filename);
752 my $config_name = '';
755 foreach my $script (@scripts) {
756 $filename = $$script{filename};
757 $modules = $$script{modules};
758 $generated = $$script{generated};
759 $variables = $$script{variables};
761 ($config_name = $filename) =~ s#.*/##;
762 $config_name = $config_name_opt if (defined $config_name_opt);
764 if (exists $variables->{BUILDDIR}) {
765 $builddir = File::Spec->rel2abs($variables->{BUILDDIR});
766 chdir($builddir) or die "Can't change directory to $builddir: $!";
767 print "novaboot: Entering directory `$builddir'\n";
771 $prefix = $grub_prefix;
772 $prefix =~ s/\$NAME/$config_name/;
773 $prefix =~ s/\$BUILDDIR/$builddir/;
775 # TODO: use $grub_prefix as first parameter if some switch is given
776 generate_configs('', $generated, $filename);
778 ### Generate bootloader configuration files
779 my @bootloader_configs;
780 push @bootloader_configs, generate_grub_config($grub_config, $config_name, $prefix, $modules, $grub_preamble) if (defined $grub_config);
781 push @bootloader_configs, generate_grub2_config($grub2_config, $config_name, $prefix, $modules, $grub_preamble, $grub2_prolog) if (defined $grub2_config);
782 push @bootloader_configs, generate_pulsar_config('config-'.($pulsar||'novaboot'), $modules) if (defined $pulsar);
784 ### Run scons or make
786 my @files = map({ ($file) = m/([^ ]*)/; $file; } @$modules);
787 # Filter-out generated files
788 my @to_build = grep({ my $file = $_; !scalar(grep($file eq ($$_{filename} || ''), @$generated)) } @files);
790 system_verbose($scons || $CFG::scons." ".join(" ", @to_build)) if (defined $scons);
791 system_verbose($make || $CFG::make ." ".join(" ", @to_build)) if (defined $make);
794 ### Copy files (using rsync)
795 if (defined $server && !defined($gen_only)) {
796 (my $real_server = $server) =~ s/\$NAME/$config_name/;
798 my ($hostname, $path) = split(":", $real_server, 2);
799 if (! defined $path) {
803 my $files = join(" ", map({ ($file) = m/([^ ]*)/; $file; } ( @$modules, @bootloader_configs)));
804 map({ my $file = (split)[0]; die "$file: $!" if ! -f $file; } @$modules);
805 my $istty = -t STDOUT && ($ENV{'TERM'} || 'dumb') ne 'dumb';
806 my $progress = $istty ? "--progress" : "";
807 system_verbose("rsync $progress -RLp $rsync_flags $files $real_server");
808 if ($server =~ m|/\$NAME$| && $concat) {
809 my $cmd = join("; ", map { "( cd $path/.. && cat */$_ > $_ )" } @bootloader_configs);
810 system_verbose($hostname ? "ssh $hostname '$cmd'" : $cmd);
814 ### Prepare ISO image generation
815 if (defined $iso_image) {
816 generate_configs("(cd)", $generated, $filename);
818 generate_syslinux_config(\$menu, $config_name, "/", $modules);
819 $menu_iso .= "$menu\n";
820 map { ($file,undef) = split; $files_iso{$file} = 1; } @$modules;
824 ## Generate ISO image
825 if (defined $iso_image) {
826 system_verbose("mkdir -p isolinux");
829 if (-f '/usr/lib/ISOLINUX/isolinux.bin') {
830 # Newer ISOLINUX version
831 @files = qw(/usr/lib/ISOLINUX/isolinux.bin /usr/lib/syslinux/modules/bios/mboot.c32 /usr/lib/syslinux/modules/bios/libcom32.c32 /usr/lib/syslinux/modules/bios/menu.c32 /usr/lib/syslinux/modules/bios/ldlinux.c32);
833 # Older ISOLINUX version
834 @files = qw(/usr/lib/syslinux/isolinux.bin /usr/lib/syslinux/mboot.c32 /usr/lib/syslinux/menu.c32);
836 system_verbose("cp @files isolinux");
837 open(my $fh, ">isolinux/isolinux.cfg");
839 print $fh "TIMEOUT 50\n";
840 print $fh "DEFAULT menu\n";
842 print $fh "DEFAULT $config_name\n";
844 print $fh "$menu_iso";
847 my $files = join(" ", map("$_=$_", (keys(%files_iso), 'isolinux/isolinux.cfg', map(s|.*/|isolinux/|r, @files))));
848 $iso_image ||= "$config_name.iso";
850 # Note: We use -U flag below to "Allow 'untranslated' filenames,
851 # completely violating the ISO9660 standards". Without this
852 # option, isolinux is not able to read files names for example
854 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");
855 print("ISO image created: $builddir/$iso_image\n");
858 exit(0) if defined $gen_only;
860 ## Boot the system using various methods and send serial output to stdout
862 if (scalar(@scripts) > 1 && ( defined $dhcp_tftp || defined $serial || defined $iprelay)) {
863 die "You cannot do this with multiple scripts simultaneously";
866 if ($variables->{WVTEST_TIMEOUT}) {
867 print "wvtest: timeout ", $variables->{WVTEST_TIMEOUT}, "\n";
872 $str =~ s/^\s+|\s+$//g;
880 $qemu ||= $variables->{QEMU} || $CFG::qemu;
881 my @qemu_flags = split(" ", $qemu);
882 $qemu = shift(@qemu_flags);
884 @qemu_flags = split(/ +/, trim($variables->{QEMU_FLAGS})) if exists $variables->{QEMU_FLAGS};
885 @qemu_flags = split(/ +/, trim($qemu_flags_cmd)) if $qemu_flags_cmd;
886 push(@qemu_flags, split(/ +/, trim($qemu_append || '')));
888 if (defined $iso_image) {
889 # Boot NOVA with grub (and test the iso image)
890 push(@qemu_flags, ('-cdrom', $iso_image));
892 # Boot NOVA without GRUB
894 # Non-patched qemu doesn't like commas, but NUL can live with pluses instead of commans
895 foreach (@$modules) {s/,/+/g;}
896 generate_configs("", $generated, $filename);
898 if (scalar @$modules) {
899 my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);
900 $kcmd = '' if !defined $kcmd;
902 @$modules = map { if (/\.dtb$/) { $dtb=$_; (); } else { $_ } } @$modules;
903 my $initrd = join ",", @$modules;
905 push(@qemu_flags, ('-kernel', $kbin, '-append', $kcmd));
906 push(@qemu_flags, ('-initrd', $initrd)) if $initrd;
907 push(@qemu_flags, ('-dtb', $dtb)) if $dtb;
910 push(@qemu_flags, qw(-serial stdio)); # Redirect serial output (for collecting test restuls)
911 unshift(@qemu_flags, ('-name', $config_name));
912 print "novaboot: Running: ".shell_cmd_string($qemu, @qemu_flags)."\n";
913 $exp = Expect->spawn(($qemu, @qemu_flags)) || die("exec() failed: $!");
916 ### Local DHCPD and TFTPD
918 my ($dhcpd_pid, $tftpd_pid);
920 $tftp=1 if $tftp_port;
922 if (defined $dhcp_tftp)
924 generate_configs("(nd)", $generated, $filename);
925 system_verbose('mkdir -p tftpboot');
926 generate_grub_config("tftpboot/os-menu.lst", $config_name, "(nd)", \@$modules, "timeout 0");
927 open(my $fh, '>', 'dhcpd.conf');
928 my $mac = `cat /sys/class/net/eth0/address`;
930 print $fh "subnet 10.23.23.0 netmask 255.255.255.0 {
931 range 10.23.23.10 10.23.23.100;
932 filename \"bin/boot/grub/pxegrub.pxe\";
933 next-server 10.23.23.1;
936 hardware ethernet $mac;
937 fixed-address 10.23.23.1;
940 system_verbose("sudo ip a add 10.23.23.1/24 dev eth0;
941 sudo ip l set dev eth0 up;
942 sudo touch dhcpd.leases");
944 # We run servers by forking ourselves, because the servers end up
945 # in our process group and get killed by signals sent to the
946 # process group (e.g. Ctrl-C on terminal).
948 exec_verbose("sudo dhcpd -d -cf dhcpd.conf -lf dhcpd.leases -pf dhcpd.pid") if ($dhcpd_pid == 0);
951 if (defined $dhcp_tftp || defined $tftp) {
953 # Unfortunately, tftpd requires root privileges even with
954 # non-privileged (>1023) port due to initgroups().
955 system_verbose("sudo in.tftpd --listen --secure -v -v -v --pidfile tftpd.pid --address :$tftp_port $builddir");
957 # Kill server when we die
958 $SIG{__DIE__} = sub { system_verbose('sudo pkill --pidfile=dhcpd.pid') if (defined $dhcp_tftp);
959 system_verbose('sudo pkill --pidfile=tftpd.pid'); };
961 # We have to kill tftpd explicitely, because it is not in our process group
962 $SIG{INT} = sub { system_verbose('sudo pkill --pidfile=tftpd.pid'); exit(0); };
967 my $ider_cmd= "amtider -c $iso_image -u $amt_user -p $amt_password $amt_host $amt_port" ;
968 print "novaboot: Running: $ider_cmd\n" =~ s/\Q$amt_password\E/???/r;
969 my $ider_pid = fork();
970 if ($ider_pid == 0) {
972 die "IDE redirection failed";
974 # FIXME: This collides with --tftp option. Hopefully, nobody needs
975 # to use both simultaneously.
976 $SIG{__DIE__} = sub { system_verbose('kill $ider_pid'); };
979 ### Reset target (IP relay, AMT, ...)
981 if (defined $target_reset && $reset) {
982 print "novaboot: Reseting the test box... ";
987 ### U-boot conversation
988 if (defined $uboot) {
989 my $uboot_prompt = $uboot || '=> ';
990 print "novaboot: Waiting for U-Boot prompt...\n";
992 #$exp->exp_internal(1);
994 [qr/Hit any key to stop autoboot:/, sub { $exp->send("\n"); exp_continue; }],
995 $uboot_prompt) || die "No U-Boot prompt deteceted";
996 foreach my $cmd (@uboot_init) {
997 if ($cmd =~ /\$NB_MYIP/) {
998 my $ip = (grep /inet /, `ip addr show eth0`)[0] || die "Problem determining our IP address";
999 $ip =~ s/\s*inet ([0-9.]*).*/$1/;
1000 $cmd =~ s/\$NB_MYIP/$ip/g;
1003 $exp->send("$cmd\n");
1004 $exp->expect(10, $uboot_prompt) || die "U-Boot prompt timeout";
1007 # Boot the system if there are some load lines in the script
1008 if (scalar(@$modules) > 0) {
1009 my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);
1011 @$modules = map { if (/\.dtb$/) { $dtb=$_; (); } else { $_ } } @$modules;
1012 my $initrd = shift @$modules;
1014 my ($ramdisk_addr, $fdt_addr) = ('-', '');
1016 die "No '--uboot-addr kernel' given" unless $uboot_addr{kernel};
1017 $exp->send("tftpboot $uboot_addr{kernel} $prefix$kbin\n");
1019 [qr/##/, sub { exp_continue; }],
1020 $uboot_prompt) || die "Kernel load timeout";
1022 die "No '--uboot-addr fdt' given" unless $uboot_addr{fdt};
1023 $fdt_addr = $uboot_addr{fdt};
1024 $exp->send("tftpboot $fdt_addr $prefix$dtb\n");
1026 [qr/##/, sub { exp_continue; }],
1027 $uboot_prompt) || die "Device tree load timeout";
1029 if (defined $initrd) {
1030 die "No '--uboot-addr ramdisk' given" unless $uboot_addr{ramdisk};
1031 $ramdisk_addr = $uboot_addr{ramdisk};
1032 $exp->send("tftpboot $ramdisk_addr $prefix$initrd\n");
1034 [qr/#/, sub { exp_continue; }],
1035 $uboot_prompt) || die "Initrd load timeout";
1037 $exp->send("echo $kcmd\n");
1038 $exp->expect(1, '-re', qr{echo .*\n(.*)\n$uboot_prompt}) || die "Command line test timeout";
1039 my $args = ($exp->matchlist)[0];
1040 if ($args =~ /^setenv\s+bootargs/) {
1041 $exp->send("$args\n");
1043 $exp->send("setenv bootargs $kcmd\n");
1045 $exp->expect(5, $uboot_prompt) || die "U-Boot prompt timeout";
1046 $exp->send("bootm $uboot_addr{kernel} $ramdisk_addr $fdt_addr\n");
1047 $exp->expect(5, "\n") || die "U-Boot command timeout";
1051 ### Serial line interaction
1053 # Serial line of the target is available
1054 my $interrupt = 'Ctrl-C';
1055 if ($interactive && !@exiton) {
1056 $interrupt = '"~~."';
1058 my $note = (-t STDIN) ? '' : '- only target->host ';
1059 print "novaboot: Serial line interaction $note(press $interrupt to interrupt)...\n";
1060 $exp->log_stdout(1);
1062 $exp->expect(undef, @expect_raw, @exiton);
1064 my @inputs = ($exp);
1065 if (-t STDIN) { # Set up bi-directional communication if we run on terminal
1066 my $infile = new IO::File;
1067 $infile->IO::File::fdopen(*STDIN,'r');
1068 my $in_object = Expect->exp_init($infile);
1069 $in_object->set_group($exp);
1072 $in_object->set_seq('~~\.', sub { print "novaboot: Escape sequence detected\r\n"; undef; });
1073 $in_object->manual_stty(0); # Use raw terminal mode
1075 $in_object->manual_stty(1); # Do not modify terminal settings
1077 push(@inputs, $in_object);
1080 #print Dumper(\@expect_raw);
1081 $exp->expect(undef, @expect_raw) if @expect_raw;
1082 Expect::interconnect(@inputs) unless defined($exp->exitstatus);
1086 ## Kill dhcpc or tftpd
1087 if (defined $dhcp_tftp || defined $tftp) {
1088 die("novaboot: This should kill servers on background\n");
1095 novaboot - A tool for booting various operating systems on various hardware or in qemu
1101 B<novaboot> [option]... [--] script...
1103 B<./script> [option]...
1107 This program makes booting of an operating system (e.g. NOVA or Linux)
1108 as simple as running a local program. It facilitates booting on local
1109 or remote hosts or in emulators such as qemu. Novaboot operation is
1110 controlled by command line options and by a so called novaboot script,
1111 which can be thought as a generalization of bootloader configuration
1112 files (see L</"NOVABOOT SCRIPT SYNTAX">). Based on this input,
1113 novaboot setups everything for the target host to boot the desired
1114 configuration, i.e. it generates the bootloader configuration file in
1115 the proper format, deploy the binaries and other needed files to
1116 required locations, perhaps on a remote boot server and reset the
1117 target host. Finally, target host's serial output is redirected to
1118 standard output if that is possible.
1120 Typical way of using novaboot is to make the novaboot script
1121 executable and set its first line to I<#!/usr/bin/env novaboot>. Then,
1122 booting a particular OS configuration becomes the same as executing a
1123 local program - the novaboot script.
1125 For example, with C<novaboot> you can:
1131 Run an OS in Qemu. This is the default action when no other action is
1132 specified by command line switches. Thus running C<novaboot ./script>
1133 (or C<./script> as described above) will run Qemu and make it boot the
1134 configuration specified in the F<script>.
1138 Create a bootloader configuration file (currently supported
1139 bootloaders are GRUB, GRUB2, ISOLINUX, Pulsar and U-Boot) and copy it
1140 with all other files needed for booting to a remote boot server.
1142 ./script --server=192.168.1.1:/tftp --iprelay=192.168.1.2
1144 This command copies files to the TFTP server and uses
1145 TCP/IP-controlled relay to reset the target host and receive its
1150 Run DHCP and TFTP server on developer's machine to PXE-boot the target
1151 machine from it. E.g.
1153 ./script --dhcp-tftp
1155 When a PXE-bootable machine is connected via Ethernet to developer's
1156 machine, it will boot the configuration described in the I<script>.
1160 Create bootable ISO images. E.g.
1162 novaboot --iso -- script1 script2
1164 The created ISO image will use ISOLINUX bootloader installed on it and
1165 the boot menu will allow selecting between I<script1> and I<script2>
1170 Note that the options needed for a specific target can be stored in a
1171 L</"CONFIGURATION FILE">. Then it is sufficient to use only the B<-t>
1172 option to specify the name of the target.
1174 =head1 PHASES AND OPTIONS
1176 Novaboot performs its work in several phases. Each phase can be
1177 influenced by several command line options, certain phases can be
1178 skipped. The list of phases (in the execution order) and the
1179 corresponding options follow.
1181 =head2 Configuration reading phase
1183 After starting, novaboot reads configuration files. Their content is
1184 described in section L</"CONFIGURATION FILE">. By default,
1185 configuration is read from two locations. First from the configuration
1186 directory and second from F<.novaboot> files along the path to the
1187 current directory. The later read files override settings from the
1190 Configuration directory is determined by the content of
1191 NOVABOOT_CONFIG_DIR environment variable defaulting to
1192 F</etc/novaboot.d>. Files in this directory with names consisting
1193 solely from English letters, numbers, dashes '-' and underscores '_'
1194 (note that dot '.' is not included) are read in alphabetical order.
1196 Then novaboot searches for files named F<.novaboot> starting from the
1197 directory of the novaboot script (or working directory, see bellow)
1198 and continuing upwards up to the root directory. The found
1199 configuration files are then read in the opposite order (i.e. from the
1200 root directory downwards). This allows to have, for example, user
1201 specific configuration in F<~/.novaboot> and project specific one in
1202 F<~/project/.novaboot>.
1204 In certain cases, the location of the novaboot script cannot be
1205 determined in this early phase. This happens either when the script is
1206 read from the standard input or when novaboot is invoked explicitly as
1207 in the example L</"4."> above. In this case the current working
1208 directory is used as a starting point for configuration file search
1209 instead of the novaboot script directory.
1213 =item -c, --config=I<filename>
1215 Use the specified configuration file instead of the default one(s).
1219 =head2 Command line processing phase
1225 Dump the current configuration to stdout end exits. Useful as an
1226 initial template for a configuration file.
1230 Print short (B<-h>) or long (B<--help>) help.
1232 =item -t, --target=I<target>
1234 This option serves as a user configurable shortcut for other novaboot
1235 options. The effect of this option is the same as the options stored
1236 in the C<%targets> configuration variable under key I<target>. See
1237 also L</"CONFIGURATION FILE">.
1241 =head2 Script preprocessing phase
1243 This phases allows to modify the parsed novaboot script before it is
1244 used in the later phases.
1248 =item -a, --append=I<parameters>
1250 Append a string to the first C<load> line in the novaboot script. This
1251 can be used to append parameters to the kernel's or root task's
1252 command line. This option can appear multiple times.
1256 Use F<bender> chainloader. Bender scans the PCI bus for PCI serial
1257 ports and stores the information about them in the BIOS data area for
1260 =item --chainloader=I<chainloader>
1262 Specifies a chainloader that is loaded before the kernel and other
1263 files specified in the novaboot script. E.g. 'bin/boot/bender
1268 Print the modules to boot and their parameters after this phase
1269 finishes. Then exit. This is useful for seeing the effect of other
1270 options in this section.
1272 =item -k, --kernel=F<file>
1274 Replace the first word on the first C<load> line in the novaboot
1275 script with F<file>.
1277 =item --scriptmod=I<perl expression>
1279 When novaboot script is read, I<perl expression> is executed for every
1280 line (in $_ variable). For example, C<novaboot
1281 --scriptmod=s/sigma0/omega6/g> replaces every occurrence of I<sigma0>
1282 in the script with I<omega6>.
1284 When this option is present, it overrides I<$script_modifier> variable
1285 from the configuration file, which has the same effect. If this option
1286 is given multiple times all expressions are evaluated in the command
1291 =head2 File generation phase
1293 In this phase, files needed for booting are generated in a so called
1294 I<build directory> (see L</--build-dir>). In most cases configuration
1295 for a bootloader is generated automatically by novaboot. It is also
1296 possible to generate other files using I<heredoc> or I<"<"> syntax in
1297 novaboot scripts. Finally, binaries can be generated in this phases by
1298 running C<scons> or C<make>.
1302 =item --build-dir=I<directory>
1304 Overrides the default build directory location.
1306 The default build directory location is determined as follows: If the
1307 configuration file defines the C<$builddir> variable, its value is
1308 used. Otherwise, it is the directory that contains the first processed
1311 See also L</BUILDDIR> variable.
1313 =item -g, --grub[=I<filename>]
1315 Generates grub bootloader menu file. If the I<filename> is not
1316 specified, F<menu.lst> is used. The I<filename> is relative to the
1317 build directory (see B<--build-dir>).
1319 =item --grub-preamble=I<prefix>
1321 Specifies the I<preable> that is at the beginning of the generated
1322 GRUB or GRUB2 config files. This is useful for specifying GRUB's
1325 =item --prefix=I<prefix>
1327 Specifies I<prefix> (e.g. F</srv/tftp>) that is put in front of every
1328 file name in generated bootloader configuration files (or in U-Boot
1331 If the I<prefix> contains string $NAME, it will be replaced with the
1332 name of the novaboot script (see also B<--name>).
1334 If the I<prefix> contains string $BUILDDIR, it will be replaced with
1335 the build directory (see also B<--build-dir>).
1339 Alias for B<--prefix>.
1341 =item --grub2[=I<filename>]
1343 Generate GRUB2 menu entry in I<filename>. If I<filename> is not
1344 specified F<grub.cfg> is used. The content of the menu entry can be
1345 customized with B<--grub-preamble>, B<--grub2-prolog> or
1346 B<--grub_prefix> options.
1348 In order to use the the generated menu entry on your development
1349 machine that uses GRUB2, append the following snippet to
1350 F</etc/grub.d/40_custom> file and regenerate your grub configuration,
1351 i.e. run update-grub on Debian/Ubuntu.
1353 if [ -f /path/to/nul/build/grub.cfg ]; then
1354 source /path/to/nul/build/grub.cfg
1357 =item --grub2-prolog=I<prolog>
1359 Specifies text that is put at the beginning of the GRUB2 menu entry.
1361 =item -m, --make[=make command]
1363 Runs C<make> to build files that are not generated by novaboot itself.
1365 =item --name=I<string>
1367 Use the name I<string> instead of the name of the novaboot script.
1368 This name is used for things like a title of grub menu or for the
1369 server directory where the boot files are copied to.
1373 Do not run external commands to generate files (i.e. "<" syntax and
1374 C<run> keyword). This switch does not influence generation of files
1375 specified with "<<WORD" syntax.
1377 =item -p, --pulsar[=mac]
1379 Generates pulsar bootloader configuration file named F<config-I<mac>>
1380 The I<mac> string is typically a MAC address and defaults to
1383 =item --scons[=scons command]
1385 Runs C<scons> to build files that are not generated by novaboot
1390 Strip I<rom://> prefix from command lines and generated config files.
1391 The I<rom://> prefix is used by NUL. For NRE, it has to be stripped.
1395 Exit novaboot after file generation phase.
1399 =head2 Target connection check
1401 If supported by the target, the connection to it is made and it is
1402 checked whether the target is not occupied by another novaboot
1407 =item --amt=I<"[user[:password]@]host[:port]>
1409 Use Intel AMT technology to control the target machine. WS management
1410 is used to powercycle it and Serial-Over-Lan (SOL) for input/output.
1411 The hostname or (IP address) is given by the I<host> parameter. If
1412 I<password> is not specified, environment variable AMT_PASSWORD is
1413 used. The I<port> specifies a TCP port for SOL. If not specified, the
1414 default is 16992. Default I<user> is admin.
1416 =item --iprelay=I<addr[:port]>
1418 Use TCP/IP relay and serial port to access the target's serial port
1419 and powercycle it. The IP address of the relay is given by I<addr>
1420 parameter. If I<port> is not specified, it default to 23.
1422 Note: This option is supposed to work with HWG-ER02a IP relays.
1424 =item -s, --serial[=device]
1426 Target's serial line is connected to host's serial line (device). The
1427 default value for device is F</dev/ttyUSB0>.
1429 The value of this option is exported in NB_NOVABOOT environment
1430 variable to all subprocesses run by C<novaboot>.
1432 =item --stty=I<settings>
1434 Specifies settings passed to C<stty> invoked on the serial line
1435 specified with B<--serial> option. If this option is not given,
1436 C<stty> is called with C<raw -crtscts -onlcr 115200> settings.
1438 =item --remote-cmd=I<cmd>
1440 Command that mediates connection to the target's serial line. For
1441 example C<ssh server 'cu -l /dev/ttyS0'>.
1443 =item --remote-expect=I<string>
1445 Wait for reception of I<string> after establishing the the remote
1446 connection before continuing.
1451 =head2 File deployment phase
1453 In some setups, it is necessary to copy the files needed for booting
1454 to a particular location, e.g. to a TFTP boot server or to the
1459 =item -d, --dhcp-tftp
1461 Turns your workstation into a DHCP and TFTP server so that the OS can
1462 be booted via PXE BIOS (or similar mechanism) on the test machine
1463 directly connected by a plain Ethernet cable to your workstation.
1465 The DHCP and TFTP servers requires root privileges and C<novaboot>
1466 uses C<sudo> command to obtain those. You can put the following to
1467 I</etc/sudoers> to allow running the necessary commands without asking
1470 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
1471 your_login ALL=NOPASSWD: NOVABOOT
1475 Starts a TFTP server on your workstation. This is similar to
1476 B<--dhcp-tftp> except that DHCP server is not started.
1478 The TFTP server require root privileges and C<novaboot> uses C<sudo>
1479 command to obtain those. You can put the following to I</etc/sudoers>
1480 to allow running the necessary commands without asking for password.
1482 Cmnd_Alias NOVABOOT = /usr/sbin/in.tftpd --listen --secure -v -v -v --pidfile tftpd.pid *, /usr/bin/pkill --pidfile=tftpd.pid
1483 your_login ALL=NOPASSWD: NOVABOOT
1485 =item --tftp-port=I<port>
1487 Port to run the TFTP server on. Implies B<--tftp>.
1489 =item --iso[=filename]
1491 Generates the ISO image that boots NOVA system via GRUB. If no filename
1492 is given, the image is stored under I<NAME>.iso, where I<NAME> is the name
1493 of the novaboot script (see also B<--name>).
1495 =item --server[=[[user@]server:]path]
1497 Copy all files needed for booting to another location. The files will
1498 be copied (by B<rsync> tool) to the directory I<path>. If the I<path>
1499 contains string $NAME, it will be replaced with the name of the
1500 novaboot script (see also B<--name>).
1502 =item --rsync-flags=I<flags>
1504 Specifies which I<flags> are appended to F<rsync> command line when
1505 copying files as a result of I<--server> option.
1509 If B<--server> is used and its value ends with $NAME, then after
1510 copying the files, a new bootloader configuration file (e.g. menu.lst)
1511 is created at I<path-wo-name>, i.e. the path specified by B<--server>
1512 with $NAME part removed. The content of the file is created by
1513 concatenating all files of the same name from all subdirectories of
1514 I<path-wo-name> found on the "server".
1518 Use Intel AMT technology for IDE redirection. This allows the target
1519 machine to boot from novaboot created ISO image. Implies B<--iso>.
1521 The experimental C<amtider> utility needed by this option can be
1522 obtained from https://github.com/wentasah/amtterm.
1526 =head2 Target power-on and reset phase
1528 At this point, the target is reset (or switched on/off). There is
1529 several ways how this can be accomplished. Resetting a physical target
1530 can currently be accomplished by the following options: B<--amt>,
1531 B<--iprelay>, B<--reset-cmd>.
1537 Switch on/off the target machine and exit. The script (if any) is
1538 completely ignored. Currently it works only with B<--iprelay> or
1541 =item -Q, --qemu[=I<qemu-binary>]
1543 Boot the configuration in qemu. Optionally, the name of qemu binary
1544 can be specified as a parameter.
1546 =item --qemu-append=I<flags>
1548 Append I<flags> to the default qemu flags (QEMU_FLAGS variable or
1549 C<-cpu coreduo -smp 2>).
1551 =item -q, --qemu-flags=I<flags>
1553 Replace the default qemu flags (QEMU_FLAGS variable or C<-cpu coreduo
1554 -smp 2>) with I<flags> specified here.
1556 =item --reset-cmd=I<cmd>
1558 Command that resets the target.
1560 =item --no-reset, --reset
1562 Disable/enable reseting of the target.
1566 =head2 Interaction with the bootloader on the target
1570 =item --uboot[=I<prompt>]
1572 Interact with U-Boot bootloader to boot the thing described in the
1573 novaboot script. I<prompt> specifies the U-Boot's prompt (default is
1574 "=> ", other common prompts are "U-Boot> " or "U-Boot# ").
1575 Implementation of this option is currently tied to a particular board
1576 that we use. It may be subject to changes in the future!
1580 Command(s) to send the U-Boot bootloader before loading the images and
1581 booting them. This option can be given multiple times. After sending
1582 commands from each option novaboot waits for U-Boot I<prompt>.
1584 If the command contains string I<$NB_MYIP> then this string is
1585 replaced by IPv4 address of eth0 interface.
1587 =item --uboot-addr I<name>=I<address>
1589 Load address of U-Boot's C<tftpboot> command for loading I<name>,
1590 where name is one of I<kernel>, I<ramdisk> or I<fdt> (flattened device
1595 =head2 Target interaction phase
1597 In this phase, target's serial output is redirected to stdout and if
1598 stdin is a TTY, it is redirected to the target's serial input allowing
1599 interactive work with the target.
1603 =item --exiton=I<string>
1605 When I<string> is sent by the target, novaboot exits. This option can
1606 be specified multiple times.
1608 If I<string> is C<-re>, then the next B<--exiton>'s I<string> is
1609 treated as regular expression. For example:
1611 --exiton -re --exiton 'error:.*failed'
1613 =item -i, --interactive
1615 Setup things for interactive use of target. Your terminal will be
1616 switched to raw mode. In raw mode, your system does not process input
1617 in any way (no echoing of entered characters, no interpretation
1618 special characters). This, among others, means that Ctrl-C is passed
1619 to the target and does no longer interrupt novaboot. Use "~~."
1620 sequence to exit novaboot.
1622 =item --expect=I<string>
1624 When I<string> is received from the target, send the string specified
1625 with the subsequent B<--send*> option to the target.
1627 =item --expect-re=I<regex>
1629 When target's output matches regular expression I<regex>, send the
1630 string specified with the subsequent B<--send*> option to the target.
1632 =item --expect-raw=I<perl-code>
1634 Provides direct control over Perl's Expect module.
1636 =item --send=I<string>
1638 Send I<string> to the target after the previously specified
1639 B<--expect*> was matched in the target's output. The I<string> may
1640 contain escape sequences such as "\n".
1642 Note that I<string> is actually interpreted by Perl, so it can contain
1643 much more that escape sequences. This behavior may change in the
1646 Example: C<--expect='login: ' --send='root\n'>
1648 =item --sendcont=I<string>
1650 Similar to B<--send> but continue expecting more input.
1652 Example: C<--expect='Continue?' --sendcont='yes\n'>
1656 =head1 NOVABOOT SCRIPT SYNTAX
1658 The syntax tries to mimic POSIX shell syntax. The syntax is defined
1659 with the following rules.
1661 Lines starting with "#" and empty lines are ignored.
1663 Lines that end with "\" are concatenated with the following line after
1664 removal of the final "\" and leading whitespace of the following line.
1666 Lines of the form I<VARIABLE=...> (i.e. matching '^[A-Z_]+=' regular
1667 expression) assign values to internal variables. See L</VARIABLES>
1670 Lines starting with C<load> keyword represent modules to boot. The
1671 word after C<load> is a file name (relative to the build directory
1672 (see B<--build-dir>) of the module to load and the remaining words are
1673 passed to it as the command line parameters.
1675 When the C<load> line ends with "<<WORD" then the subsequent lines
1676 until the line containing solely WORD are copied literally to the file
1677 named on that line. This is similar to shell's heredoc feature.
1679 When the C<load> line ends with "< CMD" then command CMD is executed
1680 with F</bin/sh> and its standard output is stored in the file named on
1681 that line. The SRCDIR variable in CMD's environment is set to the
1682 absolute path of the directory containing the interpreted novaboot
1685 Lines starting with C<run> keyword contain shell commands that are run
1686 during file generation phase. This is the same as the "< CMD" syntax
1687 for C<load> keyboard except that the command's output is not
1688 redirected to a file. The ordering of commands is the same as they
1689 appear in the novaboot script.
1693 #!/usr/bin/env novaboot
1694 load bzImage console=ttyS0,115200
1695 run make -C buildroot
1696 load rootfs.cpio < gen_cpio buildroot/images/rootfs.cpio "myapp->/etc/init.d/S99myapp"
1698 Example (NOVA User Land - NUL):
1700 #!/usr/bin/env novaboot
1701 WVDESC=Example program
1702 load bin/apps/sigma0.nul S0_DEFAULT script_start:1,1 \
1703 verbose hostkeyb:0,0x60,1,12,2
1704 load bin/apps/hello.nul
1705 load hello.nulconfig <<EOF
1706 sigma0::mem:16 name::/s0/log name::/s0/timer name::/s0/fs/rom ||
1707 rom://bin/apps/hello.nul
1710 This example will load three modules: F<sigma0.nul>, F<hello.nul> and
1711 F<hello.nulconfig>. sigma0 receives some command line parameters and
1712 F<hello.nulconfig> file is generated on the fly from the lines between
1713 C<<<EOF> and C<EOF>.
1717 The following variables are interpreted in the novaboot script:
1723 Novaboot chdir()s to this directory before file generation phase. The
1724 directory name specified here is relative to the build directory
1725 specified by other means (see L</--build-dir>).
1729 Assigning this variable has the same effect as specifying L</--exiton>
1732 =item HYPERVISOR_PARAMS
1734 Parameters passed to hypervisor. The default value is "serial", unless
1735 overridden in configuration file.
1739 The kernel to use instead of the hypervisor specified in the
1740 configuration file with the C<$hypervisor> variable. The value should
1741 contain the name of the kernel image as well as its command line
1742 parameters. If this variable is defined and non-empty, the variable
1743 HYPERVISOR_PARAMS is not used.
1747 Use a specific qemu binary (can be overridden with B<-Q>) and flags
1748 when booting this script under qemu. If QEMU_FLAGS variable is also
1749 specified flags specified in QEMU variable are replaced by those in
1754 Use specific qemu flags (can be overridden with B<-q>).
1758 Description of the WvTest-compliant program.
1760 =item WVTEST_TIMEOUT
1762 The timeout in seconds for WvTest harness. If no complete line appears
1763 in the test output within the time specified here, the test fails. It
1764 is necessary to specify this for long running tests that produce no
1765 intermediate output.
1769 =head1 CONFIGURATION FILE
1771 Novaboot can read its configuration from one or more files. By
1772 default, novaboot looks for files named F<.novaboot> as described in
1773 L</Configuration reading phase>. Alternatively, configuration file
1774 location can be specified with the B<-c> switch or with the
1775 NOVABOOT_CONFIG environment variable. The configuration file has Perl
1776 syntax and should set values of certain Perl variables. The current
1777 configuration can be dumped with the B<--dump-config> switch. Some
1778 configuration variables can be overridden by environment variables
1779 (see below) or by command line switches.
1781 Supported configuration variables include:
1787 Build directory location relative to the location of the configuration
1790 =item $default_target
1792 Default target (see below) to use when no target is explicitly
1793 specified on command line with the B<--target> option.
1797 Hash of target definitions to be used with the B<--target> option. The
1798 key is the identifier of the target, the value is the string with
1799 command line options. For instance, if the configuration file contains:
1801 $targets{'mybox'} = '--server=boot:/tftproot --serial=/dev/ttyUSB0 --grub',
1803 then the following two commands are equivalent:
1805 ./script --server=boot:/tftproot --serial=/dev/ttyUSB0 --grub
1810 =head1 ENVIRONMENT VARIABLES
1812 Some options can be specified not only via config file or command line
1813 but also through environment variables. Environment variables override
1814 the values from configuration file and command line parameters
1815 override the environment variables.
1819 =item NOVABOOT_CONFIG
1821 Name of the novaboot configuration file to use instead of the default
1824 =item NOVABOOT_CONFIG_DIR
1826 Name of the novaboot configuration directory. When not specified
1827 F</etc/novaboot.d> is used.
1829 =item NOVABOOT_BENDER
1831 Defining this variable has the same meaning as B<--bender> option.
1837 Michal Sojka <sojka@os.inf.tu-dresden.de>
1841 # LocalWords: novaboot Novaboot NOVABOOT TFTP PXE DHCP filename stty
1842 # LocalWords: chainloader stdout Qemu qemu preprocessing ISOLINUX bootable
1843 # LocalWords: config subprocesses sudo sudoers tftp dhcp IDE stdin
1844 # LocalWords: subdirectories TTY whitespace heredoc POSIX WvTest