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 ( @{$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 ## File generation phase
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 ($config_name = $filename) =~ s#.*/##;
481 $config_name = $config_name_opt if (defined $config_name_opt);
484 if (exists $variables->{KERNEL}) {
485 $kernel = $variables->{KERNEL};
487 if ($CFG::hypervisor) {
488 $kernel = $CFG::hypervisor . " ";
489 if (exists $variables->{HYPERVISOR_PARAMS}) {
490 $kernel .= $variables->{HYPERVISOR_PARAMS};
492 $kernel .= $CFG::hypervisor_params;
496 @$modules = ($kernel, @$modules) if $kernel;
497 @$modules = (@CFG::chainloaders, @$modules);
498 @$modules = ("bin/boot/bender", @$modules) if ($bender || defined $ENV{'NOVABOOT_BENDER'});
500 ### Generate bootloader configuration files
501 if (defined $grub_config) {
502 generate_configs("", $generated, $filename);
503 generate_grub_config($grub_config, $config_name, "", $modules);
504 print("GRUB menu created: $CFG::builddir/$grub_config\n");
508 if (defined $grub2_config && !defined $server) {
509 generate_configs('', $generated, $filename);
510 generate_grub2_config($grub2_config, $config_name, $CFG::builddir, $modules);
511 print("GRUB2 configuration created: $CFG::builddir/$grub2_config\n");
516 if (defined $pulsar) {
517 $pulsar_config = "config-$CFG::pulsar_mac";
518 generate_configs('', $generated, $filename);
519 generate_pulsar_config($pulsar_config, $modules);
520 if (!defined $server) {
521 print("Pulsar configuration created: $CFG::builddir/$pulsar_config\n");
527 if (defined $scons) {
528 my @files = map({ ($file) = m/([^ ]*)/; $file; } @$modules);
529 # Filter-out generated files
530 my @to_build = grep({ my $file = $_; !scalar(grep($file eq $$_{filename}, @$generated)) } @files);
531 system_verbose($CFG::scons." ".join(" ", @to_build));
534 ### Copy files (using rsync)
535 if (defined $server) {
536 ($server_grub_prefix = $CFG::server_grub_prefix) =~ s/\$NAME/$config_name/;
537 ($server = $CFG::server) =~ s/\$NAME/$config_name/;
538 my $bootloader_config;
540 generate_configs('', $generated, $filename);
541 $bootloader_config ||= "grub.cfg";
542 generate_grub2_config($grub2_config, $config_name, $server_grub_prefix, $modules);
543 } elsif (defined $pulsar) {
544 $bootloader_config = $pulsar_config;
546 generate_configs($server_grub_prefix, $generated, $filename);
547 $bootloader_config ||= "menu.lst";
548 if (!grep { $_ eq $bootloader_config } @$modules) {
549 generate_grub_config($bootloader_config, $config_name, $server_grub_prefix, $modules,
550 $server_grub_prefix eq $CFG::server_grub_prefix ? "timeout 0" : undef);
553 my ($hostname, $path) = split(":", $server, 2);
554 if (! defined $path) {
558 my $files = "$bootloader_config " . join(" ", map({ ($file) = m/([^ ]*)/; $file; } @$modules));
559 my $combined_menu_lst = ($CFG::server =~ m|/\$NAME$|);
560 map({ my $file = (split)[0]; die "$file: $!" if ! -f $file; } @$modules);
561 my $istty = -t STDOUT && ($ENV{'TERM'} || 'dumb') ne 'dumb';
562 my $progress = $istty ? "--progress" : "";
563 system_verbose("rsync $progress -RLp $rsync_flags $files $server");
564 my $cmd = "cd $path/.. && cat */menu.lst > menu.lst";
565 if ($combined_menu_lst) { system_verbose($hostname ? "ssh $hostname '$cmd'" : $cmd); }
568 ### Prepare ISO image generation
569 if (defined $iso_image) {
570 generate_configs("(cd)", $generated, $filename);
572 generate_grub_config(\$menu, $config_name, "(cd)", $modules);
573 $menu_iso .= "$menu\n";
574 map { ($file,undef) = split; $files_iso{$file} = 1; } @$modules;
578 ## Generate ISO image
579 if (defined $iso_image) {
580 open(my $fh, ">menu-iso.lst");
581 print $fh "timeout 5\n\n$menu_iso";
583 my $files = "boot/grub/menu.lst=menu-iso.lst " . join(" ", map("$_=$_", keys(%files_iso)));
584 $iso_image ||= "$config_name.iso";
585 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");
586 print("ISO image created: $CFG::builddir/$iso_image\n");
589 ## Boot the system using various methods and send serial output to stdout
591 if (scalar(@scripts) > 1 && ( defined $dhcp_tftp || defined $serial || defined $iprelay)) {
592 die "You cannot do this with multiple scripts simultaneously";
595 if ($variables->{WVTEST_TIMEOUT}) {
596 print "wvtest: timeout ", $variables->{WVTEST_TIMEOUT}, "\n";
601 $str =~ s/^\s+|\s+$//g;
607 if (!(defined $dhcp_tftp || defined $serial || defined $iprelay || defined $server || defined $iso_image)) {
609 if (!$qemu && $variables->{QEMU}) {
610 @qemu_flags = split(" ", $variables->{QEMU});
611 $CFG::qemu = shift(@qemu_flags);
614 @qemu_flags = split(/ +/, trim($variables->{QEMU_FLAGS})) if exists $variables->{QEMU_FLAGS};
615 @qemu_flags = split(/ +/, trim($qemu_flags_cmd)) if $qemu_flags_cmd;
616 push(@qemu_flags, split(/ +/, trim($qemu_append)));
618 if (defined $iso_image) {
619 # Boot NOVA with grub (and test the iso image)
620 push(@qemu_flags, ('-cdrom', "$config_name.iso"));
622 # Boot NOVA without GRUB
624 # Non-patched qemu doesn't like commas, but NUL can live with pluses instead of commans
625 foreach (@$modules) {s/,/+/g;}
626 generate_configs("", $generated, $filename);
628 my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);
629 $kcmd = '' if !defined $kcmd;
631 @$modules = map { if (/\.dtb$/) { $dtb=$_; (); } else { $_ } } @$modules;
632 my $initrd = join ",", @$modules;
634 push(@qemu_flags, ('-kernel', $kbin, '-append', $kcmd));
635 push(@qemu_flags, ('-initrd', $initrd)) if $initrd;
636 push(@qemu_flags, ('-dtb', $dtb)) if $dtb;
638 push(@qemu_flags, qw(-serial stdio)); # Redirect serial output (for collecting test restuls)
639 exec_verbose(($CFG::qemu, '-name', $config_name, @qemu_flags));
642 ### Local DHCPD and TFTPD
644 my ($dhcpd_pid, $tftpd_pid);
646 if (defined $dhcp_tftp)
648 generate_configs("(nd)", $generated, $filename);
649 system_verbose('mkdir -p tftpboot');
650 generate_grub_config("tftpboot/os-menu.lst", $config_name, "(nd)", \@$modules, "timeout 0");
651 open(my $fh, '>', 'dhcpd.conf');
652 my $mac = `cat /sys/class/net/eth0/address`;
654 print $fh "subnet 10.23.23.0 netmask 255.255.255.0 {
655 range 10.23.23.10 10.23.23.100;
656 filename \"bin/boot/grub/pxegrub.pxe\";
657 next-server 10.23.23.1;
660 hardware ethernet $mac;
661 fixed-address 10.23.23.1;
664 system_verbose("sudo ip a add 10.23.23.1/24 dev eth0;
665 sudo ip l set dev eth0 up;
666 sudo touch dhcpd.leases");
669 if ($dhcpd_pid == 0) {
670 # This way, the spawned server are killed when this script is killed.
671 exec_verbose("sudo dhcpd -d -cf dhcpd.conf -lf dhcpd.leases -pf dhcpd.pid");
674 if ($tftpd_pid == 0) {
675 exec_verbose("sudo in.tftpd --foreground --secure -v -v -v $CFG::builddir");
677 $SIG{TERM} = sub { print "CHILDS KILLED\n"; kill 15, $dhcpd_pid, $tftpd_pid; };
680 ### Serial line or IP relay
682 if ($serial || defined $iprelay) {
684 if (defined $iprelay) {
685 print "novaboot: Reseting the test box... ";
686 relay(2, 1, 1); # Reset the machine
693 system("stty -F $serial raw -crtscts -onlcr 115200");
694 open($CONN, "+<", $serial) || die "open $serial: $!";
697 if (!defined $dhcp_tftp && $CFG::grub_keys) {
698 # Control grub via serial line
699 print "Waiting for GRUB's serial output... ";
701 if (/Press any key to continue/) { print $CONN "\n"; last; }
703 $CFG::grub_keys =~ s/\$NAME/$config_name;/;
704 my @characters = split(//, $CFG::grub_keys);
705 foreach (@characters) {
707 usleep($_ eq "\n" ? 100000 : 10000);
712 # Pass the NOVA output to stdout.
716 kill 15, $dhcpd_pid, $tftpd_pid if ($dhcp_tftp);
720 ### Wait for dhcpc or tftpd
721 if (defined $dhcp_tftp) {
723 if ($pid == $dhcpd_pid) { print "dhcpd exited!\n"; }
724 elsif ($pid == $tftpd_pid) { print "tftpd exited!\n"; }
725 else { print "wait returned: $pid\n"; }
726 kill(15, 0); # Kill current process group i.e. all remaining children
733 novaboot - A tool for booting various operating systems on various hardware or in qemu
737 B<novaboot> [ options ] [--] script...
739 B<./script> [ options ]
745 This program makes it easier to boot NOVA or other operating system
746 (OS) in different environments. It reads a so called novaboot script
747 and uses it either to boot the OS in an emulator (e.g. in qemu) or to
748 generate the configuration for a specific bootloader and optionally to
749 copy the necessary binaries and other needed files to proper
750 locations, perhaps on a remote server. In case the system is actually
751 booted, its serial output is redirected to standard output if that is
754 A typical way of using novaboot is to make the novaboot script
755 executable and set its first line to I<#!/usr/bin/env novaboot>. Then,
756 booting a particular OS configuration becomes the same as executing a
757 local program - the novaboot script.
759 With C<novaboot> you can:
765 Run an OS in Qemu. This is the default action when no other action is
766 specified by command line switches. Thus running C<novaboot ./script>
767 (or C<./script> as described above) will run Qemu and make it boot the
768 configuration specified in the I<script>.
772 Create a bootloader configuration file (currently supported
773 bootloaders are GRUB, GRUB2 and Pulsar) and copy it with all other
774 files needed for booting to another, perhaps remote, location.
776 ./script --server --iprelay
778 This command copies files to a TFTP server specified in the
779 configuration file and uses TCP/IP-controlled relay to reset the test
780 box and receive its serial output.
784 Run DHCP and TFTP server on developer's machine to PXE-boot NOVA from
789 When a PXE-bootable machine is connected via Ethernet to developer's
790 machine, it will boot the configuration described in I<script>.
794 Create bootable ISO images. E.g.
796 novaboot --iso -- script1 script2
798 The created ISO image will have GRUB bootloader installed on it and
799 the boot menu will allow selecting between I<script1> and I<script2>
804 =head1 PHASES AND OPTIONS
806 Novaboot perform its work in several phases. Each phase can be
807 influenced by several options, certain phases can be skipped. The list
808 of phases with the corresponding options follows.
810 =head2 Command line processing phase
814 =item -c, --config=<filename>
816 Use a different configuration file than the default one (i.e.
821 Dumps current configuration to stdout end exits. Useful as an initial
822 template for a configuration file.
826 Print short (B<-h>) or long (B<--help>) help.
830 This is an alias (see C<%custom_options> below) defined in the default
831 configuration. When used, it causes novaboot to use Michal's remotely
832 controllable test bed.
836 This is an alias (see C<%custom_options> below) defined in the default
837 configuration. When used, it causes novaboot to use another remotely
838 controllable test bed.
842 =head2 Script preprocessing phase
844 This phases allows to modify the parsed novaboot script before it is
845 used in the later phases.
849 =item -a, --append=<parameters>
851 Appends a string to the first "filename" line in the novaboot script.
852 This can be used to append parameters to the kernel's or root task's
857 Use F<bender> chainloader. Bender scans the PCI bus for PCI serial
858 ports and stores the information about them in the BIOS data area for
863 Prints the content of the novaboot script after removing comments and
864 evaluating all I<--scriptmod> expressions. Exit after reading (and
867 =item --scriptmod=I<perl expression>
869 When novaboot script is read, I<perl expression> is executed for every
870 line (in $_ variable). For example, C<novaboot
871 --scriptmod=s/sigma0/omega6/g> replaces every occurrence of I<sigma0>
872 in the script with I<omega6>.
874 When this option is present, it overrides I<$script_modifier> variable
875 from the configuration file, which has the same effect. If this option
876 is given multiple times all expressions are evaluated in the command
881 Strip I<rom://> prefix from command lines and generated config files.
882 The I<rom://> prefix is used by NUL. For NRE, it has to be stripped.
886 =head2 File generation phase
888 In this phase, files needed for booting are generated in a so called
889 I<build directory> (see TODO). In most cases configuration for a
890 bootloader is generated automatically by novaboot. It is also possible
891 to generate other files using I<heredoc> or I<"<"> syntax in novaboot
892 scripts. Finally, binaries can be generated in this phases by running
897 =item --build-dir=<directory>
899 Overrides the default build directory location.
901 The default build directory location is determined as follows:
903 If there is a configuration file, the value specified in the
904 I<$builddir> variable is used. Otherwise, if the current working
905 directory is inside git work tree and there is F<build> directory at
906 the top of that tree, it is used. Otherwise, if directory
907 F<~/nul/build> exists, it is used. Otherwise, it is the directory that
908 contains the first processed novaboot script.
910 =item -g, --grub[=I<filename>]
912 Generates grub bootloader menu file. If the I<filename> is not
913 specified, F<menu.lst> is used. The I<filename> is relative to the
914 build directory (see B<--build-dir>).
916 =item --grub-prefix=I<prefix>
918 Specifies I<prefix> that is put before every file in GRUB's F<menu.lst>.
919 This overrides the value of I<$server_grub_prefix> from the
922 =item --grub2[=I<filename>]
924 Generate GRUB2 menuentry in I<filename>. If I<filename> is not
925 specified F<grub.cfg> is used. The content of the menuentry can be
926 customized by I<$grub2_prolog> and I<$server_grub_prefix>
927 configuration variables.
929 In order to use the the generated menuentry on your development
930 machine that uses GRUB2, append the following snippet to
931 F</etc/grub.d/40_custom> file and regenerate your grub configuration,
932 i.e. run update-grub on Debian/Ubuntu.
934 if [ -f /path/to/nul/build/grub.cfg ]; then
935 source /path/to/nul/build/grub.cfg
938 =item --name=I<string>
940 Use the name I<string> instead of the name of the novaboot script.
941 This name is used for things like a title of grub menu or for the
942 server directory where the boot files are copied to.
946 Do not generate files on the fly (i.e. "<" syntax) except for the
947 files generated via "<<WORD" syntax.
949 =item -p, --pulsar[=mac]
951 Generates pulsar bootloader configuration file whose name is based on
952 the MAC address specified either on the command line or taken from
953 I<.novaboot> configuration file.
957 =head2 File deployment phase
959 In some setups, it is necessary to copy the files needed for booting
960 to a particular location, e.g. to a TFTP boot server or to the
965 =item -d, --dhcp-tftp
967 Turns your workstation into a DHCP and TFTP server so that NOVA
968 can be booted via PXE BIOS on a test machine directly connected by
969 a plain Ethernet cable to your workstation.
971 The DHCP and TFTP servers require root privileges and C<novaboot>
972 uses C<sudo> command to obtain those. You can put the following to
973 I</etc/sudoers> to allow running the necessary commands without
976 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
977 your_login ALL=NOPASSWD: NOVABOOT
979 =item -i, --iso[=filename]
981 Generates the ISO image that boots NOVA system via GRUB. If no filename
982 is given, the image is stored under I<NAME>.iso, where I<NAME> is the name
983 of the novaboot script (see also B<--name>).
985 =item --server[=[[user@]server:]path]
987 Copy all files needed for booting to another location (implies B<-g>
988 unless B<--grub2> is given). The files will be copied (by B<rsync>
989 tool) to the directory I<path>. If the I<path> contains string $NAME,
990 it will be replaced with the name of the novaboot script (see also
993 Additionally, if $NAME is the last component of the I<path>, a file
994 named I<path>/menu.lst (with $NAME removed from the I<path>) will be
995 created on the server by concatenating all I<path>/*/menu.lst (with
996 $NAME removed from the I<path>) files found on the server.
998 =item --rsync-flags=I<flags>
1000 Specifies which I<flags> are appended to F<rsync> command line when
1001 copying files as a result of I<--server> option.
1003 =item --scons[=scons command]
1005 Runs I<scons> to build files that are not generated by novaboot
1010 =head2 Target power-on and reset phase
1014 =item --iprelay[=addr or cmd]
1016 If no I<cmd> is given, use IP relay to reset the machine and to get
1017 the serial output. The IP address of the relay is given by I<addr>
1018 parameter if specified or by $iprelay_addr variable in the
1021 If I<cmd> is one of "on" or "off", the IP relay is used to press power
1022 button for a short (in case of "on") or long (in case of "off") time.
1023 Then, novaboot exits.
1025 Note: This option is expected to work with HWG-ER02a IP relays.
1029 Synonym for --iprelay=on/off.
1031 =item -Q, --qemu=I<qemu-binary>
1033 The name of qemu binary to use. The default is 'qemu'.
1035 =item --qemu-append=I<flags>
1037 Append I<flags> to the default qemu flags (QEMU_FLAGS variable or
1038 C<-cpu coreduo -smp 2>).
1040 =item -q, --qemu-flags=I<flags>
1042 Replace the default qemu flags (QEMU_FLAGS variable or C<-cpu coreduo
1043 -smp 2>) with I<flags> specified here.
1047 =head2 Interaction with the bootloader on the target
1049 See B<--serial>. There will be new options soon.
1051 =head2 Target's output reception phase
1055 =item -s, --serial[=device]
1057 Use serial line to control GRUB bootloader and to see the output
1058 serial output of the machine. The default value is F</dev/ttyUSB0>.
1062 See also B<--iprelay>.
1064 =head2 Termination phase
1066 Daemons that were spwned (F<dhcpd> and F<tftpd>) are killed here.
1068 =head1 NOVABOOT SCRIPT SYNTAX
1070 The syntax tries to mimic POSIX shell syntax. The syntax is defined with the following rules.
1072 Lines starting with "#" are ignored.
1074 Lines that end with "\" are concatenated with the following line after
1075 removal of the final "\" and leading whitespace of the following line.
1077 Lines in the form I<VARIABLE=...> (i.e. matching '^[A-Z_]+=' regular
1078 expression) assign values to internal variables. See VARIABLES
1081 Otherwise, the first word on the line represents the filename
1082 (relative to the build directory (see B<--build-dir>) of the module to
1083 load and the remaining words are passed as the command line
1086 When the line ends with "<<WORD" then the subsequent lines until the
1087 line containing only WORD are copied literally to the file named on
1090 When the line ends with "< CMD" the command CMD is executed with
1091 C</bin/sh> and its standard output is stored in the file named on that
1092 line. The SRCDIR variable in CMD's environment is set to the absolute
1093 path of the directory containing the interpreted novaboot script.
1096 #!/usr/bin/env novaboot
1097 WVDESC=Example program
1098 bin/apps/sigma0.nul S0_DEFAULT script_start:1,1 \
1099 verbose hostkeyb:0,0x60,1,12,2
1101 hello.nulconfig <<EOF
1102 sigma0::mem:16 name::/s0/log name::/s0/timer name::/s0/fs/rom ||
1103 rom://bin/apps/hello.nul
1106 This example will load three modules: sigma0.nul, hello.nul and
1107 hello.nulconfig. sigma0 gets some command line parameters and
1108 hello.nulconfig file is generated on the fly from the lines between
1113 The following variables are interpreted in the novaboot script:
1119 Description of the wvtest-compliant program.
1121 =item WVTEST_TIMEOUT
1123 The timeout in seconds for WvTest harness. If no complete line appears
1124 in the test output within the time specified here, the test fails. It
1125 is necessary to specify this for long running tests that produce no
1126 intermediate output.
1130 Use a specific qemu binary (can be overriden with B<-Q>) and flags
1131 when booting this script under qemu. If QEMU_FLAGS variable is also
1132 specified flags specified in QEMU variable are replaced by those in
1137 Use specific qemu flags (can be overriden with B<-q>).
1139 =item HYPERVISOR_PARAMS
1141 Parameters passed to hypervisor. The default value is "serial", unless
1142 overriden in configuration file.
1146 The kernel to use instead of NOVA hypervisor specified in the
1147 configuration file. The value should contain the name of the kernel
1148 image as well as its command line parameters. If this variable is
1149 defined and non-empty, the variable HYPERVISOR_PARAMS is not used.
1153 =head1 CONFIGURATION FILE
1155 Novaboot can read its configuration from a file. Configuration file
1156 was necessary in early days of novaboot. Nowadays, an attempt is made
1157 to not use the configuration file because it makes certain novaboot
1158 scripts unusable on systems without (or with different) configuration
1159 file. The only recommended use of the configuration file is to specify
1160 custom_options (see bellow).
1162 If you decide to use the configuration file, its default location is
1163 ~/.novaboot, other location can be specified with the B<-c> switch or
1164 with the NOVABOOT_CONFIG environment variable. The configuration file
1165 has perl syntax and should set values of certain Perl variables. The
1166 current configuration can be dumped with the B<--dump-config> switch.
1167 Some configuration variables can be overriden by environment variables
1168 (see below) or by command line switches.
1170 Documentation of some configuration variables follows:
1176 Custom chainloaders to load before hypervisor and files specified in
1177 novaboot script. E.g. ('bin/boot/bender promisc', 'bin/boot/zapp').
1179 =item %custom_options
1181 Defines custom command line options that can serve as aliases for
1182 other options. E.g. 'S' => '--server=boot:/tftproot
1183 --serial=/dev/ttyUSB0'.
1187 =head1 ENVIRONMENT VARIABLES
1189 Some options can be specified not only via config file or command line
1190 but also through environment variables. Environment variables override
1191 the values from configuration file and command line parameters
1192 override the environment variables.
1196 =item NOVABOOT_CONFIG
1198 A name of default novaboot configuration file.
1200 =item NOVABOOT_BENDER
1202 Defining this variable has the same meaning as B<--bender> option.
1204 =item NOVABOOT_IPRELAY
1206 The IP address (and optionally the port) of the IP relay. This
1207 overrides $iprelay_addr variable from the configuration file.
1213 Michal Sojka <sojka@os.inf.tu-dresden.de>