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 config(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 @qemu_flags = split(/ +/, trim($variables->{QEMU_FLAGS})) if exists $variables->{QEMU_FLAGS};
613 @qemu_flags = split(/ +/, trim($qemu_flags_cmd)) if $qemu_flags_cmd;
614 push(@qemu_flags, split(/ +/, trim($qemu_append)));
616 if (defined $iso_image) {
617 # Boot NOVA with grub (and test the iso image)
618 push(@qemu_flags, ('-cdrom', "$config_name.iso"));
620 # Boot NOVA without GRUB
622 # Non-patched qemu doesn't like commas, but NUL can live with pluses instead of commans
623 foreach (@$modules) {s/,/+/g;}
624 generate_configs("", $generated, $filename);
626 my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);
627 $kcmd = '' if !defined $kcmd;
628 my $initrd = join ",", @$modules;
630 push(@qemu_flags, ('-kernel', $kbin, '-append', $kcmd));
631 push(@qemu_flags, ('-initrd', $initrd)) if $initrd;
633 push(@qemu_flags, qw(-serial stdio)); # Redirect serial output (for collecting test restuls)
634 exec_verbose(($CFG::qemu, '-name', $config_name, @qemu_flags));
637 ### Local DHCPD and TFTPD
639 my ($dhcpd_pid, $tftpd_pid);
641 if (defined $dhcp_tftp)
643 generate_configs("(nd)", $generated, $filename);
644 system_verbose('mkdir -p tftpboot');
645 generate_grub_config("tftpboot/os-menu.lst", $config_name, "(nd)", \@$modules, "timeout 0");
646 open(my $fh, '>', 'dhcpd.conf');
647 my $mac = `cat /sys/class/net/eth0/address`;
649 print $fh "subnet 10.23.23.0 netmask 255.255.255.0 {
650 range 10.23.23.10 10.23.23.100;
651 filename \"bin/boot/grub/pxegrub.pxe\";
652 next-server 10.23.23.1;
655 hardware ethernet $mac;
656 fixed-address 10.23.23.1;
659 system_verbose("sudo ip a add 10.23.23.1/24 dev eth0;
660 sudo ip l set dev eth0 up;
661 sudo touch dhcpd.leases");
664 if ($dhcpd_pid == 0) {
665 # This way, the spawned server are killed when this script is killed.
666 exec_verbose("sudo dhcpd -d -cf dhcpd.conf -lf dhcpd.leases -pf dhcpd.pid");
669 if ($tftpd_pid == 0) {
670 exec_verbose("sudo in.tftpd --foreground --secure -v -v -v $CFG::builddir");
672 $SIG{TERM} = sub { print "CHILDS KILLED\n"; kill 15, $dhcpd_pid, $tftpd_pid; };
675 ### Serial line or IP relay
677 if ($serial || defined $iprelay) {
679 if (defined $iprelay) {
680 print "novaboot: Reseting the test box... ";
681 relay(2, 1, 1); # Reset the machine
688 system("stty -F $serial raw -crtscts -onlcr 115200");
689 open($CONN, "+<", $serial) || die "open $serial: $!";
692 if (!defined $dhcp_tftp && $CFG::grub_keys) {
693 # Control grub via serial line
694 print "Waiting for GRUB's serial output... ";
696 if (/Press any key to continue/) { print $CONN "\n"; last; }
698 $CFG::grub_keys =~ s/\$NAME/$config_name;/;
699 my @characters = split(//, $CFG::grub_keys);
700 foreach (@characters) {
702 usleep($_ eq "\n" ? 100000 : 10000);
707 # Pass the NOVA output to stdout.
711 kill 15, $dhcpd_pid, $tftpd_pid if ($dhcp_tftp);
715 ### Wait for dhcpc or tftpd
716 if (defined $dhcp_tftp) {
718 if ($pid == $dhcpd_pid) { print "dhcpd exited!\n"; }
719 elsif ($pid == $tftpd_pid) { print "tftpd exited!\n"; }
720 else { print "wait returned: $pid\n"; }
721 kill(15, 0); # Kill current process group i.e. all remaining children
728 novaboot - A tool for booting various operating systems on various hardware or in qemu
732 B<novaboot> [ options ] [--] script...
734 B<./script> [ options ]
740 This program makes it easier to boot NOVA or other operating system
741 (OS) in different environments. It reads a so called novaboot script
742 and uses it either to boot the OS in an emulator (e.g. in qemu) or to
743 generate the configuration for a specific bootloader and optionally to
744 copy the necessary binaries and other needed files to proper
745 locations, perhaps on a remote server. In case the system is actually
746 booted, its serial output is redirected to standard output if that is
749 A typical way of using novaboot is to make the novaboot script
750 executable and set its first line to I<#!/usr/bin/env novaboot>. Then,
751 booting a particular OS configuration becomes the same as executing a
752 local program - the novaboot script.
754 With C<novaboot> you can:
760 Run an OS in Qemu. This is the default action when no other action is
761 specified by command line switches. Thus running C<novaboot ./script>
762 (or C<./script> as described above) will run Qemu and make it boot the
763 configuration specified in the I<script>.
767 Create a bootloader configuration file (currently supported
768 bootloaders are GRUB, GRUB2 and Pulsar) and copy it with all other
769 files needed for booting to another, perhaps remote, location.
771 ./script --server --iprelay
773 This command copies files to a TFTP server specified in the
774 configuration file and uses TCP/IP-controlled relay to reset the test
775 box and receive its serial output.
779 Run DHCP and TFTP server on developer's machine to PXE-boot NOVA from
784 When a PXE-bootable machine is connected via Ethernet to developer's
785 machine, it will boot the configuration described in I<script>.
789 Create bootable ISO images. E.g.
791 novaboot --iso -- script1 script2
793 The created ISO image will have GRUB bootloader installed on it and
794 the boot menu will allow selecting between I<script1> and I<script2>
803 =item -a, --append=<parameters>
805 Appends a string to the root task's command line.
809 Boot bender tool before the kernel to find PCI serial ports.
811 =item --build-dir=<directory>
813 Overrides the default build directory location.
815 The default build directory location is determined as follows:
817 If there is a configuration file, the value specified in I<$builddir>
818 variable is used. Otherwise, if the current working directory is
819 inside git work tree and there is F<build> directory at the top of
820 that tree, it is used. Otherwise, if directory F<~/nul/build> exists,
821 it is used. Otherwise, it is the directory that contains the first
822 processed novaboot script.
824 =item -c, --config=<filename>
826 Use a different configuration file than the default one (i.e.
829 =item -d, --dhcp-tftp
831 Turns your workstation into a DHCP and TFTP server so that NOVA
832 can be booted via PXE BIOS on a test machine directly connected by
833 a plain Ethernet cable to your workstation.
835 The DHCP and TFTP servers require root privileges and C<novaboot>
836 uses C<sudo> command to obtain those. You can put the following to
837 I</etc/sudoers> to allow running the necessary commands without
840 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
841 your_login ALL=NOPASSWD: NOVABOOT
845 Prints the content of the novaboot script after removing comments and
846 evaluating all I<--scriptmod> expressions.
850 Dumps current configuration to stdout end exits. Useful as an initial
851 template for a configuration file.
853 =item -g, --grub[=I<filename>]
855 Generates grub menu file. If the I<filename> is not specified,
856 F<menu.lst> is used. The I<filename> is relative to NUL build
859 =item --grub-prefix=I<prefix>
861 Specifies I<prefix> that is put before every file in GRUB's menu.lst.
862 This overrides the value of I<$server_grub_prefix> from the
865 =item --grub2[=I<filename>]
867 Generate GRUB2 menuentry in I<filename>. If I<filename> is not
868 specified grub.cfg is used. The content of the menuentry can be
869 customized by I<$grub2_prolog> and I<$server_grub_prefix>
870 configuration variables.
872 In order to use the the generated menuentry on your development
873 machine that uses GRUB2, append the following snippet to
874 F</etc/grub.d/40_custom> file and regenerate your grub configuration,
875 i.e. run update-grub on Debian/Ubuntu.
877 if [ -f /path/to/nul/build/grub.cfg ]; then
878 source /path/to/nul/build/grub.cfg
884 Print short (B<-h>) or long (B<--help>) help.
886 =item --iprelay[=addr or cmd]
888 If no I<cmd> is given, use IP relay to reset the machine and to get
889 the serial output. The IP address of the relay is given by I<addr>
890 parameter if specified or by $iprelay_addr variable in the
893 If I<cmd> is one of "on" or "off", the IP relay is used to press power
894 button for a short (in case of "on") or long (in case of "off") time.
895 Then, novaboot exits.
897 Note: This option is expected to work with HWG-ER02a IP relays.
899 =item -i, --iso[=filename]
901 Generates the ISO image that boots NOVA system via GRUB. If no filename
902 is given, the image is stored under I<NAME>.iso, where I<NAME> is the name
903 of novaboot script (see also B<--name>).
907 This is an alias (see C<%custom_options> below) defined in the default
908 configuration. When used, it causes novaboot to use Michal's remotely
909 controllable test bed.
913 This is an alias (see C<%custom_options> below) defined in the default
914 configuration. When used, it causes novaboot to use another remotely
915 controllable test bed.
919 Synonym for --iprelay=on/off.
921 =item --name=I<string>
923 Use the name I<string> instead of the name of the novaboot script.
924 This name is used for things like a title of grub menu or for the
925 server directory where the boot files are copied to.
929 Do not generate files on the fly (i.e. "<" syntax) except for the
930 files generated via "<<WORD" syntax.
932 =item -p, --pulsar[=mac]
934 Generates pulsar bootloader configuration file whose name is based on
935 the MAC address specified either on the command line or taken from
936 I<.novaboot> configuration file.
938 =item -Q, --qemu=I<qemu-binary>
940 Use specific version of qemu binary. The default is 'qemu'.
942 =item --qemu-append=I<flags>
944 Append I<flags> to the default qemu flags (QEMU_FLAGS variable or
945 C<-cpu coreduo -smp 2>).
947 =item -q, --qemu-flags=I<flags>
949 Replace the default qemu flags (QEMU_FLAGS variable or C<-cpu coreduo
950 -smp 2>) with I<flags> specified here.
952 =item --rsync-flags=I<flags>
954 Specifies which I<flags> are appended to F<rsync> command line when
955 copying files as a result of I<--server> option.
957 =item --scons[=scons command]
959 Runs I<scons> to build files that are not generated by novaboot
962 =item --scriptmod=I<perl expression>
964 When novaboot script is read, I<perl expression> is executed for every
965 line (in $_ variable). For example, C<novaboot
966 --scriptmod=s/sigma0/omega6/g> replaces every occurrence of I<sigma0>
967 in the script with I<omega6>.
969 When this option is present, it overrides I<$script_modifier> variable
970 from the configuration file, which has the same effect. If this option
971 is given multiple times all expressions are evaluated in the command
974 =item --server[=[[user@]server:]path]
976 Copy all files needed for booting to a server (implies B<-g> unless
977 B<--grub2> is given). The files will be copied to the directory
978 I<path>. If the I<path> contains string $NAME, it will be replaced
979 with the name of the novaboot script (see also B<--name>).
981 Additionally, if $NAME is the last component of the I<path>, a file
982 named I<path>/menu.lst (with $NAME removed from the I<path>) will be
983 created on the server by concatenating all I<path>/*/menu.lst (with
984 $NAME removed from the I<path>) files found on the server.
986 =item -s, --serial[=device]
988 Use serial line to control GRUB bootloader and to get the serial
989 output of the machine. The default value is /dev/ttyUSB0.
993 Strip I<rom://> prefix from command lines and generated config files.
994 This is needed for NRE.
998 =head1 NOVABOOT SCRIPT SYNTAX
1000 The syntax tries to mimic POSIX shell syntax. The syntax is defined with the following rules.
1002 Lines starting with "#" are ignored.
1004 Lines that end with "\" are concatenated with the following line after
1005 removal of the final "\" and leading whitespace of the following line.
1007 Lines in the form I<VARIABLE=...> (i.e. matching '^[A-Z_]+=' regular
1008 expression) assign values to internal variables. See VARIABLES
1011 Otherwise, the first word on the line represents the filename
1012 (relative to the build directory (see I<--build-dir>) of the module to
1013 load and the remaining words are passed as the command line
1016 When the line ends with "<<WORD" then the subsequent lines until the
1017 line containing only WORD are copied literally to the file named on
1020 When the line ends with "< CMD" the command CMD is executed with
1021 C</bin/sh> and its standard output is stored in the file named on that
1022 line. The SRCDIR variable in CMD's environment is set to the absolute
1023 path of the directory containing the interpreted novaboot script.
1026 #!/usr/bin/env novaboot
1027 WVDESC=Example program
1028 bin/apps/sigma0.nul S0_DEFAULT script_start:1,1 \
1029 verbose hostkeyb:0,0x60,1,12,2
1031 hello.nulconfig <<EOF
1032 sigma0::mem:16 name::/s0/log name::/s0/timer name::/s0/fs/rom ||
1033 rom://bin/apps/hello.nul
1036 This example will load three modules: sigma0.nul, hello.nul and
1037 hello.nulconfig. sigma0 gets some command line parameters and
1038 hello.nulconfig file is generated on the fly from the lines between
1043 The following variables are interpreted in the novaboot script:
1049 Description of the wvtest-compliant program.
1051 =item WVTEST_TIMEOUT
1053 The timeout in seconds for WvTest harness. If no complete line appears
1054 in the test output within the time specified here, the test fails. It
1055 is necessary to specify this for long running tests that produce no
1056 intermediate output.
1060 Use specific qemu flags (can be overriden by B<-q>).
1062 =item HYPERVISOR_PARAMS
1064 Parameters passed to hypervisor. The default value is "serial", unless
1065 overriden in configuration file.
1069 The kernel to use instead of NOVA hypervisor specified in the
1070 configuration file. The value should contain the name of the kernel
1071 image as well as its command line parameters. If this variable is
1072 defined and non-empty, the variable HYPERVISOR_PARAMS is not used.
1076 =head1 CONFIGURATION FILE
1078 novaboot can read its configuration from ~/.novaboot file (or another
1079 file specified with B<-c> parameter or NOVABOOT_CONFIG environment
1080 variable). It is a file with perl syntax, which sets values of certain
1081 variables. The current configuration can be dumped with
1082 B<--dump-config> switch. Use
1084 novaboot --dump-config > ~/.novaboot
1086 to create a default configuration file and modify it to your needs.
1087 Some configuration variables can be overriden by environment variables
1088 (see below) or by command line switches.
1090 Documentation of some configuration variables follows:
1096 Custom chainloaders to load before hypervisor and files specified in
1097 novaboot script. E.g. ('bin/boot/bender promisc', 'bin/boot/zapp').
1099 =item %custom_options
1101 Defines custom command line options that can serve as aliases for
1102 other options. E.g. 'S' => '--server=boot:/tftproot
1103 --serial=/dev/ttyUSB0'.
1107 =head1 ENVIRONMENT VARIABLES
1109 Some options can be specified not only via config file or command line
1110 but also through environment variables. Environment variables override
1111 the values from configuration file and command line parameters
1112 override the environment variables.
1116 =item NOVABOOT_CONFIG
1118 A name of default novaboot configuration file.
1120 =item NOVABOOT_BENDER
1122 Defining this variable has the same meaning as B<--bender> option.
1124 =item NOVABOOT_IPRELAY
1126 The IP address (and optionally the port) of the IP relay. This
1127 overrides $iprelay_addr variable from the configuration file.
1133 Michal Sojka <sojka@os.inf.tu-dresden.de>