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 Getopt::Long qw(GetOptionsFromString);
23 use Time::HiRes("usleep");
27 use POSIX qw(:errno_h);
28 use Cwd qw(getcwd abs_path);
33 my $invocation_dir = getcwd();
35 chomp(my $gittop = `git rev-parse --show-toplevel 2>/dev/null`);
37 ## Configuration file handling
39 # Default configuration
40 $CFG::hypervisor = "";
41 $CFG::hypervisor_params = "serial";
42 $CFG::server = "erwin.inf.tu-dresden.de:boot/novaboot/\$NAME";
43 $CFG::grub_keys = ''; #"/novaboot\n\n/\$NAME\n\n";
44 $CFG::genisoimage = "genisoimage";
45 $CFG::iprelay_addr = '141.76.48.80:2324'; #'141.76.48.252';
47 @CFG::chainloaders = (); #('bin/boot/bender promisc');
48 $CFG::pulsar_root = '';
49 $CFG::pulsar_mac = '52-54-00-12-34-56';
51 "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',
52 "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=novaboot --iprelay=147.32.86.92:2324',
53 "localhost" => '--scriptmod=s/console=tty[A-Z0-9,]+// --server=/boot/novaboot/$NAME --grub2 --grub-prefix=/boot/novaboot/$NAME --grub2-prolog=" set root=\'(hd0,msdos1)\'"',
55 $CFG::scons = "scons -j2";
57 my @qemu_flags = qw(-cpu coreduo -smp 2);
61 package CFG; # Put config data into a separate namespace
66 die("ERROR: Failure compiling '$cfg' - $@");
67 } elsif (! defined($rc)) {
68 die("ERROR: Failure reading '$cfg' - $!");
70 die("ERROR: Failure processing '$cfg'");
73 print "novaboot: Read $cfg\n";
78 # We don't use $0 here, because it points to the novaboot itself and
79 # not to the novaboot script. The problem with this approach is that
80 # when a script is run as "novaboot <options> <script>" then $ARGV[0]
81 # contains the first option. Hence the -f check.
82 my $dir = abs_path($invocation_dir . ((-f $ARGV[0]) ? '/'.dirname($ARGV[0]) : ''));
83 while (-d $dir && $dir ne "/") {
84 push @cfgs, "$dir/.novaboot" if -r "$dir/.novaboot";
85 $dir = abs_path($dir."/..");
88 my $cfg = $ENV{'NOVABOOT_CONFIG'};
89 Getopt::Long::Configure(qw/no_ignore_case pass_through/);
90 GetOptions ("config|c=s" => \$cfg);
91 read_config($_) foreach $cfg or reverse @cfgs;
93 ## Command line handling
95 my ($append, $bender, $builddir, $concat, $config_name_opt, $dhcp_tftp, $dump_opt, $dump_config, $grub_config, $grub_prefix, $grub_preamble, $grub2_prolog, $grub2_config, $help, $iprelay, $iso_image, $man, $no_file_gen, $off_opt, $on_opt, $pulsar, $qemu, $qemu_append, $qemu_flags_cmd, $rom_prefix, $rsync_flags, @scriptmod, $scons, $serial, $server);
98 $rom_prefix = 'rom://';
100 Getopt::Long::Configure(qw/no_ignore_case no_pass_through/);
103 "append|a=s" => \$append,
104 "bender|b" => \$bender,
105 "build-dir=s" => \$builddir,
106 "concat" => \$concat,
107 "dhcp-tftp|d" => \$dhcp_tftp,
108 "dump" => \$dump_opt,
109 "dump-config" => \$dump_config,
110 "grub|g:s" => \$grub_config,
111 "grub-preamble=s"=> \$grub_preamble,
112 "grub-prefix=s" => \$grub_prefix,
113 "grub2:s" => \$grub2_config,
114 "grub2-prolog=s" => \$grub2_prolog,
115 "iprelay:s" => \$iprelay,
116 "iso|i:s" => \$iso_image,
117 "name=s" => \$config_name_opt,
118 "no-file-gen" => \$no_file_gen,
121 "pulsar|p:s" => \$pulsar,
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 = ''; },
131 "target|t=s" => sub { my ($opt_name, $opt_value) = @_;
132 exists $CFG::targets{$opt_value} or die("Unknown target '$opt_value' (valid targets are: ".join(", ", sort keys(%CFG::targets)).")");
133 GetOptionsFromString($CFG::targets{$opt_value}, %opt_spec); },
137 GetOptions %opt_spec or pod2usage(2);
138 pod2usage(1) if $help;
139 pod2usage(-exitstatus => 0, -verbose => 2) if $man;
141 ### Sanitize configuration
143 $CFG::iprelay_addr = $ENV{'NOVABOOT_IPRELAY'} if $ENV{'NOVABOOT_IPRELAY'};
144 if ($iprelay && $iprelay ne "on" && $iprelay ne "off") { $CFG::iprelay_addr = $iprelay; }
146 if (defined $config_name_opt && scalar(@ARGV) > 1) { die "You cannot use --name with multiple scripts"; }
148 if ($server) { $CFG::server = $server; }
149 if ($qemu) { $CFG::qemu = $qemu; }
153 $CFG::pulsar_mac = $pulsar;
156 if ($scons) { $CFG::scons = $scons; }
158 ### Dump sanitized configuration (if requested)
162 $Data::Dumper::Indent=0;
163 print "# This file is in perl syntax.\n";
164 foreach my $key(sort(keys(%CFG::))) { # See "Symbol Tables" in perlmod(1)
165 if (defined ${$CFG::{$key}}) { print Data::Dumper->Dump([${$CFG::{$key}}], ["*$key"]); }
166 if ( @{$CFG::{$key}}) { print Data::Dumper->Dump([\@{$CFG::{$key}}], ["*$key"]); }
167 if ( %{$CFG::{$key}}) { print Data::Dumper->Dump([\%{$CFG::{$key}}], ["*$key"]); }
174 if (defined $serial) {
175 $serial ||= "/dev/ttyUSB0";
178 if (defined $grub_config) {
179 $grub_config ||= "menu.lst";
182 if (defined $grub2_config) {
183 $grub2_config ||= "grub.cfg";
186 if ($on_opt) { $iprelay="on"; }
187 if ($off_opt) { $iprelay="off"; }
189 ## Parse the novaboot script(s)
195 my ($modules, $variables, $generated, $continuation);
197 if ($ARGV ne $last_fn) { # New script
198 die "Missing EOF in $last_fn" if $file;
199 die "Unfinished line in $last_fn" if $line;
201 push @scripts, { 'filename' => $ARGV,
202 'modules' => $modules = [],
203 'variables' => $variables = {},
204 'generated' => $generated = []};
208 next if /^#/ || /^\s*$/; # Skip comments and empty lines
210 foreach my $mod(@scriptmod) { eval $mod; }
212 print "$_\n" if $dump_opt;
214 if (/^([A-Z_]+)=(.*)$/) { # Internal variable
215 $$variables{$1} = $2;
218 if (/^([^ ]*)(.*?)[[:space:]]*<<([^ ]*)$/) { # Heredoc start
219 push @$modules, "$1$2";
221 push @$generated, {filename => $1, content => $file};
225 if ($file && $_ eq $EOF) { # Heredoc end
229 if ($file) { # Heredoc content
230 push @{$file}, "$_\n";
233 $_ =~ s/^[[:space:]]*// if ($continuation);
234 if (/\\$/) { # Line continuation
235 $line .= substr($_, 0, length($_)-1);
241 $line .= " $append" if ($append && scalar(@$modules) == 0);
243 if ($line =~ /^([^ ]*)(.*?)[[:space:]]*< ?(.*)$/) { # Command substitution
244 push @$modules, "$1$2";
245 push @$generated, {filename => $1, command => $3};
249 push @$modules, $line;
253 #print Dumper(\@scripts);
259 sub generate_configs($$$) {
260 my ($base, $generated, $filename) = @_;
261 if ($base) { $base = "$base/"; };
262 foreach my $g(@$generated) {
263 if (exists $$g{content}) {
264 my $config = $$g{content};
265 my $fn = $$g{filename};
266 open(my $f, '>', $fn) || die("$fn: $!");
267 map { s|\brom://([^ ]*)|$rom_prefix$base$1|g; print $f "$_"; } @{$config};
269 print "novaboot: Created $fn\n";
270 } elsif (exists $$g{command} && ! $no_file_gen) {
271 $ENV{SRCDIR} = dirname(File::Spec->rel2abs( $filename, $invocation_dir ));
272 system_verbose("( $$g{command} ) > $$g{filename}");
277 sub generate_grub_config($$$$;$)
279 my ($filename, $title, $base, $modules_ref, $preamble) = @_;
280 if ($base) { $base = "$base/"; };
281 open(my $fg, '>', $filename) or die "$filename: $!";
282 print $fg "$preamble\n" if $preamble;
283 my $endmark = ($serial || defined $iprelay) ? ';' : '';
284 print $fg "title $title$endmark\n" if $title;
285 #print $fg "root $base\n"; # root doesn't really work for (nd)
287 foreach (@$modules_ref) {
290 my ($kbin, $kcmd) = split(' ', $_, 2);
291 $kcmd = '' if !defined $kcmd;
292 print $fg "kernel ${base}$kbin $kcmd\n";
294 s|\brom://([^ ]*)|$rom_prefix$base$1|g; # Translate rom:// files - needed for vdisk parameter of sigma0
295 print $fg "module $base$_\n";
299 print("novaboot: Created $CFG::builddir/$filename\n");
303 sub generate_grub2_config($$$$;$$)
305 my ($filename, $title, $base, $modules_ref, $preamble, $prolog) = @_;
306 if ($base && substr($base,-1,1) ne '/') { $base = "$base/"; };
307 open(my $fg, '>', $filename) or die "$filename: $!";
308 print $fg "$preamble\n" if $preamble;
309 my $endmark = ($serial || defined $iprelay) ? ';' : '';
310 $title ||= 'novaboot';
311 print $fg "menuentry $title$endmark {\n";
312 print $fg "$prolog\n" if $prolog;
314 foreach (@$modules_ref) {
317 my ($kbin, $kcmd) = split(' ', $_, 2);
318 $kcmd = '' if !defined $kcmd;
319 print $fg " multiboot ${base}$kbin $kcmd\n";
322 # GRUB2 doesn't pass filename in multiboot info so we have to duplicate it here
323 $_ = join(' ', ($args[0], @args));
324 s|\brom://|$rom_prefix|g; # We do not need to translate path for GRUB2
325 print $fg " module $base$_\n";
330 print("novaboot: Created $CFG::builddir/$filename\n");
334 sub generate_pulsar_config($$)
336 my ($filename, $modules_ref) = @_;
337 open(my $fg, '>', $filename) or die "$filename: $!";
338 print $fg "root $CFG::pulsar_root\n" if $CFG::pulsar_root;
341 foreach (@$modules_ref) {
344 ($kbin, $kcmd) = split(' ', $_, 2);
345 $kcmd = '' if !defined $kcmd;
348 s|\brom://|$rom_prefix|g;
349 print $fg "load $_\n";
352 # Put kernel as last - this is needed for booting Linux and has no influence on non-Linux OSes
353 print $fg "exec $kbin $kcmd\n";
355 print("novaboot: Created $CFG::builddir/$filename\n");
361 print "novaboot: Running: ".join(' ', map("'$_'", @_))."\n";
365 sub system_verbose($)
368 print "novaboot: Running: $cmd\n";
369 my $ret = system($cmd);
370 if ($ret & 0x007f) { die("Command terminated by a signal"); }
371 if ($ret & 0xff00) {die("Command exit with non-zero exit code"); }
372 if ($ret) { die("Command failure $ret"); }
377 if (exists $variables->{WVDESC}) {
378 print "Testing \"$variables->{WVDESC}\" in $last_fn:\n";
379 } elsif ($last_fn =~ /\.wv$/) {
380 print "Testing \"all\" in $last_fn:\n";
383 ## Handle reset and power on/off
386 if (defined $iprelay) {
387 $CFG::iprelay_addr =~ /([.0-9]+)(:([0-9]+))?/;
390 my $paddr = sockaddr_in($port, inet_aton($addr));
391 my $proto = getprotobyname('tcp');
392 socket($IPRELAY, PF_INET, SOCK_STREAM, $proto) || die "socket: $!";
393 print "novaboot: Connecting to IP relay... ";
394 connect($IPRELAY, $paddr) || die "connect: $!";
396 $IPRELAY->autoflush(1);
399 print $IPRELAY "\xFF\xF6";
401 local $SIG{ALRM} = sub { die "Relay AYT timeout"; };
402 my $ayt_reponse = "";
403 my $read = sysread($IPRELAY, $ayt_reponse, 100);
407 print "$ayt_reponse\n";
408 if ($ayt_reponse =~ /<iprelayd: not connected/) {
416 my ($relay, $onoff) = @_;
417 die unless ($relay == 1 || $relay == 2);
419 my $cmd = ($relay == 1 ? 0x5 : 0x6) | ($onoff ? 0x20 : 0x10);
420 return "\xFF\xFA\x2C\x32".chr($cmd)."\xFF\xF0";
424 my ($relay, $onoff) = @_;
425 die unless ($relay == 1 || $relay == 2);
426 my $cmd = ($relay == 1 ? 0xdf : 0xbf) | ($onoff ? 0x00 : 0xff);
427 return "\xFF\xFA\x2C\x97".chr($cmd)."\xFF\xF0";
431 my ($relay, $onoff, $can_giveup) = @_;
432 my $confirmation = '';
433 print $IPRELAY relaycmd($relay, $onoff);
435 # We use non-blocking I/O and polling here because for some
436 # reason read() on blocking FD returns only after all
437 # requested data is available. If we get during the first
438 # read() only a part of confirmation, we may get the rest
439 # after the system boots and print someting, which may be too
441 $IPRELAY->blocking(0);
443 alarm(20); # Timeout in seconds
445 local $SIG{ALRM} = sub {
446 if ($can_giveup) { print("Relay confirmation timeout - ignoring\n"); $giveup = 1;}
447 else {die "Relay confirmation timeout";}
450 while (($index=index($confirmation, relayconf($relay, $onoff))) < 0 && !$giveup) {
451 my $read = read($IPRELAY, $confirmation, 70, length($confirmation));
452 if (!defined($read)) {
453 die("IP relay: $!") unless $! == EAGAIN;
457 #use MIME::QuotedPrint;
458 #print "confirmation = ".encode_qp($confirmation)."\n";
461 $IPRELAY->blocking(1);
465 if ($iprelay && ($iprelay eq "on" || $iprelay eq "off")) {
466 relay(1, 1); # Press power button
467 if ($iprelay eq "on") {
468 usleep(100000); # Short press
470 usleep(6000000); # Long press to switch off
472 print $IPRELAY relay(1, 0);
476 ## Figure out the location of builddir and chdir() there
478 $CFG::builddir = $builddir;
480 if (! defined $CFG::builddir) {
481 $CFG::builddir = ( $gittop || $ENV{'HOME'}."/nul" ) . "/build";
482 if (! -d $CFG::builddir) {
483 $CFG::builddir = $ENV{SRCDIR} = dirname(File::Spec->rel2abs( ${$scripts[0]}{filename}, $invocation_dir ));
488 chdir($CFG::builddir) or die "Can't change directory to $CFG::builddir: $!";
489 print "novaboot: Entering directory `$CFG::builddir'\n";
491 ## File generation phase
492 my (%files_iso, $menu_iso, $config_name, $filename);
494 foreach my $script (@scripts) {
495 $filename = $$script{filename};
496 $modules = $$script{modules};
497 $generated = $$script{generated};
498 $variables = $$script{variables};
500 ($config_name = $filename) =~ s#.*/##;
501 $config_name = $config_name_opt if (defined $config_name_opt);
504 if (exists $variables->{KERNEL}) {
505 $kernel = $variables->{KERNEL};
507 if ($CFG::hypervisor) {
508 $kernel = $CFG::hypervisor . " ";
509 if (exists $variables->{HYPERVISOR_PARAMS}) {
510 $kernel .= $variables->{HYPERVISOR_PARAMS};
512 $kernel .= $CFG::hypervisor_params;
516 @$modules = ($kernel, @$modules) if $kernel;
517 @$modules = (@CFG::chainloaders, @$modules);
518 @$modules = ("bin/boot/bender", @$modules) if ($bender || defined $ENV{'NOVABOOT_BENDER'});
521 ($prefix = $grub_prefix) =~ s/\$NAME/$config_name/ if defined $grub_prefix;
522 $prefix ||= $CFG::builddir;
523 # TODO: use $grub_prefix as first parameter if some switch is given
524 generate_configs('', $generated, $filename);
526 ### Generate bootloader configuration files
528 my @bootloader_configs;
529 push @bootloader_configs, generate_grub_config($grub_config, $config_name, $prefix, $modules, $grub_preamble) if (defined $grub_config);
530 push @bootloader_configs, generate_grub2_config($grub2_config, $config_name, $prefix, $modules, $grub_preamble, $grub2_prolog) if (defined $grub2_config);
531 push @bootloader_configs, generate_pulsar_config($pulsar_config = "config-$CFG::pulsar_mac", $modules) if (defined $pulsar);
534 if (defined $scons) {
535 my @files = map({ ($file) = m/([^ ]*)/; $file; } @$modules);
536 # Filter-out generated files
537 my @to_build = grep({ my $file = $_; !scalar(grep($file eq $$_{filename}, @$generated)) } @files);
538 system_verbose($CFG::scons." ".join(" ", @to_build));
541 ### Copy files (using rsync)
542 if (defined $server) {
543 ($server = $CFG::server) =~ s/\$NAME/$config_name/;
545 my ($hostname, $path) = split(":", $server, 2);
546 if (! defined $path) {
550 my $files = join(" ", map({ ($file) = m/([^ ]*)/; $file; } ( @$modules, @bootloader_configs)));
551 map({ my $file = (split)[0]; die "$file: $!" if ! -f $file; } @$modules);
552 my $istty = -t STDOUT && ($ENV{'TERM'} || 'dumb') ne 'dumb';
553 my $progress = $istty ? "--progress" : "";
554 system_verbose("rsync $progress -RLp $rsync_flags $files $server");
555 if ($CFG::server =~ m|/\$NAME$| && $concat) {
556 my $cmd = join("; ", map { "( cd $path/.. && cat */$_ > $_ )" } @bootloader_configs);
557 system_verbose($hostname ? "ssh $hostname '$cmd'" : $cmd);
561 ### Prepare ISO image generation
562 if (defined $iso_image) {
563 generate_configs("(cd)", $generated, $filename);
565 generate_grub_config(\$menu, $config_name, "(cd)", $modules);
566 $menu_iso .= "$menu\n";
567 map { ($file,undef) = split; $files_iso{$file} = 1; } @$modules;
571 ## Generate ISO image
572 if (defined $iso_image) {
573 open(my $fh, ">menu-iso.lst");
574 print $fh "timeout 5\n\n$menu_iso";
576 my $files = "boot/grub/menu.lst=menu-iso.lst " . join(" ", map("$_=$_", keys(%files_iso)));
577 $iso_image ||= "$config_name.iso";
578 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");
579 print("ISO image created: $CFG::builddir/$iso_image\n");
582 ## Boot the system using various methods and send serial output to stdout
584 if (scalar(@scripts) > 1 && ( defined $dhcp_tftp || defined $serial || defined $iprelay)) {
585 die "You cannot do this with multiple scripts simultaneously";
588 if ($variables->{WVTEST_TIMEOUT}) {
589 print "wvtest: timeout ", $variables->{WVTEST_TIMEOUT}, "\n";
594 $str =~ s/^\s+|\s+$//g;
600 if (!(defined $dhcp_tftp || defined $serial || defined $iprelay || defined $server || defined $iso_image)) {
602 if (!$qemu && $variables->{QEMU}) {
603 @qemu_flags = split(" ", $variables->{QEMU});
604 $CFG::qemu = shift(@qemu_flags);
607 @qemu_flags = split(/ +/, trim($variables->{QEMU_FLAGS})) if exists $variables->{QEMU_FLAGS};
608 @qemu_flags = split(/ +/, trim($qemu_flags_cmd)) if $qemu_flags_cmd;
609 push(@qemu_flags, split(/ +/, trim($qemu_append)));
611 if (defined $iso_image) {
612 # Boot NOVA with grub (and test the iso image)
613 push(@qemu_flags, ('-cdrom', "$config_name.iso"));
615 # Boot NOVA without GRUB
617 # Non-patched qemu doesn't like commas, but NUL can live with pluses instead of commans
618 foreach (@$modules) {s/,/+/g;}
619 generate_configs("", $generated, $filename);
621 my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);
622 $kcmd = '' if !defined $kcmd;
624 @$modules = map { if (/\.dtb$/) { $dtb=$_; (); } else { $_ } } @$modules;
625 my $initrd = join ",", @$modules;
627 push(@qemu_flags, ('-kernel', $kbin, '-append', $kcmd));
628 push(@qemu_flags, ('-initrd', $initrd)) if $initrd;
629 push(@qemu_flags, ('-dtb', $dtb)) if $dtb;
631 push(@qemu_flags, qw(-serial stdio)); # Redirect serial output (for collecting test restuls)
632 exec_verbose(($CFG::qemu, '-name', $config_name, @qemu_flags));
635 ### Local DHCPD and TFTPD
637 my ($dhcpd_pid, $tftpd_pid);
639 if (defined $dhcp_tftp)
641 generate_configs("(nd)", $generated, $filename);
642 system_verbose('mkdir -p tftpboot');
643 generate_grub_config("tftpboot/os-menu.lst", $config_name, "(nd)", \@$modules, "timeout 0");
644 open(my $fh, '>', 'dhcpd.conf');
645 my $mac = `cat /sys/class/net/eth0/address`;
647 print $fh "subnet 10.23.23.0 netmask 255.255.255.0 {
648 range 10.23.23.10 10.23.23.100;
649 filename \"bin/boot/grub/pxegrub.pxe\";
650 next-server 10.23.23.1;
653 hardware ethernet $mac;
654 fixed-address 10.23.23.1;
657 system_verbose("sudo ip a add 10.23.23.1/24 dev eth0;
658 sudo ip l set dev eth0 up;
659 sudo touch dhcpd.leases");
662 if ($dhcpd_pid == 0) {
663 # This way, the spawned server are killed when this script is killed.
664 exec_verbose("sudo dhcpd -d -cf dhcpd.conf -lf dhcpd.leases -pf dhcpd.pid");
667 if ($tftpd_pid == 0) {
668 exec_verbose("sudo in.tftpd --foreground --secure -v -v -v $CFG::builddir");
670 $SIG{TERM} = sub { print "CHILDS KILLED\n"; kill 15, $dhcpd_pid, $tftpd_pid; };
673 ### Serial line or IP relay
675 if ($serial || defined $iprelay) {
677 if (defined $iprelay) {
678 print "novaboot: Reseting the test box... ";
679 relay(2, 1, 1); # Reset the machine
686 system("stty -F $serial raw -crtscts -onlcr 115200");
687 open($CONN, "+<", $serial) || die "open $serial: $!";
690 if (!defined $dhcp_tftp && $CFG::grub_keys) {
691 # Control grub via serial line
692 print "Waiting for GRUB's serial output... ";
694 if (/Press any key to continue/) { print $CONN "\n"; last; }
696 $CFG::grub_keys =~ s/\$NAME/$config_name;/;
697 my @characters = split(//, $CFG::grub_keys);
698 foreach (@characters) {
700 usleep($_ eq "\n" ? 100000 : 10000);
705 # Pass the NOVA output to stdout.
709 kill 15, $dhcpd_pid, $tftpd_pid if ($dhcp_tftp);
713 ### Wait for dhcpc or tftpd
714 if (defined $dhcp_tftp) {
716 if ($pid == $dhcpd_pid) { print "dhcpd exited!\n"; }
717 elsif ($pid == $tftpd_pid) { print "tftpd exited!\n"; }
718 else { print "wait returned: $pid\n"; }
719 kill(15, 0); # Kill current process group i.e. all remaining children
726 novaboot - A tool for booting various operating systems on various hardware or in qemu
730 B<novaboot> [ options ] [--] script...
732 B<./script> [ options ]
738 This program makes it easier to boot NOVA or other operating system
739 (OS) in different environments. It reads a so called novaboot script
740 and uses it either to boot the OS in an emulator (e.g. in qemu) or to
741 generate the configuration for a specific bootloader and optionally to
742 copy the necessary binaries and other needed files to proper
743 locations, perhaps on a remote server. In case the system is actually
744 booted, its serial output is redirected to standard output if that is
747 A typical way of using novaboot is to make the novaboot script
748 executable and set its first line to I<#!/usr/bin/env novaboot>. Then,
749 booting a particular OS configuration becomes the same as executing a
750 local program - the novaboot script.
752 With C<novaboot> you can:
758 Run an OS in Qemu. This is the default action when no other action is
759 specified by command line switches. Thus running C<novaboot ./script>
760 (or C<./script> as described above) will run Qemu and make it boot the
761 configuration specified in the I<script>.
765 Create a bootloader configuration file (currently supported
766 bootloaders are GRUB, GRUB2 and Pulsar) and copy it with all other
767 files needed for booting to another, perhaps remote, location.
769 ./script --server --iprelay
771 This command copies files to a TFTP server specified in the
772 configuration file and uses TCP/IP-controlled relay to reset the test
773 box and receive its serial output.
777 Run DHCP and TFTP server on developer's machine to PXE-boot NOVA from
782 When a PXE-bootable machine is connected via Ethernet to developer's
783 machine, it will boot the configuration described in I<script>.
787 Create bootable ISO images. E.g.
789 novaboot --iso -- script1 script2
791 The created ISO image will have GRUB bootloader installed on it and
792 the boot menu will allow selecting between I<script1> and I<script2>
797 =head1 PHASES AND OPTIONS
799 Novaboot perform its work in several phases. Each phase can be
800 influenced by several options, certain phases can be skipped. The list
801 of phases with the corresponding options follows.
803 =head2 Configuration reading phase
805 After starting, novaboot reads configuration files. By default, it
806 searches for files named F<.novaboot> starting from the directory of
807 the novaboot script (or working directory, see bellow) and continuing
808 upwards up to the root directory. The configuration files are read in
809 order from the root directory downwards with latter files overriding
810 settings from the former ones.
812 In certain cases, the location of the novaboot script cannot be
813 determined in this early phase. This happens either when the script is
814 read from the standard input or when novaboot is invoked explicitly
815 and options precede the script name, as in the example L</"4."> above.
816 In this case the current working directory is used as a starting point
817 for configuration file search.
821 =item -c, --config=<filename>
823 Use the specified configuration file instead of the default one(s).
827 =head2 Command line processing phase
833 Dump the current configuration to stdout end exits. Useful as an
834 initial template for a configuration file.
838 Print short (B<-h>) or long (B<--help>) help.
840 =item -t, --target=<target>
842 This option serves as a user configurable shortcut for other novaboot
843 options. The effect of this option is the same as the options stored
844 in the C<%targets> configuration variable under key I<target>. See
845 also L</"CONFIGURATION FILE">.
849 =head2 Script preprocessing phase
851 This phases allows to modify the parsed novaboot script before it is
852 used in the later phases.
856 =item -a, --append=<parameters>
858 Appends a string to the first "filename" line in the novaboot script.
859 This can be used to append parameters to the kernel's or root task's
864 Use F<bender> chainloader. Bender scans the PCI bus for PCI serial
865 ports and stores the information about them in the BIOS data area for
870 Prints the content of the novaboot script after removing comments and
871 evaluating all I<--scriptmod> expressions. Exit after reading (and
874 =item --scriptmod=I<perl expression>
876 When novaboot script is read, I<perl expression> is executed for every
877 line (in $_ variable). For example, C<novaboot
878 --scriptmod=s/sigma0/omega6/g> replaces every occurrence of I<sigma0>
879 in the script with I<omega6>.
881 When this option is present, it overrides I<$script_modifier> variable
882 from the configuration file, which has the same effect. If this option
883 is given multiple times all expressions are evaluated in the command
888 Strip I<rom://> prefix from command lines and generated config files.
889 The I<rom://> prefix is used by NUL. For NRE, it has to be stripped.
893 =head2 File generation phase
895 In this phase, files needed for booting are generated in a so called
896 I<build directory> (see TODO). In most cases configuration for a
897 bootloader is generated automatically by novaboot. It is also possible
898 to generate other files using I<heredoc> or I<"<"> syntax in novaboot
899 scripts. Finally, binaries can be generated in this phases by running
904 =item --build-dir=<directory>
906 Overrides the default build directory location.
908 The default build directory location is determined as follows:
910 If there is a configuration file, the value specified in the
911 I<$builddir> variable is used. Otherwise, if the current working
912 directory is inside git work tree and there is F<build> directory at
913 the top of that tree, it is used. Otherwise, if directory
914 F<~/nul/build> exists, it is used. Otherwise, it is the directory that
915 contains the first processed novaboot script.
917 =item -g, --grub[=I<filename>]
919 Generates grub bootloader menu file. If the I<filename> is not
920 specified, F<menu.lst> is used. The I<filename> is relative to the
921 build directory (see B<--build-dir>).
923 =item --grub-preamble=I<prefix>
925 Specifies the I<preable> that is at the beginning of the generated
926 GRUB or GRUB2 config files. This is useful for specifying GRUB's
929 =item --grub-prefix=I<prefix>
931 Specifies I<prefix> that is put in front of every file name in GRUB's
932 F<menu.lst>. The default value is the absolute path to the build directory.
934 If the I<prefix> contains string $NAME, it will be replaced with the
935 name of the novaboot script (see also B<--name>).
937 =item --grub2[=I<filename>]
939 Generate GRUB2 menuentry in I<filename>. If I<filename> is not
940 specified F<grub.cfg> is used. The content of the menuentry can be
941 customized with B<--grub-preable>, B<--grub2-prolog> or
942 B<--grub_prefix> options.
944 In order to use the the generated menuentry on your development
945 machine that uses GRUB2, append the following snippet to
946 F</etc/grub.d/40_custom> file and regenerate your grub configuration,
947 i.e. run update-grub on Debian/Ubuntu.
949 if [ -f /path/to/nul/build/grub.cfg ]; then
950 source /path/to/nul/build/grub.cfg
953 =item --grub2-prolog=I<prolog>
955 Specifies text I<preable> that is put at the begiging of the entry
958 =item --name=I<string>
960 Use the name I<string> instead of the name of the novaboot script.
961 This name is used for things like a title of grub menu or for the
962 server directory where the boot files are copied to.
966 Do not generate files on the fly (i.e. "<" syntax) except for the
967 files generated via "<<WORD" syntax.
969 =item -p, --pulsar[=mac]
971 Generates pulsar bootloader configuration file whose name is based on
972 the MAC address specified either on the command line or taken from
973 I<.novaboot> configuration file.
977 =head2 File deployment phase
979 In some setups, it is necessary to copy the files needed for booting
980 to a particular location, e.g. to a TFTP boot server or to the
985 =item -d, --dhcp-tftp
987 Turns your workstation into a DHCP and TFTP server so that NOVA
988 can be booted via PXE BIOS on a test machine directly connected by
989 a plain Ethernet cable to your workstation.
991 The DHCP and TFTP servers require root privileges and C<novaboot>
992 uses C<sudo> command to obtain those. You can put the following to
993 I</etc/sudoers> to allow running the necessary commands without
996 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
997 your_login ALL=NOPASSWD: NOVABOOT
999 =item -i, --iso[=filename]
1001 Generates the ISO image that boots NOVA system via GRUB. If no filename
1002 is given, the image is stored under I<NAME>.iso, where I<NAME> is the name
1003 of the novaboot script (see also B<--name>).
1005 =item --server[=[[user@]server:]path]
1007 Copy all files needed for booting to another location (implies B<-g>
1008 unless B<--grub2> is given). The files will be copied (by B<rsync>
1009 tool) to the directory I<path>. If the I<path> contains string $NAME,
1010 it will be replaced with the name of the novaboot script (see also
1015 If B<--server> is used and its value ends with $NAME, then after
1016 copying the files, a new bootloader configuration file (e.g. menu.lst)
1017 is created at I<path-wo-name>, i.e. the path specified by B<--server>
1018 with $NAME part removed. The content of the file is created by
1019 concatenating all files of the same name from all subdirectories of
1020 I<path-wo-name> found on the "server".
1022 =item --rsync-flags=I<flags>
1024 Specifies which I<flags> are appended to F<rsync> command line when
1025 copying files as a result of I<--server> option.
1027 =item --scons[=scons command]
1029 Runs I<scons> to build files that are not generated by novaboot
1034 =head2 Target power-on and reset phase
1038 =item --iprelay[=addr or cmd]
1040 If no I<cmd> is given, use IP relay to reset the machine and to get
1041 the serial output. The IP address of the relay is given by I<addr>
1042 parameter if specified or by $iprelay_addr variable in the
1045 If I<cmd> is one of "on" or "off", the IP relay is used to press power
1046 button for a short (in case of "on") or long (in case of "off") time.
1047 Then, novaboot exits.
1049 Note: This option is expected to work with HWG-ER02a IP relays.
1053 Synonym for --iprelay=on/off.
1055 =item -Q, --qemu=I<qemu-binary>
1057 The name of qemu binary to use. The default is 'qemu'.
1059 =item --qemu-append=I<flags>
1061 Append I<flags> to the default qemu flags (QEMU_FLAGS variable or
1062 C<-cpu coreduo -smp 2>).
1064 =item -q, --qemu-flags=I<flags>
1066 Replace the default qemu flags (QEMU_FLAGS variable or C<-cpu coreduo
1067 -smp 2>) with I<flags> specified here.
1071 =head2 Interaction with the bootloader on the target
1073 See B<--serial>. There will be new options soon.
1075 =head2 Target's output reception phase
1079 =item -s, --serial[=device]
1081 Use serial line to control GRUB bootloader and to see the output
1082 serial output of the machine. The default value is F</dev/ttyUSB0>.
1086 See also B<--iprelay>.
1088 =head2 Termination phase
1090 Daemons that were spwned (F<dhcpd> and F<tftpd>) are killed here.
1092 =head1 NOVABOOT SCRIPT SYNTAX
1094 The syntax tries to mimic POSIX shell syntax. The syntax is defined with the following rules.
1096 Lines starting with "#" are ignored.
1098 Lines that end with "\" are concatenated with the following line after
1099 removal of the final "\" and leading whitespace of the following line.
1101 Lines in the form I<VARIABLE=...> (i.e. matching '^[A-Z_]+=' regular
1102 expression) assign values to internal variables. See VARIABLES
1105 Otherwise, the first word on the line represents the filename
1106 (relative to the build directory (see B<--build-dir>) of the module to
1107 load and the remaining words are passed as the command line
1110 When the line ends with "<<WORD" then the subsequent lines until the
1111 line containing only WORD are copied literally to the file named on
1114 When the line ends with "< CMD" the command CMD is executed with
1115 C</bin/sh> and its standard output is stored in the file named on that
1116 line. The SRCDIR variable in CMD's environment is set to the absolute
1117 path of the directory containing the interpreted novaboot script.
1120 #!/usr/bin/env novaboot
1121 WVDESC=Example program
1122 bin/apps/sigma0.nul S0_DEFAULT script_start:1,1 \
1123 verbose hostkeyb:0,0x60,1,12,2
1125 hello.nulconfig <<EOF
1126 sigma0::mem:16 name::/s0/log name::/s0/timer name::/s0/fs/rom ||
1127 rom://bin/apps/hello.nul
1130 This example will load three modules: sigma0.nul, hello.nul and
1131 hello.nulconfig. sigma0 gets some command line parameters and
1132 hello.nulconfig file is generated on the fly from the lines between
1137 The following variables are interpreted in the novaboot script:
1143 Description of the wvtest-compliant program.
1145 =item WVTEST_TIMEOUT
1147 The timeout in seconds for WvTest harness. If no complete line appears
1148 in the test output within the time specified here, the test fails. It
1149 is necessary to specify this for long running tests that produce no
1150 intermediate output.
1154 Use a specific qemu binary (can be overriden with B<-Q>) and flags
1155 when booting this script under qemu. If QEMU_FLAGS variable is also
1156 specified flags specified in QEMU variable are replaced by those in
1161 Use specific qemu flags (can be overriden with B<-q>).
1163 =item HYPERVISOR_PARAMS
1165 Parameters passed to hypervisor. The default value is "serial", unless
1166 overriden in configuration file.
1170 The kernel to use instead of NOVA hypervisor specified in the
1171 configuration file. The value should contain the name of the kernel
1172 image as well as its command line parameters. If this variable is
1173 defined and non-empty, the variable HYPERVISOR_PARAMS is not used.
1177 =head1 CONFIGURATION FILE
1179 Novaboot can read its configuration from a file. Configuration file
1180 was necessary in early days of novaboot. Nowadays, an attempt is made
1181 to not use the configuration file because it makes certain novaboot
1182 scripts unusable on systems without (or with different) configuration
1183 file. The only recommended use of the configuration file is to specify
1184 custom_options (see bellow).
1186 If you decide to use the configuration file, it is looked up, by
1187 default, in files named F<.novaboot> as described in L</Configuration
1188 reading phase>. Alternatively, its location can be specified with the
1189 B<-c> switch or with the NOVABOOT_CONFIG environment variable. The
1190 configuration file has perl syntax and should set values of certain
1191 Perl variables. The current configuration can be dumped with the
1192 B<--dump-config> switch. Some configuration variables can be overriden
1193 by environment variables (see below) or by command line switches.
1195 Documentation of some configuration variables follows:
1201 Custom chainloaders to load before hypervisor and files specified in
1202 novaboot script. E.g. ('bin/boot/bender promisc', 'bin/boot/zapp').
1206 Hash of shortcuts to be used with the B<--target> option. If the hash
1207 contains, for instance, the following pair of values
1209 'mybox' => '--server=boot:/tftproot --serial=/dev/ttyUSB0 --grub',
1211 then the following two commands are equivalent:
1213 ./script --server=boot:/tftproot --serial=/dev/ttyUSB0 --grub
1218 =head1 ENVIRONMENT VARIABLES
1220 Some options can be specified not only via config file or command line
1221 but also through environment variables. Environment variables override
1222 the values from configuration file and command line parameters
1223 override the environment variables.
1227 =item NOVABOOT_CONFIG
1229 Name of the novaboot configuration file to use instead of the default
1232 =item NOVABOOT_BENDER
1234 Defining this variable has the same meaning as B<--bender> option.
1236 =item NOVABOOT_IPRELAY
1238 The IP address (and optionally the port) of the IP relay. This
1239 overrides $iprelay_addr variable from the configuration file.
1245 Michal Sojka <sojka@os.inf.tu-dresden.de>