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);
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::server_grub_prefix = "(nd)/tftpboot/sojka/novaboot/\$NAME";
44 $CFG::grub_keys = ''; #"/novaboot\n\n/\$NAME\n\n";
45 $CFG::grub2_prolog = " set root='(hd0,msdos1)'";
46 $CFG::genisoimage = "genisoimage";
47 $CFG::iprelay_addr = '141.76.48.80:2324'; #'141.76.48.252';
49 $CFG::script_modifier = ''; # Depricated, use --scriptmod commandline option or custom_options.
50 @CFG::chainloaders = (); #('bin/boot/bender promisc');
51 $CFG::pulsar_root = '';
52 $CFG::pulsar_mac = '52-54-00-12-34-56';
53 %CFG::custom_options = ('I' => '--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-prefix=(nd)/tftpboot/sojka/novaboot --iprelay=141.76.48.80:2324 --scriptmod=s/\\\\bhostserial\\\\b/hostserialpci/g',
54 'J' => '--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');
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'");
75 my $cfg = $ENV{'NOVABOOT_CONFIG'} || $ENV{'HOME'}."/.novaboot";
76 Getopt::Long::Configure(qw/no_ignore_case pass_through/);
77 GetOptions ("config|c=s" => \$cfg);
78 if (-s $cfg) { read_config($cfg); }
80 ## Command line handling
82 my ($append, $bender, $builddir, $config_name_opt, $dhcp_tftp, $dump_opt, $dump_config, $grub_config, $grub_prefix, $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);
85 $rom_prefix = 'rom://';
87 Getopt::Long::Configure(qw/no_ignore_case no_pass_through/);
89 "append|a=s" => \$append,
90 "bender|b" => \$bender,
91 "build-dir=s" => \$builddir,
92 "dhcp-tftp|d" => \$dhcp_tftp,
94 "dump-config" => \$dump_config,
95 "grub|g:s" => \$grub_config,
96 "grub-prefix=s" => \$grub_prefix,
97 "grub2:s" => \$grub2_config,
98 "iprelay:s" => \$iprelay,
99 "iso|i:s" => \$iso_image,
100 "name=s" => \$config_name_opt,
101 "no-file-gen" => \$no_file_gen,
104 "pulsar|p:s" => \$pulsar,
105 "qemu|Q=s" => \$qemu,
106 "qemu-append:s" => \$qemu_append,
107 "qemu-flags|q=s" => \$qemu_flags_cmd,
108 "rsync-flags=s" => \$rsync_flags,
109 "scons:s" => \$scons,
110 "scriptmod=s" => \@scriptmod,
111 "serial|s:s" => \$serial,
112 "server:s" => \$server,
113 "strip-rom" => sub { $rom_prefix = ''; },
117 foreach my $opt(keys(%CFG::custom_options)) {
118 $opt_spec{$opt} = sub { GetOptionsFromString($CFG::custom_options{$opt}, %opt_spec); };
120 GetOptions %opt_spec or pod2usage(2);
121 pod2usage(1) if $help;
122 pod2usage(-exitstatus => 0, -verbose => 2) if $man;
124 ### Sanitize configuration
126 $CFG::iprelay_addr = $ENV{'NOVABOOT_IPRELAY'} if $ENV{'NOVABOOT_IPRELAY'};
127 if ($iprelay && $iprelay ne "on" && $iprelay ne "off") { $CFG::iprelay_addr = $iprelay; }
129 if (defined $config_name_opt && scalar(@ARGV) > 1) { die "You cannot use --name with multiple scripts"; }
131 if ($server) { $CFG::server = $server; }
132 if ($qemu) { $CFG::qemu = $qemu; }
136 $CFG::pulsar_mac = $pulsar;
139 if ($scons) { $CFG::scons = $scons; }
140 if (!@scriptmod && $CFG::script_modifier) { @scriptmod = ( $CFG::script_modifier ); }
141 if (defined $grub_prefix) { $CFG::server_grub_prefix = $grub_prefix; }
143 ### Dump sanitized configuration (if requested)
147 $Data::Dumper::Indent=0;
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 (defined @{$CFG::{$key}}) { print Data::Dumper->Dump([\@{$CFG::{$key}}], ["*$key"]); }
152 if ( %{$CFG::{$key}}) { print Data::Dumper->Dump([\%{$CFG::{$key}}], ["*$key"]); }
159 if (defined $serial) {
160 $serial ||= "/dev/ttyUSB0";
163 if (defined $grub_config) {
164 $grub_config ||= "menu.lst";
167 if (defined $grub2_config) {
168 $grub2_config ||= "grub.cfg";
171 if ($on_opt) { $iprelay="on"; }
172 if ($off_opt) { $iprelay="off"; }
174 ## Parse the novaboot script(s)
180 my ($modules, $variables, $generated, $continuation);
182 if ($ARGV ne $last_fn) { # New script
183 die "Missing EOF in $last_fn" if $file;
184 die "Unfinished line in $last_fn" if $line;
186 push @scripts, { 'filename' => $ARGV,
187 'modules' => $modules = [],
188 'variables' => $variables = {},
189 'generated' => $generated = []};
193 next if /^#/ || /^\s*$/; # Skip comments and empty lines
195 foreach my $mod(@scriptmod) { eval $mod; }
197 print "$_\n" if $dump_opt;
199 if (/^([A-Z_]+)=(.*)$/) { # Internal variable
200 $$variables{$1} = $2;
203 if (/^([^ ]*)(.*?)[[:space:]]*<<([^ ]*)$/) { # Heredoc start
204 push @$modules, "$1$2";
206 push @$generated, {filename => $1, content => $file};
210 if ($file && $_ eq $EOF) { # Heredoc end
214 if ($file) { # Heredoc content
215 push @{$file}, "$_\n";
218 $_ =~ s/^[[:space:]]*// if ($continuation);
219 if (/\\$/) { # Line continuation
220 $line .= substr($_, 0, length($_)-1);
226 $line .= " $append" if ($append && scalar(@$modules) == 0);
228 if ($line =~ /^([^ ]*)(.*?)[[:space:]]*< ?(.*)$/) { # Command substitution
229 push @$modules, "$1$2";
230 push @$generated, {filename => $1, command => $3};
234 push @$modules, $line;
238 #print Dumper(\@scripts);
244 sub generate_configs($$$) {
245 my ($base, $generated, $filename) = @_;
246 if ($base) { $base = "$base/"; };
247 foreach my $g(@$generated) {
248 if (exists $$g{content}) {
249 my $config = $$g{content};
250 my $fn = $$g{filename};
251 open(my $f, '>', $fn) || die("$fn: $!");
252 map { s|\brom://([^ ]*)|$rom_prefix$base$1|g; print $f "$_"; } @{$config};
254 print "novaboot: Created $fn\n";
255 } elsif (exists $$g{command} && ! $no_file_gen) {
256 $ENV{SRCDIR} = dirname(File::Spec->rel2abs( $filename, $invocation_dir ));
257 system_verbose("( $$g{command} ) > $$g{filename}");
262 sub generate_grub_config($$$$;$)
264 my ($filename, $title, $base, $modules_ref, $prepend) = @_;
265 if ($base) { $base = "$base/"; };
266 open(my $fg, '>', $filename) or die "$filename: $!";
267 print $fg "$prepend\n" if $prepend;
268 my $endmark = ($serial || defined $iprelay) ? ';' : '';
269 print $fg "title $title$endmark\n" if $title;
270 #print $fg "root $base\n"; # root doesn't really work for (nd)
272 foreach (@$modules_ref) {
275 my ($kbin, $kcmd) = split(' ', $_, 2);
276 $kcmd = '' if !defined $kcmd;
277 print $fg "kernel ${base}$kbin $kcmd\n";
279 s|\brom://([^ ]*)|$rom_prefix$base$1|g; # Translate rom:// files - needed for vdisk parameter of sigma0
280 print $fg "module $base$_\n";
286 sub generate_grub2_config($$$$;$)
288 my ($filename, $title, $base, $modules_ref, $prepend) = @_;
289 if ($base && substr($base,-1,1) ne '/') { $base = "$base/"; };
290 open(my $fg, '>', $filename) or die "$filename: $!";
291 print $fg "$prepend\n" if $prepend;
292 my $endmark = ($serial || defined $iprelay) ? ';' : '';
293 $title ||= 'novaboot';
294 print $fg "menuentry $title$endmark {\n";
295 print $fg "$CFG::grub2_prolog\n";
297 foreach (@$modules_ref) {
300 my ($kbin, $kcmd) = split(' ', $_, 2);
301 $kcmd = '' if !defined $kcmd;
302 print $fg " multiboot ${base}$kbin $kcmd\n";
305 # GRUB2 doesn't pass filename in multiboot info so we have to duplicate it here
306 $_ = join(' ', ($args[0], @args));
307 s|\brom://|$rom_prefix|g; # We do not need to translate path for GRUB2
308 print $fg " module $base$_\n";
315 sub generate_pulsar_config($$)
317 my ($filename, $modules_ref) = @_;
318 open(my $fg, '>', $filename) or die "$filename: $!";
319 print $fg "root $CFG::pulsar_root\n" if $CFG::pulsar_root;
322 foreach (@$modules_ref) {
325 ($kbin, $kcmd) = split(' ', $_, 2);
326 $kcmd = '' if !defined $kcmd;
329 s|\brom://|$rom_prefix|g;
330 print $fg "load $_\n";
333 # Put kernel as last - this is needed for booting Linux and has no influence on non-Linux OSes
334 print $fg "exec $kbin $kcmd\n";
340 print "novaboot: Running: ".join(' ', map("'$_'", @_))."\n";
344 sub system_verbose($)
347 print "novaboot: Running: $cmd\n";
348 my $ret = system($cmd);
349 if ($ret & 0x007f) { die("Command terminated by a signal"); }
350 if ($ret & 0xff00) {die("Command exit with non-zero exit code"); }
351 if ($ret) { die("Command failure $ret"); }
356 if (exists $variables->{WVDESC}) {
357 print "Testing \"$variables->{WVDESC}\" in $last_fn:\n";
358 } elsif ($last_fn =~ /\.wv$/) {
359 print "Testing \"all\" in $last_fn:\n";
362 ## Handle reset and power on/off
365 if (defined $iprelay) {
366 $CFG::iprelay_addr =~ /([.0-9]+)(:([0-9]+))?/;
369 my $paddr = sockaddr_in($port, inet_aton($addr));
370 my $proto = getprotobyname('tcp');
371 socket($IPRELAY, PF_INET, SOCK_STREAM, $proto) || die "socket: $!";
372 print "novaboot: Connecting to IP relay... ";
373 connect($IPRELAY, $paddr) || die "connect: $!";
375 $IPRELAY->autoflush(1);
378 print $IPRELAY "\xFF\xF6";
380 local $SIG{ALRM} = sub { die "Relay AYT timeout"; };
381 my $ayt_reponse = "";
382 my $read = sysread($IPRELAY, $ayt_reponse, 100);
386 print "$ayt_reponse\n";
387 if ($ayt_reponse =~ /<iprelayd: not connected/) {
395 my ($relay, $onoff) = @_;
396 die unless ($relay == 1 || $relay == 2);
398 my $cmd = ($relay == 1 ? 0x5 : 0x6) | ($onoff ? 0x20 : 0x10);
399 return "\xFF\xFA\x2C\x32".chr($cmd)."\xFF\xF0";
403 my ($relay, $onoff) = @_;
404 die unless ($relay == 1 || $relay == 2);
405 my $cmd = ($relay == 1 ? 0xdf : 0xbf) | ($onoff ? 0x00 : 0xff);
406 return "\xFF\xFA\x2C\x97".chr($cmd)."\xFF\xF0";
410 my ($relay, $onoff, $can_giveup) = @_;
411 my $confirmation = '';
412 print $IPRELAY relaycmd($relay, $onoff);
414 # We use non-blocking I/O and polling here because for some
415 # reason read() on blocking FD returns only after all
416 # requested data is available. If we get during the first
417 # read() only a part of confirmation, we may get the rest
418 # after the system boots and print someting, which may be too
420 $IPRELAY->blocking(0);
422 alarm(20); # Timeout in seconds
424 local $SIG{ALRM} = sub {
425 if ($can_giveup) { print("Relay confirmation timeout - ignoring\n"); $giveup = 1;}
426 else {die "Relay confirmation timeout";}
429 while (($index=index($confirmation, relayconf($relay, $onoff))) < 0 && !$giveup) {
430 my $read = read($IPRELAY, $confirmation, 70, length($confirmation));
431 if (!defined($read)) {
432 die("IP relay: $!") unless $! == EAGAIN;
436 #use MIME::QuotedPrint;
437 #print "confirmation = ".encode_qp($confirmation)."\n";
440 $IPRELAY->blocking(1);
444 if ($iprelay && ($iprelay eq "on" || $iprelay eq "off")) {
445 relay(1, 1); # Press power button
446 if ($iprelay eq "on") {
447 usleep(100000); # Short press
449 usleep(6000000); # Long press to switch off
451 print $IPRELAY relay(1, 0);
455 ## Figure out the location of builddir and chdir() there
457 $CFG::builddir = $builddir;
459 if (! defined $CFG::builddir) {
460 $CFG::builddir = ( $gittop || $ENV{'HOME'}."/nul" ) . "/build";
461 if (! -d $CFG::builddir) {
462 $CFG::builddir = $ENV{SRCDIR} = dirname(File::Spec->rel2abs( ${$scripts[0]}{filename}, $invocation_dir ));
467 chdir($CFG::builddir) or die "Can't change directory to $CFG::builddir: $!";
468 print "novaboot: Entering directory `$CFG::builddir'\n";
470 ## Process novaboot scripts
471 my (%files_iso, $menu_iso, $config_name, $filename);
473 foreach my $script (@scripts) {
474 $filename = $$script{filename};
475 $modules = $$script{modules};
476 $generated = $$script{generated};
477 $variables = $$script{variables};
478 my ($server_grub_prefix);
480 if (defined $config_name_opt) {
481 $config_name = $config_name_opt;
483 ($config_name = $filename) =~ s#.*/##;
487 if (exists $variables->{KERNEL}) {
488 $kernel = $variables->{KERNEL};
490 if ($CFG::hypervisor) {
491 $kernel = $CFG::hypervisor . " ";
492 if (exists $variables->{HYPERVISOR_PARAMS}) {
493 $kernel .= $variables->{HYPERVISOR_PARAMS};
495 $kernel .= $CFG::hypervisor_params;
499 @$modules = ($kernel, @$modules) if $kernel;
500 @$modules = (@CFG::chainloaders, @$modules);
501 @$modules = ("bin/boot/bender", @$modules) if ($bender || defined $ENV{'NOVABOOT_BENDER'});
503 ### Generate bootloader configuration files
504 if (defined $grub_config) {
505 generate_configs("", $generated, $filename);
506 generate_grub_config($grub_config, $config_name, "", $modules);
507 print("GRUB menu created: $CFG::builddir/$grub_config\n");
511 if (defined $grub2_config && !defined $server) {
512 generate_configs('', $generated, $filename);
513 generate_grub2_config($grub2_config, $config_name, $CFG::builddir, $modules);
514 print("GRUB2 configuration created: $CFG::builddir/$grub2_config\n");
519 if (defined $pulsar) {
520 $pulsar_config = "config-$CFG::pulsar_mac";
521 generate_configs('', $generated, $filename);
522 generate_pulsar_config($pulsar_config, $modules);
523 if (!defined $server) {
524 print("Pulsar configuration created: $CFG::builddir/$pulsar_config\n");
530 if (defined $scons) {
531 my @files = map({ ($file) = m/([^ ]*)/; $file; } @$modules);
532 # Filter-out generated files
533 my @to_build = grep({ my $file = $_; !scalar(grep($file eq $$_{filename}, @$generated)) } @files);
534 system_verbose($CFG::scons." ".join(" ", @to_build));
537 ### Copy files (using rsync)
538 if (defined $server) {
539 ($server_grub_prefix = $CFG::server_grub_prefix) =~ s/\$NAME/$config_name/;
540 ($server = $CFG::server) =~ s/\$NAME/$config_name/;
541 my $bootloader_config;
543 generate_configs('', $generated, $filename);
544 $bootloader_config ||= "grub.cfg";
545 generate_grub2_config($grub2_config, $config_name, $server_grub_prefix, $modules);
546 } elsif (defined $pulsar) {
547 $bootloader_config = $pulsar_config;
549 generate_configs($server_grub_prefix, $generated, $filename);
550 $bootloader_config ||= "menu.lst";
551 if (!grep { $_ eq $bootloader_config } @$modules) {
552 generate_grub_config($bootloader_config, $config_name, $server_grub_prefix, $modules,
553 $server_grub_prefix eq $CFG::server_grub_prefix ? "timeout 0" : undef);
556 my ($hostname, $path) = split(":", $server, 2);
557 if (! defined $path) {
561 my $files = "$bootloader_config " . join(" ", map({ ($file) = m/([^ ]*)/; $file; } @$modules));
562 my $combined_menu_lst = ($CFG::server =~ m|/\$NAME$|);
563 map({ my $file = (split)[0]; die "$file: $!" if ! -f $file; } @$modules);
564 my $istty = -t STDOUT && ($ENV{'TERM'} || 'dumb') ne 'dumb';
565 my $progress = $istty ? "--progress" : "";
566 system_verbose("rsync $progress -RLp $rsync_flags $files $server");
567 my $cmd = "cd $path/.. && cat */menu.lst > menu.lst";
568 if ($combined_menu_lst) { system_verbose($hostname ? "ssh $hostname '$cmd'" : $cmd); }
571 ### Prepare ISO image generation
572 if (defined $iso_image) {
573 generate_configs("(cd)", $generated, $filename);
575 generate_grub_config(\$menu, $config_name, "(cd)", $modules);
576 $menu_iso .= "$menu\n";
577 map { ($file,undef) = split; $files_iso{$file} = 1; } @$modules;
581 ## Generate ISO image
582 if (defined $iso_image) {
583 open(my $fh, ">menu-iso.lst");
584 print $fh "timeout 5\n\n$menu_iso";
586 my $files = "boot/grub/menu.lst=menu-iso.lst " . join(" ", map("$_=$_", keys(%files_iso)));
587 $iso_image ||= "$config_name.iso";
588 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");
589 print("ISO image created: $CFG::builddir/$iso_image\n");
592 ## Boot the system using various methods and send serial output to stdout
594 if (scalar(@scripts) > 1 && ( defined $dhcp_tftp || defined $serial || defined $iprelay)) {
595 die "You cannot do this with multiple scripts simultaneously";
598 if ($variables->{WVTEST_TIMEOUT}) {
599 print "wvtest: timeout ", $variables->{WVTEST_TIMEOUT}, "\n";
604 $str =~ s/^\s+|\s+$//g;
610 if (!(defined $dhcp_tftp || defined $serial || defined $iprelay || defined $server || defined $iso_image)) {
612 if (!$qemu && $variables->{QEMU}) {
613 @qemu_flags = split(" ", $variables->{QEMU});
614 $CFG::qemu = shift(@qemu_flags);
617 @qemu_flags = split(/ +/, trim($variables->{QEMU_FLAGS})) if exists $variables->{QEMU_FLAGS};
618 @qemu_flags = split(/ +/, trim($qemu_flags_cmd)) if $qemu_flags_cmd;
619 push(@qemu_flags, split(/ +/, trim($qemu_append)));
621 if (defined $iso_image) {
622 # Boot NOVA with grub (and test the iso image)
623 push(@qemu_flags, ('-cdrom', "$config_name.iso"));
625 # Boot NOVA without GRUB
627 # Non-patched qemu doesn't like commas, but NUL can live with pluses instead of commans
628 foreach (@$modules) {s/,/+/g;}
629 generate_configs("", $generated, $filename);
631 my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);
632 $kcmd = '' if !defined $kcmd;
634 @$modules = map { if (/\.dtb$/) { $dtb=$_; (); } else { $_ } } @$modules;
635 my $initrd = join ",", @$modules;
637 push(@qemu_flags, ('-kernel', $kbin, '-append', $kcmd));
638 push(@qemu_flags, ('-initrd', $initrd)) if $initrd;
639 push(@qemu_flags, ('-dtb', $dtb)) if $dtb;
641 push(@qemu_flags, qw(-serial stdio)); # Redirect serial output (for collecting test restuls)
642 exec_verbose(($CFG::qemu, '-name', $config_name, @qemu_flags));
645 ### Local DHCPD and TFTPD
647 my ($dhcpd_pid, $tftpd_pid);
649 if (defined $dhcp_tftp)
651 generate_configs("(nd)", $generated, $filename);
652 system_verbose('mkdir -p tftpboot');
653 generate_grub_config("tftpboot/os-menu.lst", $config_name, "(nd)", \@$modules, "timeout 0");
654 open(my $fh, '>', 'dhcpd.conf');
655 my $mac = `cat /sys/class/net/eth0/address`;
657 print $fh "subnet 10.23.23.0 netmask 255.255.255.0 {
658 range 10.23.23.10 10.23.23.100;
659 filename \"bin/boot/grub/pxegrub.pxe\";
660 next-server 10.23.23.1;
663 hardware ethernet $mac;
664 fixed-address 10.23.23.1;
667 system_verbose("sudo ip a add 10.23.23.1/24 dev eth0;
668 sudo ip l set dev eth0 up;
669 sudo touch dhcpd.leases");
672 if ($dhcpd_pid == 0) {
673 # This way, the spawned server are killed when this script is killed.
674 exec_verbose("sudo dhcpd -d -cf dhcpd.conf -lf dhcpd.leases -pf dhcpd.pid");
677 if ($tftpd_pid == 0) {
678 exec_verbose("sudo in.tftpd --foreground --secure -v -v -v $CFG::builddir");
680 $SIG{TERM} = sub { print "CHILDS KILLED\n"; kill 15, $dhcpd_pid, $tftpd_pid; };
683 ### Serial line or IP relay
685 if ($serial || defined $iprelay) {
687 if (defined $iprelay) {
688 print "novaboot: Reseting the test box... ";
689 relay(2, 1, 1); # Reset the machine
696 system("stty -F $serial raw -crtscts -onlcr 115200");
697 open($CONN, "+<", $serial) || die "open $serial: $!";
700 if (!defined $dhcp_tftp && $CFG::grub_keys) {
701 # Control grub via serial line
702 print "Waiting for GRUB's serial output... ";
704 if (/Press any key to continue/) { print $CONN "\n"; last; }
706 $CFG::grub_keys =~ s/\$NAME/$config_name;/;
707 my @characters = split(//, $CFG::grub_keys);
708 foreach (@characters) {
710 usleep($_ eq "\n" ? 100000 : 10000);
715 # Pass the NOVA output to stdout.
719 kill 15, $dhcpd_pid, $tftpd_pid if ($dhcp_tftp);
723 ### Wait for dhcpc or tftpd
724 if (defined $dhcp_tftp) {
726 if ($pid == $dhcpd_pid) { print "dhcpd exited!\n"; }
727 elsif ($pid == $tftpd_pid) { print "tftpd exited!\n"; }
728 else { print "wait returned: $pid\n"; }
729 kill(15, 0); # Kill current process group i.e. all remaining children
736 novaboot - A tool for booting various operating systems on various hardware or in qemu
740 B<novaboot> [ options ] [--] script...
742 B<./script> [ options ]
748 This program makes it easier to boot NOVA or other operating system
749 (OS) in different environments. It reads a so called novaboot script
750 and uses it either to boot the OS in an emulator (e.g. in qemu) or to
751 generate the configuration for a specific bootloader and optionally to
752 copy the necessary binaries and other needed files to proper
753 locations, perhaps on a remote server. In case the system is actually
754 booted, its serial output is redirected to standard output if that is
757 A typical way of using novaboot is to make the novaboot script
758 executable and set its first line to I<#!/usr/bin/env novaboot>. Then,
759 booting a particular OS configuration becomes the same as executing a
760 local program - the novaboot script.
762 With C<novaboot> you can:
768 Run an OS in Qemu. This is the default action when no other action is
769 specified by command line switches. Thus running C<novaboot ./script>
770 (or C<./script> as described above) will run Qemu and make it boot the
771 configuration specified in the I<script>.
775 Create a bootloader configuration file (currently supported
776 bootloaders are GRUB, GRUB2 and Pulsar) and copy it with all other
777 files needed for booting to another, perhaps remote, location.
779 ./script --server --iprelay
781 This command copies files to a TFTP server specified in the
782 configuration file and uses TCP/IP-controlled relay to reset the test
783 box and receive its serial output.
787 Run DHCP and TFTP server on developer's machine to PXE-boot NOVA from
792 When a PXE-bootable machine is connected via Ethernet to developer's
793 machine, it will boot the configuration described in I<script>.
797 Create bootable ISO images. E.g.
799 novaboot --iso -- script1 script2
801 The created ISO image will have GRUB bootloader installed on it and
802 the boot menu will allow selecting between I<script1> and I<script2>
811 =item -a, --append=<parameters>
813 Appends a string to the root task's command line.
817 Boot bender tool before the kernel to find PCI serial ports.
819 =item --build-dir=<directory>
821 Overrides the default build directory location.
823 The default build directory location is determined as follows:
825 If there is a configuration file, the value specified in I<$builddir>
826 variable is used. Otherwise, if the current working directory is
827 inside git work tree and there is F<build> directory at the top of
828 that tree, it is used. Otherwise, if directory F<~/nul/build> exists,
829 it is used. Otherwise, it is the directory that contains the first
830 processed novaboot script.
832 =item -c, --config=<filename>
834 Use a different configuration file than the default one (i.e.
837 =item -d, --dhcp-tftp
839 Turns your workstation into a DHCP and TFTP server so that NOVA
840 can be booted via PXE BIOS on a test machine directly connected by
841 a plain Ethernet cable to your workstation.
843 The DHCP and TFTP servers require root privileges and C<novaboot>
844 uses C<sudo> command to obtain those. You can put the following to
845 I</etc/sudoers> to allow running the necessary commands without
848 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
849 your_login ALL=NOPASSWD: NOVABOOT
853 Prints the content of the novaboot script after removing comments and
854 evaluating all I<--scriptmod> expressions.
858 Dumps current configuration to stdout end exits. Useful as an initial
859 template for a configuration file.
861 =item -g, --grub[=I<filename>]
863 Generates grub menu file. If the I<filename> is not specified,
864 F<menu.lst> is used. The I<filename> is relative to NUL build
867 =item --grub-prefix=I<prefix>
869 Specifies I<prefix> that is put before every file in GRUB's menu.lst.
870 This overrides the value of I<$server_grub_prefix> from the
873 =item --grub2[=I<filename>]
875 Generate GRUB2 menuentry in I<filename>. If I<filename> is not
876 specified grub.cfg is used. The content of the menuentry can be
877 customized by I<$grub2_prolog> and I<$server_grub_prefix>
878 configuration variables.
880 In order to use the the generated menuentry on your development
881 machine that uses GRUB2, append the following snippet to
882 F</etc/grub.d/40_custom> file and regenerate your grub configuration,
883 i.e. run update-grub on Debian/Ubuntu.
885 if [ -f /path/to/nul/build/grub.cfg ]; then
886 source /path/to/nul/build/grub.cfg
892 Print short (B<-h>) or long (B<--help>) help.
894 =item --iprelay[=addr or cmd]
896 If no I<cmd> is given, use IP relay to reset the machine and to get
897 the serial output. The IP address of the relay is given by I<addr>
898 parameter if specified or by $iprelay_addr variable in the
901 If I<cmd> is one of "on" or "off", the IP relay is used to press power
902 button for a short (in case of "on") or long (in case of "off") time.
903 Then, novaboot exits.
905 Note: This option is expected to work with HWG-ER02a IP relays.
907 =item -i, --iso[=filename]
909 Generates the ISO image that boots NOVA system via GRUB. If no filename
910 is given, the image is stored under I<NAME>.iso, where I<NAME> is the name
911 of novaboot script (see also B<--name>).
915 This is an alias (see C<%custom_options> below) defined in the default
916 configuration. When used, it causes novaboot to use Michal's remotely
917 controllable test bed.
921 This is an alias (see C<%custom_options> below) defined in the default
922 configuration. When used, it causes novaboot to use another remotely
923 controllable test bed.
927 Synonym for --iprelay=on/off.
929 =item --name=I<string>
931 Use the name I<string> instead of the name of the novaboot script.
932 This name is used for things like a title of grub menu or for the
933 server directory where the boot files are copied to.
937 Do not generate files on the fly (i.e. "<" syntax) except for the
938 files generated via "<<WORD" syntax.
940 =item -p, --pulsar[=mac]
942 Generates pulsar bootloader configuration file whose name is based on
943 the MAC address specified either on the command line or taken from
944 I<.novaboot> configuration file.
946 =item -Q, --qemu=I<qemu-binary>
948 Use specific version of qemu binary. The default is 'qemu'.
950 =item --qemu-append=I<flags>
952 Append I<flags> to the default qemu flags (QEMU_FLAGS variable or
953 C<-cpu coreduo -smp 2>).
955 =item -q, --qemu-flags=I<flags>
957 Replace the default qemu flags (QEMU_FLAGS variable or C<-cpu coreduo
958 -smp 2>) with I<flags> specified here.
960 =item --rsync-flags=I<flags>
962 Specifies which I<flags> are appended to F<rsync> command line when
963 copying files as a result of I<--server> option.
965 =item --scons[=scons command]
967 Runs I<scons> to build files that are not generated by novaboot
970 =item --scriptmod=I<perl expression>
972 When novaboot script is read, I<perl expression> is executed for every
973 line (in $_ variable). For example, C<novaboot
974 --scriptmod=s/sigma0/omega6/g> replaces every occurrence of I<sigma0>
975 in the script with I<omega6>.
977 When this option is present, it overrides I<$script_modifier> variable
978 from the configuration file, which has the same effect. If this option
979 is given multiple times all expressions are evaluated in the command
982 =item --server[=[[user@]server:]path]
984 Copy all files needed for booting to a server (implies B<-g> unless
985 B<--grub2> is given). The files will be copied to the directory
986 I<path>. If the I<path> contains string $NAME, it will be replaced
987 with the name of the novaboot script (see also B<--name>).
989 Additionally, if $NAME is the last component of the I<path>, a file
990 named I<path>/menu.lst (with $NAME removed from the I<path>) will be
991 created on the server by concatenating all I<path>/*/menu.lst (with
992 $NAME removed from the I<path>) files found on the server.
994 =item -s, --serial[=device]
996 Use serial line to control GRUB bootloader and to get the serial
997 output of the machine. The default value is /dev/ttyUSB0.
1001 Strip I<rom://> prefix from command lines and generated config files.
1002 This is needed for NRE.
1006 =head1 NOVABOOT SCRIPT SYNTAX
1008 The syntax tries to mimic POSIX shell syntax. The syntax is defined with the following rules.
1010 Lines starting with "#" are ignored.
1012 Lines that end with "\" are concatenated with the following line after
1013 removal of the final "\" and leading whitespace of the following line.
1015 Lines in the form I<VARIABLE=...> (i.e. matching '^[A-Z_]+=' regular
1016 expression) assign values to internal variables. See VARIABLES
1019 Otherwise, the first word on the line represents the filename
1020 (relative to the build directory (see I<--build-dir>) of the module to
1021 load and the remaining words are passed as the command line
1024 When the line ends with "<<WORD" then the subsequent lines until the
1025 line containing only WORD are copied literally to the file named on
1028 When the line ends with "< CMD" the command CMD is executed with
1029 C</bin/sh> and its standard output is stored in the file named on that
1030 line. The SRCDIR variable in CMD's environment is set to the absolute
1031 path of the directory containing the interpreted novaboot script.
1034 #!/usr/bin/env novaboot
1035 WVDESC=Example program
1036 bin/apps/sigma0.nul S0_DEFAULT script_start:1,1 \
1037 verbose hostkeyb:0,0x60,1,12,2
1039 hello.nulconfig <<EOF
1040 sigma0::mem:16 name::/s0/log name::/s0/timer name::/s0/fs/rom ||
1041 rom://bin/apps/hello.nul
1044 This example will load three modules: sigma0.nul, hello.nul and
1045 hello.nulconfig. sigma0 gets some command line parameters and
1046 hello.nulconfig file is generated on the fly from the lines between
1051 The following variables are interpreted in the novaboot script:
1057 Description of the wvtest-compliant program.
1059 =item WVTEST_TIMEOUT
1061 The timeout in seconds for WvTest harness. If no complete line appears
1062 in the test output within the time specified here, the test fails. It
1063 is necessary to specify this for long running tests that produce no
1064 intermediate output.
1068 Use a specific qemu binary (can be overriden with B<-Q>) and flags
1069 when booting this script under qemu. If QEMU_FLAGS variable is also
1070 specified flags specified in QEMU variable are replaced by those in
1075 Use specific qemu flags (can be overriden with B<-q>).
1077 =item HYPERVISOR_PARAMS
1079 Parameters passed to hypervisor. The default value is "serial", unless
1080 overriden in configuration file.
1084 The kernel to use instead of NOVA hypervisor specified in the
1085 configuration file. The value should contain the name of the kernel
1086 image as well as its command line parameters. If this variable is
1087 defined and non-empty, the variable HYPERVISOR_PARAMS is not used.
1091 =head1 CONFIGURATION FILE
1093 Novaboot can read its configuration from a file. Configuration file
1094 was necessary in early days of novaboot. Nowadays, the attempt is made
1095 to not use the configuration file because it makes certain novaboot
1096 scripts unusable on systems without (or with different) configuration
1097 file. The only recommended use of the configuration file is to specify
1098 custom_options (see bellow).
1100 If you decide to use the configuration file, its default location is
1101 ~/.novaboot, other location can be specified B<-c> parameter or with
1102 NOVABOOT_CONFIG environment variable. The configuration has perl
1103 syntax and sets values of certain variables. The current configuration
1104 can be dumped with B<--dump-config> switch. Some configuration
1105 variables can be overriden by environment variables (see below) or by
1106 command line switches.
1108 Documentation of some configuration variables follows:
1114 Custom chainloaders to load before hypervisor and files specified in
1115 novaboot script. E.g. ('bin/boot/bender promisc', 'bin/boot/zapp').
1117 =item %custom_options
1119 Defines custom command line options that can serve as aliases for
1120 other options. E.g. 'S' => '--server=boot:/tftproot
1121 --serial=/dev/ttyUSB0'.
1125 =head1 ENVIRONMENT VARIABLES
1127 Some options can be specified not only via config file or command line
1128 but also through environment variables. Environment variables override
1129 the values from configuration file and command line parameters
1130 override the environment variables.
1134 =item NOVABOOT_CONFIG
1136 A name of default novaboot configuration file.
1138 =item NOVABOOT_BENDER
1140 Defining this variable has the same meaning as B<--bender> option.
1142 =item NOVABOOT_IPRELAY
1144 The IP address (and optionally the port) of the IP relay. This
1145 overrides $iprelay_addr variable from the configuration file.
1151 Michal Sojka <sojka@os.inf.tu-dresden.de>