3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation, either version 2 of the License, or
6 # (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18 use warnings (exists $ENV{NOVABOOT_TEST} ? (FATAL => 'all') : ());
19 use Getopt::Long qw(GetOptionsFromString);
24 use Time::HiRes("usleep");
28 use POSIX qw(:errno_h);
29 use Cwd qw(getcwd abs_path);
35 my $invocation_dir = getcwd();
37 ## Configuration file handling
39 # Default configuration
40 $CFG::hypervisor = "";
41 $CFG::hypervisor_params = "serial";
42 $CFG::genisoimage = "genisoimage";
43 $CFG::qemu = 'qemu -cpu coreduo -smp 2';
45 "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',
46 "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',
47 "localhost" => '--scriptmod=s/console=tty[A-Z0-9,]+// --server=/boot/novaboot/$NAME --grub2 --grub-prefix=/boot/novaboot/$NAME --grub2-prolog=" set root=\'(hd0,msdos1)\'"',
49 $CFG::scons = "scons -j2";
56 package CFG; # Put config data into a separate namespace
61 die("ERROR: Failure compiling '$cfg' - $@");
62 } elsif (! defined($rc)) {
63 die("ERROR: Failure reading '$cfg' - $!");
65 die("ERROR: Failure processing '$cfg'");
68 $builddir = File::Spec->rel2abs($CFG::builddir, dirname($cfg)) if defined $CFG::builddir;
69 print STDERR "novaboot: Read $cfg\n";
74 # We don't use $0 here, because it points to the novaboot itself and
75 # not to the novaboot script. The problem with this approach is that
76 # when a script is run as "novaboot <options> <script>" then $ARGV[0]
77 # contains the first option. Hence the -f check.
78 my $dir = File::Spec->rel2abs($ARGV[0] && -f $ARGV[0] ? dirname($ARGV[0]) : '', $invocation_dir);
79 while (-d $dir && $dir ne "/") {
80 push @cfgs, "$dir/.novaboot" if -r "$dir/.novaboot";
81 $dir = abs_path($dir."/..");
84 my $cfg = $ENV{'NOVABOOT_CONFIG'};
85 Getopt::Long::Configure(qw/no_ignore_case pass_through/);
86 GetOptions ("config|c=s" => \$cfg);
87 read_config($_) foreach $cfg or reverse @cfgs;
89 ## Command line handling
91 my ($append, $bender, @chainloaders, $concat, $config_name_opt, $dhcp_tftp, $dump_opt, $dump_config, $gen_only, $grub_config, $grub_prefix, $grub_preamble, $grub2_prolog, $grub2_config, $help, $iprelay, $iso_image, $man, $no_file_gen, $off_opt, $on_opt, $pulsar, $pulsar_root, $qemu, $qemu_append, $qemu_flags_cmd, $rom_prefix, $rsync_flags, @scriptmod, $scons, $serial, $server, $stty, $uboot);
94 $rom_prefix = 'rom://';
95 $stty = 'raw -crtscts -onlcr 115200';
97 Getopt::Long::Configure(qw/no_ignore_case no_pass_through/);
100 "append|a=s" => \$append,
101 "bender|b" => \$bender,
102 "build-dir=s" => sub { my ($n, $v) = @_; $builddir = File::Spec->rel2abs($v); },
103 "concat" => \$concat,
104 "chainloader=s" => \@chainloaders,
105 "dhcp-tftp|d" => \$dhcp_tftp,
106 "dump" => \$dump_opt,
107 "dump-config" => \$dump_config,
108 "gen-only" => \$gen_only,
109 "grub|g:s" => \$grub_config,
110 "grub-preamble=s"=> \$grub_preamble,
111 "grub-prefix=s" => \$grub_prefix,
112 "grub2:s" => \$grub2_config,
113 "grub2-prolog=s" => \$grub2_prolog,
114 "iprelay=s" => \$iprelay,
115 "iso|i:s" => \$iso_image,
116 "name=s" => \$config_name_opt,
117 "no-file-gen" => \$no_file_gen,
120 "pulsar|p:s" => \$pulsar,
121 "pulsar-root=s" => \$pulsar_root,
122 "qemu|Q=s" => \$qemu,
123 "qemu-append=s" => \$qemu_append,
124 "qemu-flags|q=s" => \$qemu_flags_cmd,
125 "rsync-flags=s" => \$rsync_flags,
126 "scons:s" => \$scons,
127 "scriptmod=s" => \@scriptmod,
128 "serial|s:s" => \$serial,
129 "server:s" => \$server,
130 "strip-rom" => sub { $rom_prefix = ''; },
132 "target|t=s" => sub { my ($opt_name, $opt_value) = @_;
133 exists $CFG::targets{$opt_value} or die("Unknown target '$opt_value' (valid targets are: ".join(", ", sort keys(%CFG::targets)).")");
134 GetOptionsFromString($CFG::targets{$opt_value}, %opt_spec); },
139 GetOptions %opt_spec or pod2usage(2);
140 pod2usage(1) if $help;
141 pod2usage(-exitstatus => 0, -verbose => 2) if $man;
143 ### Dump sanitized configuration (if requested)
147 $Data::Dumper::Indent=1;
148 print "# This file is in perl syntax.\n";
149 foreach my $key(sort(keys(%CFG::))) { # See "Symbol Tables" in perlmod(1)
150 if (defined ${$CFG::{$key}}) { print Data::Dumper->Dump([${$CFG::{$key}}], ["*$key"]); }
151 if ( @{$CFG::{$key}}) { print Data::Dumper->Dump([\@{$CFG::{$key}}], ["*$key"]); }
152 if ( %{$CFG::{$key}}) { print Data::Dumper->Dump([\%{$CFG::{$key}}], ["*$key"]); }
158 ### Sanitize configuration
160 if (defined $config_name_opt && scalar(@ARGV) > 1) { die "You cannot use --name with multiple scripts"; }
163 if (defined $serial) { $serial ||= "/dev/ttyUSB0"; }
164 if (defined $grub_config) { $grub_config ||= "menu.lst"; }
165 if (defined $grub2_config) { $grub2_config ||= "grub.cfg"; }
167 ## Parse the novaboot script(s)
173 my ($modules, $variables, $generated, $continuation);
175 if ($ARGV ne $last_fn) { # New script
176 die "Missing EOF in $last_fn" if $file;
177 die "Unfinished line in $last_fn" if $line;
179 push @scripts, { 'filename' => $ARGV,
180 'modules' => $modules = [],
181 'variables' => $variables = {},
182 'generated' => $generated = []};
186 next if /^#/ || /^\s*$/; # Skip comments and empty lines
188 foreach my $mod(@scriptmod) { eval $mod; }
190 print "$_\n" if $dump_opt;
192 if (/^([A-Z_]+)=(.*)$/) { # Internal variable
193 $$variables{$1} = $2;
196 if (/^([^ ]*)(.*?)[[:space:]]*<<([^ ]*)$/) { # Heredoc start
197 push @$modules, "$1$2";
199 push @$generated, {filename => $1, content => $file};
203 if ($file && $_ eq $EOF) { # Heredoc end
207 if ($file) { # Heredoc content
208 push @{$file}, "$_\n";
211 $_ =~ s/^[[:space:]]*// if ($continuation);
212 if (/\\$/) { # Line continuation
213 $line .= substr($_, 0, length($_)-1);
219 $line .= " $append" if ($append && scalar(@$modules) == 0);
221 if ($line =~ /^([^ ]*)(.*?)[[:space:]]*< ?(.*)$/) { # Command substitution
222 push @$modules, "$1$2";
223 push @$generated, {filename => $1, command => $3};
227 push @$modules, $line;
231 #print Dumper(\@scripts);
237 sub generate_configs($$$) {
238 my ($base, $generated, $filename) = @_;
239 if ($base) { $base = "$base/"; };
240 foreach my $g(@$generated) {
241 if (exists $$g{content}) {
242 my $config = $$g{content};
243 my $fn = $$g{filename};
244 open(my $f, '>', $fn) || die("$fn: $!");
245 map { s|\brom://([^ ]*)|$rom_prefix$base$1|g; print $f "$_"; } @{$config};
247 print "novaboot: Created $fn\n";
248 } elsif (exists $$g{command} && ! $no_file_gen) {
249 $ENV{SRCDIR} = dirname(File::Spec->rel2abs( $filename, $invocation_dir ));
250 system_verbose("( $$g{command} ) > $$g{filename}");
255 sub generate_grub_config($$$$;$)
257 my ($filename, $title, $base, $modules_ref, $preamble) = @_;
258 if ($base) { $base = "$base/"; };
259 open(my $fg, '>', $filename) or die "$filename: $!";
260 print $fg "$preamble\n" if $preamble;
261 print $fg "title $title\n" if $title;
262 #print $fg "root $base\n"; # root doesn't really work for (nd)
264 foreach (@$modules_ref) {
267 my ($kbin, $kcmd) = split(' ', $_, 2);
268 $kcmd = '' if !defined $kcmd;
269 print $fg "kernel ${base}$kbin $kcmd\n";
271 s|\brom://([^ ]*)|$rom_prefix$base$1|g; # Translate rom:// files - needed for vdisk parameter of sigma0
272 print $fg "module $base$_\n";
276 print("novaboot: Created $builddir/$filename\n");
280 sub generate_grub2_config($$$$;$$)
282 my ($filename, $title, $base, $modules_ref, $preamble, $prolog) = @_;
283 if ($base && substr($base,-1,1) ne '/') { $base = "$base/"; };
284 open(my $fg, '>', $filename) or die "$filename: $!";
285 print $fg "$preamble\n" if $preamble;
286 $title ||= 'novaboot';
287 print $fg "menuentry $title {\n";
288 print $fg "$prolog\n" if $prolog;
290 foreach (@$modules_ref) {
293 my ($kbin, $kcmd) = split(' ', $_, 2);
294 $kcmd = '' if !defined $kcmd;
295 print $fg " multiboot ${base}$kbin $kcmd\n";
298 # GRUB2 doesn't pass filename in multiboot info so we have to duplicate it here
299 $_ = join(' ', ($args[0], @args));
300 s|\brom://|$rom_prefix|g; # We do not need to translate path for GRUB2
301 print $fg " module $base$_\n";
306 print("novaboot: Created $builddir/$filename\n");
310 sub generate_pulsar_config($$)
312 my ($filename, $modules_ref) = @_;
313 open(my $fg, '>', $filename) or die "$filename: $!";
314 print $fg "root $pulsar_root\n" if defined $pulsar_root;
317 foreach (@$modules_ref) {
320 ($kbin, $kcmd) = split(' ', $_, 2);
321 $kcmd = '' if !defined $kcmd;
324 s|\brom://|$rom_prefix|g;
325 print $fg "load $_\n";
328 # Put kernel as last - this is needed for booting Linux and has no influence on non-Linux OSes
329 print $fg "exec $kbin $kcmd\n";
331 print("novaboot: Created $builddir/$filename\n");
335 sub shell_cmd_string(@)
337 return join(' ', map((/^[-_=a-zA-Z0-9\/\.\+]+$/ ? "$_" : "'$_'"), @_));
342 print "novaboot: Running: ".shell_cmd_string(@_)."\n";
346 sub system_verbose($)
349 print "novaboot: Running: $cmd\n";
350 my $ret = system($cmd);
351 if ($ret & 0x007f) { die("Command terminated by a signal"); }
352 if ($ret & 0xff00) {die("Command exit with non-zero exit code"); }
353 if ($ret) { die("Command failure $ret"); }
358 if (exists $variables->{WVDESC}) {
359 print "Testing \"$variables->{WVDESC}\" in $last_fn:\n";
360 } elsif ($last_fn =~ /\.wv$/) {
361 print "Testing \"all\" in $last_fn:\n";
364 ## Connect to the target and check whether is not occupied
366 # We have to do this before file generation phase, because file
367 # generation is intermixed with file deployment phase and we want to
368 # check whether the target is not used by somebody else before
369 # deploying files. Otherwise, we may rewrite other user's files on a
372 my $exp; # Expect object to communicate with the target over serial line
374 my ($target_reset, $target_power_on, $target_power_off);
376 if (defined $iprelay) {
378 $iprelay =~ /([.0-9]+)(:([0-9]+))?/;
381 my $paddr = sockaddr_in($port, inet_aton($addr));
382 my $proto = getprotobyname('tcp');
383 socket($IPRELAY, PF_INET, SOCK_STREAM, $proto) || die "socket: $!";
384 print "novaboot: Connecting to IP relay... ";
385 connect($IPRELAY, $paddr) || die "connect: $!";
387 $exp = Expect->init(\*$IPRELAY);
391 print $exp "\xFF\xF6"; # AYT
392 my $connected = $exp->expect(20, # Timeout in seconds
393 '<iprelayd: connected>',
394 '-re', '<WEB51 HW[^>]*>');
399 my ($relay, $onoff) = @_;
400 die unless ($relay == 1 || $relay == 2);
402 my $cmd = ($relay == 1 ? 0x5 : 0x6) | ($onoff ? 0x20 : 0x10);
403 return "\xFF\xFA\x2C\x32".chr($cmd)."\xFF\xF0";
407 my ($relay, $onoff) = @_;
408 die unless ($relay == 1 || $relay == 2);
409 my $cmd = ($relay == 1 ? 0xdf : 0xbf) | ($onoff ? 0x00 : 0xff);
410 return "\xFF\xFA\x2C\x97".chr($cmd)."\xFF\xF0";
414 my ($relay, $onoff, $can_giveup) = @_;
415 my $confirmation = '';
417 print $exp relaycmd($relay, $onoff);
418 my $confirmed = $exp->expect(20, # Timeout in seconds
419 relayconf($relay, $onoff));
422 print("Relay confirmation timeout - ignoring\n");
424 die "Relay confirmation timeout";
430 $target_reset = sub {
431 relay(2, 1, 1); # Reset the machine
436 $target_power_off = sub {
437 relay(1, 1); # Press power button
438 usleep(6000000); # Long press to switch off
442 $target_power_on = sub {
443 relay(1, 1); # Press power button
444 usleep(100000); # Short press
450 system_verbose("stty -F $serial $stty");
451 open($CONN, "+<", $serial) || die "open $serial: $!";
452 $exp = Expect->init(\*$CONN);
455 $exp = new Expect(); # Make $exp ready for calling $exp->spawn() later
458 if (defined $on_opt && defined $target_power_on) {
462 if (defined $off_opt && defined $target_power_off) {
463 print "novaboot: Switching the target off...\n";
464 &$target_power_off();
468 $builddir ||= dirname(File::Spec->rel2abs( ${$scripts[0]}{filename})) if scalar @scripts;
469 if (defined $builddir) {
470 chdir($builddir) or die "Can't change directory to $builddir: $!";
471 print "novaboot: Entering directory `$builddir'\n";
474 ## File generation phase
475 my (%files_iso, $menu_iso, $filename);
476 my $config_name = '';
478 foreach my $script (@scripts) {
479 $filename = $$script{filename};
480 $modules = $$script{modules};
481 $generated = $$script{generated};
482 $variables = $$script{variables};
484 ($config_name = $filename) =~ s#.*/##;
485 $config_name = $config_name_opt if (defined $config_name_opt);
487 if (exists $variables->{BUILDDIR}) {
488 $builddir = File::Spec->rel2abs($variables->{BUILDDIR});
489 chdir($builddir) or die "Can't change directory to $builddir: $!";
490 print "novaboot: Entering directory `$builddir'\n";
494 if (exists $variables->{KERNEL}) {
495 $kernel = $variables->{KERNEL};
497 if ($CFG::hypervisor) {
498 $kernel = $CFG::hypervisor . " ";
499 if (exists $variables->{HYPERVISOR_PARAMS}) {
500 $kernel .= $variables->{HYPERVISOR_PARAMS};
502 $kernel .= $CFG::hypervisor_params;
506 @$modules = ($kernel, @$modules) if $kernel;
507 @$modules = (@chainloaders, @$modules);
508 @$modules = ("bin/boot/bender", @$modules) if ($bender || defined $ENV{'NOVABOOT_BENDER'});
511 ($prefix = $grub_prefix) =~ s/\$NAME/$config_name/ if defined $grub_prefix;
512 $prefix ||= $builddir;
513 # TODO: use $grub_prefix as first parameter if some switch is given
514 generate_configs('', $generated, $filename);
516 ### Generate bootloader configuration files
517 my @bootloader_configs;
518 push @bootloader_configs, generate_grub_config($grub_config, $config_name, $prefix, $modules, $grub_preamble) if (defined $grub_config);
519 push @bootloader_configs, generate_grub2_config($grub2_config, $config_name, $prefix, $modules, $grub_preamble, $grub2_prolog) if (defined $grub2_config);
520 push @bootloader_configs, generate_pulsar_config('config-'.($pulsar||'novaboot'), $modules) if (defined $pulsar);
523 if (defined $scons) {
524 my @files = map({ ($file) = m/([^ ]*)/; $file; } @$modules);
525 # Filter-out generated files
526 my @to_build = grep({ my $file = $_; !scalar(grep($file eq $$_{filename}, @$generated)) } @files);
527 system_verbose($scons || $CFG::scons." ".join(" ", @to_build));
530 ### Copy files (using rsync)
531 if (defined $server && !defined($gen_only)) {
532 (my $real_server = $server) =~ s/\$NAME/$config_name/;
534 my ($hostname, $path) = split(":", $real_server, 2);
535 if (! defined $path) {
539 my $files = join(" ", map({ ($file) = m/([^ ]*)/; $file; } ( @$modules, @bootloader_configs)));
540 map({ my $file = (split)[0]; die "$file: $!" if ! -f $file; } @$modules);
541 my $istty = -t STDOUT && ($ENV{'TERM'} || 'dumb') ne 'dumb';
542 my $progress = $istty ? "--progress" : "";
543 system_verbose("rsync $progress -RLp $rsync_flags $files $real_server");
544 if ($server =~ m|/\$NAME$| && $concat) {
545 my $cmd = join("; ", map { "( cd $path/.. && cat */$_ > $_ )" } @bootloader_configs);
546 system_verbose($hostname ? "ssh $hostname '$cmd'" : $cmd);
550 ### Prepare ISO image generation
551 if (defined $iso_image) {
552 generate_configs("(cd)", $generated, $filename);
554 generate_grub_config(\$menu, $config_name, "(cd)", $modules);
555 $menu_iso .= "$menu\n";
556 map { ($file,undef) = split; $files_iso{$file} = 1; } @$modules;
560 ## Generate ISO image
561 if (defined $iso_image) {
562 open(my $fh, ">menu-iso.lst");
563 print $fh "timeout 5\n\n$menu_iso";
565 my $files = "boot/grub/menu.lst=menu-iso.lst " . join(" ", map("$_=$_", keys(%files_iso)));
566 $iso_image ||= "$config_name.iso";
567 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");
568 print("ISO image created: $builddir/$iso_image\n");
571 exit(0) if defined $gen_only;
573 ## Boot the system using various methods and send serial output to stdout
575 if (scalar(@scripts) > 1 && ( defined $dhcp_tftp || defined $serial || defined $iprelay)) {
576 die "You cannot do this with multiple scripts simultaneously";
579 if ($variables->{WVTEST_TIMEOUT}) {
580 print "wvtest: timeout ", $variables->{WVTEST_TIMEOUT}, "\n";
585 $str =~ s/^\s+|\s+$//g;
591 if (!(defined $dhcp_tftp || defined $serial || defined $iprelay || defined $server || defined $iso_image)) {
593 $qemu ||= $variables->{QEMU} || $CFG::qemu;
594 my @qemu_flags = split(" ", $qemu);
595 $qemu = shift(@qemu_flags);
597 @qemu_flags = split(/ +/, trim($variables->{QEMU_FLAGS})) if exists $variables->{QEMU_FLAGS};
598 @qemu_flags = split(/ +/, trim($qemu_flags_cmd)) if $qemu_flags_cmd;
599 push(@qemu_flags, split(/ +/, trim($qemu_append || '')));
601 if (defined $iso_image) {
602 # Boot NOVA with grub (and test the iso image)
603 push(@qemu_flags, ('-cdrom', "$config_name.iso"));
605 # Boot NOVA without GRUB
607 # Non-patched qemu doesn't like commas, but NUL can live with pluses instead of commans
608 foreach (@$modules) {s/,/+/g;}
609 generate_configs("", $generated, $filename);
611 if (scalar @$modules) {
612 my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);
613 $kcmd = '' if !defined $kcmd;
615 @$modules = map { if (/\.dtb$/) { $dtb=$_; (); } else { $_ } } @$modules;
616 my $initrd = join ",", @$modules;
618 push(@qemu_flags, ('-kernel', $kbin, '-append', $kcmd));
619 push(@qemu_flags, ('-initrd', $initrd)) if $initrd;
620 push(@qemu_flags, ('-dtb', $dtb)) if $dtb;
623 push(@qemu_flags, qw(-serial stdio)); # Redirect serial output (for collecting test restuls)
624 unshift(@qemu_flags, ('-name', $config_name));
625 print "novaboot: Running: ".shell_cmd_string($qemu, @qemu_flags)."\n";
626 $exp->spawn(($qemu, @qemu_flags)) || die("exec() failed: $!");
629 ### Local DHCPD and TFTPD
631 my ($dhcpd_pid, $tftpd_pid);
633 if (defined $dhcp_tftp)
635 generate_configs("(nd)", $generated, $filename);
636 system_verbose('mkdir -p tftpboot');
637 generate_grub_config("tftpboot/os-menu.lst", $config_name, "(nd)", \@$modules, "timeout 0");
638 open(my $fh, '>', 'dhcpd.conf');
639 my $mac = `cat /sys/class/net/eth0/address`;
641 print $fh "subnet 10.23.23.0 netmask 255.255.255.0 {
642 range 10.23.23.10 10.23.23.100;
643 filename \"bin/boot/grub/pxegrub.pxe\";
644 next-server 10.23.23.1;
647 hardware ethernet $mac;
648 fixed-address 10.23.23.1;
651 system_verbose("sudo ip a add 10.23.23.1/24 dev eth0;
652 sudo ip l set dev eth0 up;
653 sudo touch dhcpd.leases");
656 if ($dhcpd_pid == 0) {
657 # This way, the spawned server are killed when this script is killed.
658 exec_verbose("sudo dhcpd -d -cf dhcpd.conf -lf dhcpd.leases -pf dhcpd.pid");
661 if ($tftpd_pid == 0) {
662 exec_verbose("sudo in.tftpd --foreground --secure -v -v -v $builddir");
664 $SIG{TERM} = sub { print "CHILDS KILLED\n"; kill 15, $dhcpd_pid, $tftpd_pid; };
667 ### Serial line or IP relay
669 if (defined $target_reset) {
670 print "novaboot: Reseting the test box... ";
675 if (defined $uboot) {
676 print "novaboot: Waiting for uBoot prompt...\n";
679 [qr/Hit any key to stop autoboot:/, sub { $exp->send('.'); exp_continue; }],
680 '=> ') || die "No uBoot prompt deteceted";
682 $exp->send("dhcp\n");
683 $exp->expect(1, '=> ');
685 my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);
687 @$modules = map { print "DTBBBB $_\n"; if (/\.dtb$/) { $dtb=$_; (); } else { $_ } } @$modules;
688 my $initrd = shift @$modules;
690 my $kern_addr = '800000';
691 my $initrd_addr = '-';
695 $exp->send("tftp $kern_addr $kbin\n");
697 [qr/#/, sub { exp_continue; }],
698 '=> ') || die "Kernel load failed";
700 $dtb_addr = '7f0000';
701 $exp->send("tftp $dtb_addr $dtb\n");
703 [qr/#/, sub { exp_continue; }],
704 '=> ') || die "Device tree load failed";
706 if (defined $initrd) {
707 $initrd_addr = 'b00000';
708 $exp->send("tftp $initrd_addr $initrd\n");
710 [qr/#/, sub { exp_continue; }],
711 '=> ') || die "Initrd load failed";
713 $exp->send('mw f0000b00 ${psc_cfg}');
714 $exp->send("set bootargs $kcmd\n");
715 $exp->expect(1, '=> ');
716 $exp->send("bootm $kern_addr $initrd_addr $dtb_addr\n");
720 # Serial line of the target is available
721 print "novaboot: Serial line interaction (press Ctrl-C to interrupt)...\n";
723 $exp->interact(undef, "\cC"); # Interact until Ctrl-C is pressed
726 ### Wait for dhcpc or tftpd
727 if (defined $dhcp_tftp) {
728 kill 15, $dhcpd_pid, $tftpd_pid;
730 if ($pid == $dhcpd_pid) { print "dhcpd exited!\n"; }
731 elsif ($pid == $tftpd_pid) { print "tftpd exited!\n"; }
732 else { print "wait returned: $pid\n"; }
733 kill(15, 0); # Kill current process group i.e. all remaining children
740 novaboot - A tool for booting various operating systems on various hardware or in qemu
744 B<novaboot> [ options ] [--] script...
746 B<./script> [ options ]
752 This program makes it easier to boot NOVA or other operating system
753 (OS) in different environments. It reads a so called novaboot script
754 and uses it either to boot the OS in an emulator (e.g. in qemu) or to
755 generate the configuration for a specific bootloader and optionally to
756 copy the necessary binaries and other needed files to proper
757 locations, perhaps on a remote server. In case the system is actually
758 booted, its serial output is redirected to standard output if that is
761 A typical way of using novaboot is to make the novaboot script
762 executable and set its first line to I<#!/usr/bin/env novaboot>. Then,
763 booting a particular OS configuration becomes the same as executing a
764 local program - the novaboot script.
766 With C<novaboot> you can:
772 Run an OS in Qemu. This is the default action when no other action is
773 specified by command line switches. Thus running C<novaboot ./script>
774 (or C<./script> as described above) will run Qemu and make it boot the
775 configuration specified in the I<script>.
779 Create a bootloader configuration file (currently supported
780 bootloaders are GRUB, GRUB2, Pulsar and uBoot) and copy it with all
781 other files needed for booting to another, perhaps remote, location.
783 ./script --server --iprelay=192.168.1.2
785 This command copies files to a TFTP server specified in the
786 configuration file and uses TCP/IP-controlled relay to reset the test
787 box and receive its serial output.
791 Run DHCP and TFTP server on developer's machine to PXE-boot NOVA from
796 When a PXE-bootable machine is connected via Ethernet to developer's
797 machine, it will boot the configuration described in I<script>.
801 Create bootable ISO images. E.g.
803 novaboot --iso -- script1 script2
805 The created ISO image will have GRUB bootloader installed on it and
806 the boot menu will allow selecting between I<script1> and I<script2>
811 =head1 PHASES AND OPTIONS
813 Novaboot performs its work in several phases. Each phase can be
814 influenced by several options, certain phases can be skipped. The list
815 of phases (in the execution order) and the corresponding options
818 =head2 Configuration reading phase
820 After starting, novaboot reads configuration files. By default, it
821 searches for files named F<.novaboot> starting from the directory of
822 the novaboot script (or working directory, see bellow) and continuing
823 upwards up to the root directory. The configuration files are read in
824 order from the root directory downwards with latter files overriding
825 settings from the former ones.
827 In certain cases, the location of the novaboot script cannot be
828 determined in this early phase. This happens either when the script is
829 read from the standard input or when novaboot is invoked explicitly
830 and options precede the script name, as in the example L</"4."> above.
831 In this case the current working directory is used as a starting point
832 for configuration file search.
836 =item -c, --config=<filename>
838 Use the specified configuration file instead of the default one(s).
842 =head2 Command line processing phase
848 Dump the current configuration to stdout end exits. Useful as an
849 initial template for a configuration file.
853 Print short (B<-h>) or long (B<--help>) help.
855 =item -t, --target=<target>
857 This option serves as a user configurable shortcut for other novaboot
858 options. The effect of this option is the same as the options stored
859 in the C<%targets> configuration variable under key I<target>. See
860 also L</"CONFIGURATION FILE">.
864 =head2 Script preprocessing phase
866 This phases allows to modify the parsed novaboot script before it is
867 used in the later phases.
871 =item -a, --append=<parameters>
873 Appends a string to the first "filename" line in the novaboot script.
874 This can be used to append parameters to the kernel's or root task's
879 Use F<bender> chainloader. Bender scans the PCI bus for PCI serial
880 ports and stores the information about them in the BIOS data area for
883 =item --chainloader=<chainloader>
885 Chainloader that is loaded before the kernel and other files specified
886 in the novaboot script. E.g. 'bin/boot/bender promisc'.
890 Prints the content of the novaboot script after removing comments and
891 evaluating all I<--scriptmod> expressions. Exit after reading (and
894 =item --scriptmod=I<perl expression>
896 When novaboot script is read, I<perl expression> is executed for every
897 line (in $_ variable). For example, C<novaboot
898 --scriptmod=s/sigma0/omega6/g> replaces every occurrence of I<sigma0>
899 in the script with I<omega6>.
901 When this option is present, it overrides I<$script_modifier> variable
902 from the configuration file, which has the same effect. If this option
903 is given multiple times all expressions are evaluated in the command
908 Strip I<rom://> prefix from command lines and generated config files.
909 The I<rom://> prefix is used by NUL. For NRE, it has to be stripped.
913 =head2 File generation phase
915 In this phase, files needed for booting are generated in a so called
916 I<build directory> (see TODO). In most cases configuration for a
917 bootloader is generated automatically by novaboot. It is also possible
918 to generate other files using I<heredoc> or I<"<"> syntax in novaboot
919 scripts. Finally, binaries can be generated in this phases by running
924 =item --build-dir=<directory>
926 Overrides the default build directory location.
928 The default build directory location is determined as follows: If the
929 configuration file defines the C<$builddir> variable, its value is
930 used. Otherwise, it is the directory that contains the first processed
933 =item -g, --grub[=I<filename>]
935 Generates grub bootloader menu file. If the I<filename> is not
936 specified, F<menu.lst> is used. The I<filename> is relative to the
937 build directory (see B<--build-dir>).
939 =item --grub-preamble=I<prefix>
941 Specifies the I<preable> that is at the beginning of the generated
942 GRUB or GRUB2 config files. This is useful for specifying GRUB's
945 =item --grub-prefix=I<prefix>
947 Specifies I<prefix> that is put in front of every file name in GRUB's
948 F<menu.lst>. The default value is the absolute path to the build directory.
950 If the I<prefix> contains string $NAME, it will be replaced with the
951 name of the novaboot script (see also B<--name>).
953 =item --grub2[=I<filename>]
955 Generate GRUB2 menuentry in I<filename>. If I<filename> is not
956 specified F<grub.cfg> is used. The content of the menuentry can be
957 customized with B<--grub-preable>, B<--grub2-prolog> or
958 B<--grub_prefix> options.
960 In order to use the the generated menuentry on your development
961 machine that uses GRUB2, append the following snippet to
962 F</etc/grub.d/40_custom> file and regenerate your grub configuration,
963 i.e. run update-grub on Debian/Ubuntu.
965 if [ -f /path/to/nul/build/grub.cfg ]; then
966 source /path/to/nul/build/grub.cfg
969 =item --grub2-prolog=I<prolog>
971 Specifies text I<preable> that is put at the begiging of the entry
974 =item --name=I<string>
976 Use the name I<string> instead of the name of the novaboot script.
977 This name is used for things like a title of grub menu or for the
978 server directory where the boot files are copied to.
982 Do not generate files on the fly (i.e. "<" syntax) except for the
983 files generated via "<<WORD" syntax.
985 =item -p, --pulsar[=mac]
987 Generates pulsar bootloader configuration file named F<config-I<mac>>
988 The I<mac> string is typically a MAC address and defaults to
991 =item --scons[=scons command]
993 Runs I<scons> to build files that are not generated by novaboot
998 Exit novaboot after file generation phase.
1002 =head2 Target connection check
1004 If supported by the target, the connection to it is made and it is
1005 checked whether the target is not occupied by another novaboot
1008 =head2 File deployment phase
1010 In some setups, it is necessary to copy the files needed for booting
1011 to a particular location, e.g. to a TFTP boot server or to the
1016 =item -d, --dhcp-tftp
1018 Turns your workstation into a DHCP and TFTP server so that NOVA
1019 can be booted via PXE BIOS on a test machine directly connected by
1020 a plain Ethernet cable to your workstation.
1022 The DHCP and TFTP servers require root privileges and C<novaboot>
1023 uses C<sudo> command to obtain those. You can put the following to
1024 I</etc/sudoers> to allow running the necessary commands without
1025 asking for password.
1027 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 *, /usr/bin/touch dhcpd.leases
1028 your_login ALL=NOPASSWD: NOVABOOT
1030 =item -i, --iso[=filename]
1032 Generates the ISO image that boots NOVA system via GRUB. If no filename
1033 is given, the image is stored under I<NAME>.iso, where I<NAME> is the name
1034 of the novaboot script (see also B<--name>).
1036 =item --server[=[[user@]server:]path]
1038 Copy all files needed for booting to another location (implies B<-g>
1039 unless B<--grub2> is given). The files will be copied (by B<rsync>
1040 tool) to the directory I<path>. If the I<path> contains string $NAME,
1041 it will be replaced with the name of the novaboot script (see also
1046 If B<--server> is used and its value ends with $NAME, then after
1047 copying the files, a new bootloader configuration file (e.g. menu.lst)
1048 is created at I<path-wo-name>, i.e. the path specified by B<--server>
1049 with $NAME part removed. The content of the file is created by
1050 concatenating all files of the same name from all subdirectories of
1051 I<path-wo-name> found on the "server".
1053 =item --rsync-flags=I<flags>
1055 Specifies which I<flags> are appended to F<rsync> command line when
1056 copying files as a result of I<--server> option.
1060 =head2 Target power-on and reset phase
1064 =item --iprelay=I<addr[:port]>
1066 Use IP relay to reset the machine and to get the serial output. The IP
1067 address of the relay is given by I<addr> parameter.
1069 Note: This option is expected to work with HWG-ER02a IP relays.
1073 Switch on/off the target machine. Currently works only with
1076 =item -Q, --qemu=I<qemu-binary>
1078 The name of qemu binary to use. The default is 'qemu'.
1080 =item --qemu-append=I<flags>
1082 Append I<flags> to the default qemu flags (QEMU_FLAGS variable or
1083 C<-cpu coreduo -smp 2>).
1085 =item -q, --qemu-flags=I<flags>
1087 Replace the default qemu flags (QEMU_FLAGS variable or C<-cpu coreduo
1088 -smp 2>) with I<flags> specified here.
1092 =head2 Interaction with the bootloader on the target
1094 See B<--serial>. There will be new options soon.
1100 Interact with uBoot bootloader to boot the thing described in the
1101 novaboot script. Implementation of this option is currently tied to a
1102 particular board that we use. It may be subject to changes in the
1107 =head2 Target's output reception phase
1111 =item -s, --serial[=device]
1113 Use serial line to control GRUB bootloader and to see the output
1114 serial output of the machine. The default value is F</dev/ttyUSB0>.
1116 =item --stty=<settings>
1118 Specifies settings passed to C<stty> invoked on the serial line
1119 specified with B<--serial>. If this option is not given, C<stty> is
1120 called with C<raw -crtscts -onlcr 115200> settings.
1124 See also B<--iprelay>.
1126 =head2 Termination phase
1128 Daemons that were spwned (F<dhcpd> and F<tftpd>) are killed here.
1130 =head1 NOVABOOT SCRIPT SYNTAX
1132 The syntax tries to mimic POSIX shell syntax. The syntax is defined with the following rules.
1134 Lines starting with "#" are ignored.
1136 Lines that end with "\" are concatenated with the following line after
1137 removal of the final "\" and leading whitespace of the following line.
1139 Lines in the form I<VARIABLE=...> (i.e. matching '^[A-Z_]+=' regular
1140 expression) assign values to internal variables. See VARIABLES
1143 Otherwise, the first word on the line represents the filename
1144 (relative to the build directory (see B<--build-dir>) of the module to
1145 load and the remaining words are passed as the command line
1148 When the line ends with "<<WORD" then the subsequent lines until the
1149 line containing only WORD are copied literally to the file named on
1152 When the line ends with "< CMD" the command CMD is executed with
1153 C</bin/sh> and its standard output is stored in the file named on that
1154 line. The SRCDIR variable in CMD's environment is set to the absolute
1155 path of the directory containing the interpreted novaboot script.
1158 #!/usr/bin/env novaboot
1159 WVDESC=Example program
1160 bin/apps/sigma0.nul S0_DEFAULT script_start:1,1 \
1161 verbose hostkeyb:0,0x60,1,12,2
1163 hello.nulconfig <<EOF
1164 sigma0::mem:16 name::/s0/log name::/s0/timer name::/s0/fs/rom ||
1165 rom://bin/apps/hello.nul
1168 This example will load three modules: sigma0.nul, hello.nul and
1169 hello.nulconfig. sigma0 gets some command line parameters and
1170 hello.nulconfig file is generated on the fly from the lines between
1175 The following variables are interpreted in the novaboot script:
1181 Novaboot chdir()s to this directory before file generation phase. The
1182 directory name specified here is relative to the build directory
1183 specified by other means (see L</--build-dir>).
1187 Description of the wvtest-compliant program.
1189 =item WVTEST_TIMEOUT
1191 The timeout in seconds for WvTest harness. If no complete line appears
1192 in the test output within the time specified here, the test fails. It
1193 is necessary to specify this for long running tests that produce no
1194 intermediate output.
1198 Use a specific qemu binary (can be overriden with B<-Q>) and flags
1199 when booting this script under qemu. If QEMU_FLAGS variable is also
1200 specified flags specified in QEMU variable are replaced by those in
1205 Use specific qemu flags (can be overriden with B<-q>).
1207 =item HYPERVISOR_PARAMS
1209 Parameters passed to hypervisor. The default value is "serial", unless
1210 overriden in configuration file.
1214 The kernel to use instead of NOVA hypervisor specified in the
1215 configuration file. The value should contain the name of the kernel
1216 image as well as its command line parameters. If this variable is
1217 defined and non-empty, the variable HYPERVISOR_PARAMS is not used.
1221 =head1 CONFIGURATION FILE
1223 Novaboot can read its configuration from a file. Configuration file
1224 was necessary in early days of novaboot. Nowadays, an attempt is made
1225 to not use the configuration file because it makes certain novaboot
1226 scripts unusable on systems without (or with different) configuration
1227 file. The only recommended use of the configuration file is to specify
1228 custom_options (see bellow).
1230 If you decide to use the configuration file, it is looked up, by
1231 default, in files named F<.novaboot> as described in L</Configuration
1232 reading phase>. Alternatively, its location can be specified with the
1233 B<-c> switch or with the NOVABOOT_CONFIG environment variable. The
1234 configuration file has perl syntax and should set values of certain
1235 Perl variables. The current configuration can be dumped with the
1236 B<--dump-config> switch. Some configuration variables can be overriden
1237 by environment variables (see below) or by command line switches.
1239 Documentation of some configuration variables follows:
1245 Build directory location relative to the location of the configuration
1250 Hash of shortcuts to be used with the B<--target> option. If the hash
1251 contains, for instance, the following pair of values
1253 'mybox' => '--server=boot:/tftproot --serial=/dev/ttyUSB0 --grub',
1255 then the following two commands are equivalent:
1257 ./script --server=boot:/tftproot --serial=/dev/ttyUSB0 --grub
1262 =head1 ENVIRONMENT VARIABLES
1264 Some options can be specified not only via config file or command line
1265 but also through environment variables. Environment variables override
1266 the values from configuration file and command line parameters
1267 override the environment variables.
1271 =item NOVABOOT_CONFIG
1273 Name of the novaboot configuration file to use instead of the default
1276 =item NOVABOOT_BENDER
1278 Defining this variable has the same meaning as B<--bender> option.
1284 Michal Sojka <sojka@os.inf.tu-dresden.de>