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::grub_keys = ''; #"/novaboot\n\n/\$NAME\n\n";
44 $CFG::genisoimage = "genisoimage";
45 $CFG::iprelay_addr = '141.76.48.80:2324'; #'141.76.48.252';
47 @CFG::chainloaders = (); #('bin/boot/bender promisc');
48 $CFG::pulsar_root = '';
49 $CFG::pulsar_mac = '52-54-00-12-34-56';
51 "tud" => '--server=erwin.inf.tu-dresden.de:~sojka/boot/novaboot --rsync-flags="--chmod=Dg+s,ug+w,o-w,+rX --rsync-path=\"umask 002 && rsync\"" --grub --grub-prefix=(nd)/tftpboot/sojka/novaboot --grub-preamble="timeout 0" --concat --iprelay=141.76.48.80:2324 --scriptmod=s/\\\\bhostserial\\\\b/hostserialpci/g',
52 "novabox" => '--server=rtime.felk.cvut.cz:/srv/tftp/novaboot --rsync-flags="--chmod=Dg+s,ug+w,o-w,+rX --rsync-path=\"umask 002 && rsync\"" --pulsar=novaboot --iprelay=147.32.86.92:2324',
53 "localhost" => '--scriptmod=s/console=tty[A-Z0-9,]+// --server=/boot/novaboot/$NAME --grub2 --grub-prefix=/boot/novaboot/$NAME --grub2-prolog=" set root=\'(hd0,msdos1)\'"',
55 $CFG::scons = "scons -j2";
57 my @qemu_flags = qw(-cpu coreduo -smp 2);
61 package CFG; # Put config data into a separate namespace
66 die("ERROR: Failure compiling '$cfg' - $@");
67 } elsif (! defined($rc)) {
68 die("ERROR: Failure reading '$cfg' - $!");
70 die("ERROR: Failure processing '$cfg'");
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, $concat, $config_name_opt, $dhcp_tftp, $dump_opt, $dump_config, $grub_config, $grub_prefix, $grub_preamble, $grub2_prolog, $grub2_config, $help, $iprelay, $iso_image, $man, $no_file_gen, $off_opt, $on_opt, $pulsar, $qemu, $qemu_append, $qemu_flags_cmd, $rom_prefix, $rsync_flags, @scriptmod, $scons, $serial, $server);
85 $rom_prefix = 'rom://';
87 Getopt::Long::Configure(qw/no_ignore_case no_pass_through/);
90 "append|a=s" => \$append,
91 "bender|b" => \$bender,
92 "build-dir=s" => \$builddir,
94 "dhcp-tftp|d" => \$dhcp_tftp,
96 "dump-config" => \$dump_config,
97 "grub|g:s" => \$grub_config,
98 "grub-preamble=s"=> \$grub_preamble,
99 "grub-prefix=s" => \$grub_prefix,
100 "grub2:s" => \$grub2_config,
101 "grub2-prolog=s" => \$grub2_prolog,
102 "iprelay:s" => \$iprelay,
103 "iso|i:s" => \$iso_image,
104 "name=s" => \$config_name_opt,
105 "no-file-gen" => \$no_file_gen,
108 "pulsar|p:s" => \$pulsar,
109 "qemu|Q=s" => \$qemu,
110 "qemu-append:s" => \$qemu_append,
111 "qemu-flags|q=s" => \$qemu_flags_cmd,
112 "rsync-flags=s" => \$rsync_flags,
113 "scons:s" => \$scons,
114 "scriptmod=s" => \@scriptmod,
115 "serial|s:s" => \$serial,
116 "server:s" => \$server,
117 "strip-rom" => sub { $rom_prefix = ''; },
118 "target|t=s" => sub { my ($opt_name, $opt_value) = @_;
119 exists $CFG::targets{$opt_value} or die("Unknown target '$opt_value' (valid targets are: ".join(", ", sort keys(%CFG::targets)).")");
120 GetOptionsFromString($CFG::targets{$opt_value}, %opt_spec); },
124 GetOptions %opt_spec or pod2usage(2);
125 pod2usage(1) if $help;
126 pod2usage(-exitstatus => 0, -verbose => 2) if $man;
128 ### Sanitize configuration
130 $CFG::iprelay_addr = $ENV{'NOVABOOT_IPRELAY'} if $ENV{'NOVABOOT_IPRELAY'};
131 if ($iprelay && $iprelay ne "on" && $iprelay ne "off") { $CFG::iprelay_addr = $iprelay; }
133 if (defined $config_name_opt && scalar(@ARGV) > 1) { die "You cannot use --name with multiple scripts"; }
135 if ($server) { $CFG::server = $server; }
136 if ($qemu) { $CFG::qemu = $qemu; }
140 $CFG::pulsar_mac = $pulsar;
143 if ($scons) { $CFG::scons = $scons; }
145 ### Dump sanitized configuration (if requested)
149 $Data::Dumper::Indent=0;
150 print "# This file is in perl syntax.\n";
151 foreach my $key(sort(keys(%CFG::))) { # See "Symbol Tables" in perlmod(1)
152 if (defined ${$CFG::{$key}}) { print Data::Dumper->Dump([${$CFG::{$key}}], ["*$key"]); }
153 if ( @{$CFG::{$key}}) { print Data::Dumper->Dump([\@{$CFG::{$key}}], ["*$key"]); }
154 if ( %{$CFG::{$key}}) { print Data::Dumper->Dump([\%{$CFG::{$key}}], ["*$key"]); }
161 if (defined $serial) {
162 $serial ||= "/dev/ttyUSB0";
165 if (defined $grub_config) {
166 $grub_config ||= "menu.lst";
169 if (defined $grub2_config) {
170 $grub2_config ||= "grub.cfg";
173 if ($on_opt) { $iprelay="on"; }
174 if ($off_opt) { $iprelay="off"; }
176 ## Parse the novaboot script(s)
182 my ($modules, $variables, $generated, $continuation);
184 if ($ARGV ne $last_fn) { # New script
185 die "Missing EOF in $last_fn" if $file;
186 die "Unfinished line in $last_fn" if $line;
188 push @scripts, { 'filename' => $ARGV,
189 'modules' => $modules = [],
190 'variables' => $variables = {},
191 'generated' => $generated = []};
195 next if /^#/ || /^\s*$/; # Skip comments and empty lines
197 foreach my $mod(@scriptmod) { eval $mod; }
199 print "$_\n" if $dump_opt;
201 if (/^([A-Z_]+)=(.*)$/) { # Internal variable
202 $$variables{$1} = $2;
205 if (/^([^ ]*)(.*?)[[:space:]]*<<([^ ]*)$/) { # Heredoc start
206 push @$modules, "$1$2";
208 push @$generated, {filename => $1, content => $file};
212 if ($file && $_ eq $EOF) { # Heredoc end
216 if ($file) { # Heredoc content
217 push @{$file}, "$_\n";
220 $_ =~ s/^[[:space:]]*// if ($continuation);
221 if (/\\$/) { # Line continuation
222 $line .= substr($_, 0, length($_)-1);
228 $line .= " $append" if ($append && scalar(@$modules) == 0);
230 if ($line =~ /^([^ ]*)(.*?)[[:space:]]*< ?(.*)$/) { # Command substitution
231 push @$modules, "$1$2";
232 push @$generated, {filename => $1, command => $3};
236 push @$modules, $line;
240 #print Dumper(\@scripts);
246 sub generate_configs($$$) {
247 my ($base, $generated, $filename) = @_;
248 if ($base) { $base = "$base/"; };
249 foreach my $g(@$generated) {
250 if (exists $$g{content}) {
251 my $config = $$g{content};
252 my $fn = $$g{filename};
253 open(my $f, '>', $fn) || die("$fn: $!");
254 map { s|\brom://([^ ]*)|$rom_prefix$base$1|g; print $f "$_"; } @{$config};
256 print "novaboot: Created $fn\n";
257 } elsif (exists $$g{command} && ! $no_file_gen) {
258 $ENV{SRCDIR} = dirname(File::Spec->rel2abs( $filename, $invocation_dir ));
259 system_verbose("( $$g{command} ) > $$g{filename}");
264 sub generate_grub_config($$$$;$)
266 my ($filename, $title, $base, $modules_ref, $preamble) = @_;
267 if ($base) { $base = "$base/"; };
268 open(my $fg, '>', $filename) or die "$filename: $!";
269 print $fg "$preamble\n" if $preamble;
270 my $endmark = ($serial || defined $iprelay) ? ';' : '';
271 print $fg "title $title$endmark\n" if $title;
272 #print $fg "root $base\n"; # root doesn't really work for (nd)
274 foreach (@$modules_ref) {
277 my ($kbin, $kcmd) = split(' ', $_, 2);
278 $kcmd = '' if !defined $kcmd;
279 print $fg "kernel ${base}$kbin $kcmd\n";
281 s|\brom://([^ ]*)|$rom_prefix$base$1|g; # Translate rom:// files - needed for vdisk parameter of sigma0
282 print $fg "module $base$_\n";
286 print("novaboot: Created $CFG::builddir/$filename\n");
290 sub generate_grub2_config($$$$;$$)
292 my ($filename, $title, $base, $modules_ref, $preamble, $prolog) = @_;
293 if ($base && substr($base,-1,1) ne '/') { $base = "$base/"; };
294 open(my $fg, '>', $filename) or die "$filename: $!";
295 print $fg "$preamble\n" if $preamble;
296 my $endmark = ($serial || defined $iprelay) ? ';' : '';
297 $title ||= 'novaboot';
298 print $fg "menuentry $title$endmark {\n";
299 print $fg "$prolog\n" if $prolog;
301 foreach (@$modules_ref) {
304 my ($kbin, $kcmd) = split(' ', $_, 2);
305 $kcmd = '' if !defined $kcmd;
306 print $fg " multiboot ${base}$kbin $kcmd\n";
309 # GRUB2 doesn't pass filename in multiboot info so we have to duplicate it here
310 $_ = join(' ', ($args[0], @args));
311 s|\brom://|$rom_prefix|g; # We do not need to translate path for GRUB2
312 print $fg " module $base$_\n";
317 print("novaboot: Created $CFG::builddir/$filename\n");
321 sub generate_pulsar_config($$)
323 my ($filename, $modules_ref) = @_;
324 open(my $fg, '>', $filename) or die "$filename: $!";
325 print $fg "root $CFG::pulsar_root\n" if $CFG::pulsar_root;
328 foreach (@$modules_ref) {
331 ($kbin, $kcmd) = split(' ', $_, 2);
332 $kcmd = '' if !defined $kcmd;
335 s|\brom://|$rom_prefix|g;
336 print $fg "load $_\n";
339 # Put kernel as last - this is needed for booting Linux and has no influence on non-Linux OSes
340 print $fg "exec $kbin $kcmd\n";
342 print("novaboot: Created $CFG::builddir/$filename\n");
348 print "novaboot: Running: ".join(' ', map("'$_'", @_))."\n";
352 sub system_verbose($)
355 print "novaboot: Running: $cmd\n";
356 my $ret = system($cmd);
357 if ($ret & 0x007f) { die("Command terminated by a signal"); }
358 if ($ret & 0xff00) {die("Command exit with non-zero exit code"); }
359 if ($ret) { die("Command failure $ret"); }
364 if (exists $variables->{WVDESC}) {
365 print "Testing \"$variables->{WVDESC}\" in $last_fn:\n";
366 } elsif ($last_fn =~ /\.wv$/) {
367 print "Testing \"all\" in $last_fn:\n";
370 ## Handle reset and power on/off
373 if (defined $iprelay) {
374 $CFG::iprelay_addr =~ /([.0-9]+)(:([0-9]+))?/;
377 my $paddr = sockaddr_in($port, inet_aton($addr));
378 my $proto = getprotobyname('tcp');
379 socket($IPRELAY, PF_INET, SOCK_STREAM, $proto) || die "socket: $!";
380 print "novaboot: Connecting to IP relay... ";
381 connect($IPRELAY, $paddr) || die "connect: $!";
383 $IPRELAY->autoflush(1);
386 print $IPRELAY "\xFF\xF6";
388 local $SIG{ALRM} = sub { die "Relay AYT timeout"; };
389 my $ayt_reponse = "";
390 my $read = sysread($IPRELAY, $ayt_reponse, 100);
394 print "$ayt_reponse\n";
395 if ($ayt_reponse =~ /<iprelayd: not connected/) {
403 my ($relay, $onoff) = @_;
404 die unless ($relay == 1 || $relay == 2);
406 my $cmd = ($relay == 1 ? 0x5 : 0x6) | ($onoff ? 0x20 : 0x10);
407 return "\xFF\xFA\x2C\x32".chr($cmd)."\xFF\xF0";
411 my ($relay, $onoff) = @_;
412 die unless ($relay == 1 || $relay == 2);
413 my $cmd = ($relay == 1 ? 0xdf : 0xbf) | ($onoff ? 0x00 : 0xff);
414 return "\xFF\xFA\x2C\x97".chr($cmd)."\xFF\xF0";
418 my ($relay, $onoff, $can_giveup) = @_;
419 my $confirmation = '';
420 print $IPRELAY relaycmd($relay, $onoff);
422 # We use non-blocking I/O and polling here because for some
423 # reason read() on blocking FD returns only after all
424 # requested data is available. If we get during the first
425 # read() only a part of confirmation, we may get the rest
426 # after the system boots and print someting, which may be too
428 $IPRELAY->blocking(0);
430 alarm(20); # Timeout in seconds
432 local $SIG{ALRM} = sub {
433 if ($can_giveup) { print("Relay confirmation timeout - ignoring\n"); $giveup = 1;}
434 else {die "Relay confirmation timeout";}
437 while (($index=index($confirmation, relayconf($relay, $onoff))) < 0 && !$giveup) {
438 my $read = read($IPRELAY, $confirmation, 70, length($confirmation));
439 if (!defined($read)) {
440 die("IP relay: $!") unless $! == EAGAIN;
444 #use MIME::QuotedPrint;
445 #print "confirmation = ".encode_qp($confirmation)."\n";
448 $IPRELAY->blocking(1);
452 if ($iprelay && ($iprelay eq "on" || $iprelay eq "off")) {
453 relay(1, 1); # Press power button
454 if ($iprelay eq "on") {
455 usleep(100000); # Short press
457 usleep(6000000); # Long press to switch off
459 print $IPRELAY relay(1, 0);
463 ## Figure out the location of builddir and chdir() there
465 $CFG::builddir = $builddir;
467 if (! defined $CFG::builddir) {
468 $CFG::builddir = ( $gittop || $ENV{'HOME'}."/nul" ) . "/build";
469 if (! -d $CFG::builddir) {
470 $CFG::builddir = $ENV{SRCDIR} = dirname(File::Spec->rel2abs( ${$scripts[0]}{filename}, $invocation_dir ));
475 chdir($CFG::builddir) or die "Can't change directory to $CFG::builddir: $!";
476 print "novaboot: Entering directory `$CFG::builddir'\n";
478 ## File generation phase
479 my (%files_iso, $menu_iso, $config_name, $filename);
481 foreach my $script (@scripts) {
482 $filename = $$script{filename};
483 $modules = $$script{modules};
484 $generated = $$script{generated};
485 $variables = $$script{variables};
487 ($config_name = $filename) =~ s#.*/##;
488 $config_name = $config_name_opt if (defined $config_name_opt);
491 if (exists $variables->{KERNEL}) {
492 $kernel = $variables->{KERNEL};
494 if ($CFG::hypervisor) {
495 $kernel = $CFG::hypervisor . " ";
496 if (exists $variables->{HYPERVISOR_PARAMS}) {
497 $kernel .= $variables->{HYPERVISOR_PARAMS};
499 $kernel .= $CFG::hypervisor_params;
503 @$modules = ($kernel, @$modules) if $kernel;
504 @$modules = (@CFG::chainloaders, @$modules);
505 @$modules = ("bin/boot/bender", @$modules) if ($bender || defined $ENV{'NOVABOOT_BENDER'});
508 ($prefix = $grub_prefix) =~ s/\$NAME/$config_name/ if defined $grub_prefix;
509 $prefix ||= $CFG::builddir;
510 # TODO: use $grub_prefix as first parameter if some switch is given
511 generate_configs('', $generated, $filename);
513 ### Generate bootloader configuration files
515 my @bootloader_configs;
516 push @bootloader_configs, generate_grub_config($grub_config, $config_name, $prefix, $modules, $grub_preamble) if (defined $grub_config);
517 push @bootloader_configs, generate_grub2_config($grub2_config, $config_name, $prefix, $modules, $grub_preamble, $grub2_prolog) if (defined $grub2_config);
518 push @bootloader_configs, generate_pulsar_config($pulsar_config = "config-$CFG::pulsar_mac", $modules) if (defined $pulsar);
521 if (defined $scons) {
522 my @files = map({ ($file) = m/([^ ]*)/; $file; } @$modules);
523 # Filter-out generated files
524 my @to_build = grep({ my $file = $_; !scalar(grep($file eq $$_{filename}, @$generated)) } @files);
525 system_verbose($CFG::scons." ".join(" ", @to_build));
528 ### Copy files (using rsync)
529 if (defined $server) {
530 ($server = $CFG::server) =~ s/\$NAME/$config_name/;
532 my ($hostname, $path) = split(":", $server, 2);
533 if (! defined $path) {
537 my $files = join(" ", map({ ($file) = m/([^ ]*)/; $file; } ( @$modules, @bootloader_configs)));
538 map({ my $file = (split)[0]; die "$file: $!" if ! -f $file; } @$modules);
539 my $istty = -t STDOUT && ($ENV{'TERM'} || 'dumb') ne 'dumb';
540 my $progress = $istty ? "--progress" : "";
541 system_verbose("rsync $progress -RLp $rsync_flags $files $server");
542 if ($CFG::server =~ m|/\$NAME$| && $concat) {
543 my $cmd = join("; ", map { "( cd $path/.. && cat */$_ > $_ )" } @bootloader_configs);
544 system_verbose($hostname ? "ssh $hostname '$cmd'" : $cmd);
548 ### Prepare ISO image generation
549 if (defined $iso_image) {
550 generate_configs("(cd)", $generated, $filename);
552 generate_grub_config(\$menu, $config_name, "(cd)", $modules);
553 $menu_iso .= "$menu\n";
554 map { ($file,undef) = split; $files_iso{$file} = 1; } @$modules;
558 ## Generate ISO image
559 if (defined $iso_image) {
560 open(my $fh, ">menu-iso.lst");
561 print $fh "timeout 5\n\n$menu_iso";
563 my $files = "boot/grub/menu.lst=menu-iso.lst " . join(" ", map("$_=$_", keys(%files_iso)));
564 $iso_image ||= "$config_name.iso";
565 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");
566 print("ISO image created: $CFG::builddir/$iso_image\n");
569 ## Boot the system using various methods and send serial output to stdout
571 if (scalar(@scripts) > 1 && ( defined $dhcp_tftp || defined $serial || defined $iprelay)) {
572 die "You cannot do this with multiple scripts simultaneously";
575 if ($variables->{WVTEST_TIMEOUT}) {
576 print "wvtest: timeout ", $variables->{WVTEST_TIMEOUT}, "\n";
581 $str =~ s/^\s+|\s+$//g;
587 if (!(defined $dhcp_tftp || defined $serial || defined $iprelay || defined $server || defined $iso_image)) {
589 if (!$qemu && $variables->{QEMU}) {
590 @qemu_flags = split(" ", $variables->{QEMU});
591 $CFG::qemu = shift(@qemu_flags);
594 @qemu_flags = split(/ +/, trim($variables->{QEMU_FLAGS})) if exists $variables->{QEMU_FLAGS};
595 @qemu_flags = split(/ +/, trim($qemu_flags_cmd)) if $qemu_flags_cmd;
596 push(@qemu_flags, split(/ +/, trim($qemu_append)));
598 if (defined $iso_image) {
599 # Boot NOVA with grub (and test the iso image)
600 push(@qemu_flags, ('-cdrom', "$config_name.iso"));
602 # Boot NOVA without GRUB
604 # Non-patched qemu doesn't like commas, but NUL can live with pluses instead of commans
605 foreach (@$modules) {s/,/+/g;}
606 generate_configs("", $generated, $filename);
608 my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);
609 $kcmd = '' if !defined $kcmd;
611 @$modules = map { if (/\.dtb$/) { $dtb=$_; (); } else { $_ } } @$modules;
612 my $initrd = join ",", @$modules;
614 push(@qemu_flags, ('-kernel', $kbin, '-append', $kcmd));
615 push(@qemu_flags, ('-initrd', $initrd)) if $initrd;
616 push(@qemu_flags, ('-dtb', $dtb)) if $dtb;
618 push(@qemu_flags, qw(-serial stdio)); # Redirect serial output (for collecting test restuls)
619 exec_verbose(($CFG::qemu, '-name', $config_name, @qemu_flags));
622 ### Local DHCPD and TFTPD
624 my ($dhcpd_pid, $tftpd_pid);
626 if (defined $dhcp_tftp)
628 generate_configs("(nd)", $generated, $filename);
629 system_verbose('mkdir -p tftpboot');
630 generate_grub_config("tftpboot/os-menu.lst", $config_name, "(nd)", \@$modules, "timeout 0");
631 open(my $fh, '>', 'dhcpd.conf');
632 my $mac = `cat /sys/class/net/eth0/address`;
634 print $fh "subnet 10.23.23.0 netmask 255.255.255.0 {
635 range 10.23.23.10 10.23.23.100;
636 filename \"bin/boot/grub/pxegrub.pxe\";
637 next-server 10.23.23.1;
640 hardware ethernet $mac;
641 fixed-address 10.23.23.1;
644 system_verbose("sudo ip a add 10.23.23.1/24 dev eth0;
645 sudo ip l set dev eth0 up;
646 sudo touch dhcpd.leases");
649 if ($dhcpd_pid == 0) {
650 # This way, the spawned server are killed when this script is killed.
651 exec_verbose("sudo dhcpd -d -cf dhcpd.conf -lf dhcpd.leases -pf dhcpd.pid");
654 if ($tftpd_pid == 0) {
655 exec_verbose("sudo in.tftpd --foreground --secure -v -v -v $CFG::builddir");
657 $SIG{TERM} = sub { print "CHILDS KILLED\n"; kill 15, $dhcpd_pid, $tftpd_pid; };
660 ### Serial line or IP relay
662 if ($serial || defined $iprelay) {
664 if (defined $iprelay) {
665 print "novaboot: Reseting the test box... ";
666 relay(2, 1, 1); # Reset the machine
673 system("stty -F $serial raw -crtscts -onlcr 115200");
674 open($CONN, "+<", $serial) || die "open $serial: $!";
677 if (!defined $dhcp_tftp && $CFG::grub_keys) {
678 # Control grub via serial line
679 print "Waiting for GRUB's serial output... ";
681 if (/Press any key to continue/) { print $CONN "\n"; last; }
683 $CFG::grub_keys =~ s/\$NAME/$config_name;/;
684 my @characters = split(//, $CFG::grub_keys);
685 foreach (@characters) {
687 usleep($_ eq "\n" ? 100000 : 10000);
692 # Pass the NOVA output to stdout.
696 kill 15, $dhcpd_pid, $tftpd_pid if ($dhcp_tftp);
700 ### Wait for dhcpc or tftpd
701 if (defined $dhcp_tftp) {
703 if ($pid == $dhcpd_pid) { print "dhcpd exited!\n"; }
704 elsif ($pid == $tftpd_pid) { print "tftpd exited!\n"; }
705 else { print "wait returned: $pid\n"; }
706 kill(15, 0); # Kill current process group i.e. all remaining children
713 novaboot - A tool for booting various operating systems on various hardware or in qemu
717 B<novaboot> [ options ] [--] script...
719 B<./script> [ options ]
725 This program makes it easier to boot NOVA or other operating system
726 (OS) in different environments. It reads a so called novaboot script
727 and uses it either to boot the OS in an emulator (e.g. in qemu) or to
728 generate the configuration for a specific bootloader and optionally to
729 copy the necessary binaries and other needed files to proper
730 locations, perhaps on a remote server. In case the system is actually
731 booted, its serial output is redirected to standard output if that is
734 A typical way of using novaboot is to make the novaboot script
735 executable and set its first line to I<#!/usr/bin/env novaboot>. Then,
736 booting a particular OS configuration becomes the same as executing a
737 local program - the novaboot script.
739 With C<novaboot> you can:
745 Run an OS in Qemu. This is the default action when no other action is
746 specified by command line switches. Thus running C<novaboot ./script>
747 (or C<./script> as described above) will run Qemu and make it boot the
748 configuration specified in the I<script>.
752 Create a bootloader configuration file (currently supported
753 bootloaders are GRUB, GRUB2 and Pulsar) and copy it with all other
754 files needed for booting to another, perhaps remote, location.
756 ./script --server --iprelay
758 This command copies files to a TFTP server specified in the
759 configuration file and uses TCP/IP-controlled relay to reset the test
760 box and receive its serial output.
764 Run DHCP and TFTP server on developer's machine to PXE-boot NOVA from
769 When a PXE-bootable machine is connected via Ethernet to developer's
770 machine, it will boot the configuration described in I<script>.
774 Create bootable ISO images. E.g.
776 novaboot --iso -- script1 script2
778 The created ISO image will have GRUB bootloader installed on it and
779 the boot menu will allow selecting between I<script1> and I<script2>
784 =head1 PHASES AND OPTIONS
786 Novaboot perform its work in several phases. Each phase can be
787 influenced by several options, certain phases can be skipped. The list
788 of phases with the corresponding options follows.
790 =head2 Command line processing phase
794 =item -c, --config=<filename>
796 Use a different configuration file than the default one (i.e.
801 Dumps current configuration to stdout end exits. Useful as an initial
802 template for a configuration file.
806 Print short (B<-h>) or long (B<--help>) help.
808 =item -t, --target=<target>
810 This option serves as a user configurable shortcut for other novaboot
811 options. The effect of this option is the same as the options stored
812 in the C<%targets> configuration variable under key I<target>. See
813 also L</"CONFIGURATION FILE">.
817 =head2 Script preprocessing phase
819 This phases allows to modify the parsed novaboot script before it is
820 used in the later phases.
824 =item -a, --append=<parameters>
826 Appends a string to the first "filename" line in the novaboot script.
827 This can be used to append parameters to the kernel's or root task's
832 Use F<bender> chainloader. Bender scans the PCI bus for PCI serial
833 ports and stores the information about them in the BIOS data area for
838 Prints the content of the novaboot script after removing comments and
839 evaluating all I<--scriptmod> expressions. Exit after reading (and
842 =item --scriptmod=I<perl expression>
844 When novaboot script is read, I<perl expression> is executed for every
845 line (in $_ variable). For example, C<novaboot
846 --scriptmod=s/sigma0/omega6/g> replaces every occurrence of I<sigma0>
847 in the script with I<omega6>.
849 When this option is present, it overrides I<$script_modifier> variable
850 from the configuration file, which has the same effect. If this option
851 is given multiple times all expressions are evaluated in the command
856 Strip I<rom://> prefix from command lines and generated config files.
857 The I<rom://> prefix is used by NUL. For NRE, it has to be stripped.
861 =head2 File generation phase
863 In this phase, files needed for booting are generated in a so called
864 I<build directory> (see TODO). In most cases configuration for a
865 bootloader is generated automatically by novaboot. It is also possible
866 to generate other files using I<heredoc> or I<"<"> syntax in novaboot
867 scripts. Finally, binaries can be generated in this phases by running
872 =item --build-dir=<directory>
874 Overrides the default build directory location.
876 The default build directory location is determined as follows:
878 If there is a configuration file, the value specified in the
879 I<$builddir> variable is used. Otherwise, if the current working
880 directory is inside git work tree and there is F<build> directory at
881 the top of that tree, it is used. Otherwise, if directory
882 F<~/nul/build> exists, it is used. Otherwise, it is the directory that
883 contains the first processed novaboot script.
885 =item -g, --grub[=I<filename>]
887 Generates grub bootloader menu file. If the I<filename> is not
888 specified, F<menu.lst> is used. The I<filename> is relative to the
889 build directory (see B<--build-dir>).
891 =item --grub-preamble=I<prefix>
893 Specifies the I<preable> that is at the beginning of the generated
894 GRUB or GRUB2 config files. This is useful for specifying GRUB's
897 =item --grub-prefix=I<prefix>
899 Specifies I<prefix> that is put in front of every file name in GRUB's
900 F<menu.lst>. The default value is the absolute path to the build directory.
902 If the I<prefix> contains string $NAME, it will be replaced with the
903 name of the novaboot script (see also B<--name>).
905 =item --grub2[=I<filename>]
907 Generate GRUB2 menuentry in I<filename>. If I<filename> is not
908 specified F<grub.cfg> is used. The content of the menuentry can be
909 customized with B<--grub-preable>, B<--grub2-prolog> or
910 B<--grub_prefix> options.
912 In order to use the the generated menuentry on your development
913 machine that uses GRUB2, append the following snippet to
914 F</etc/grub.d/40_custom> file and regenerate your grub configuration,
915 i.e. run update-grub on Debian/Ubuntu.
917 if [ -f /path/to/nul/build/grub.cfg ]; then
918 source /path/to/nul/build/grub.cfg
921 =item --grub2-prolog=I<prolog>
923 Specifies text I<preable> that is put at the begiging of the entry
926 =item --name=I<string>
928 Use the name I<string> instead of the name of the novaboot script.
929 This name is used for things like a title of grub menu or for the
930 server directory where the boot files are copied to.
934 Do not generate files on the fly (i.e. "<" syntax) except for the
935 files generated via "<<WORD" syntax.
937 =item -p, --pulsar[=mac]
939 Generates pulsar bootloader configuration file whose name is based on
940 the MAC address specified either on the command line or taken from
941 I<.novaboot> configuration file.
945 =head2 File deployment phase
947 In some setups, it is necessary to copy the files needed for booting
948 to a particular location, e.g. to a TFTP boot server or to the
953 =item -d, --dhcp-tftp
955 Turns your workstation into a DHCP and TFTP server so that NOVA
956 can be booted via PXE BIOS on a test machine directly connected by
957 a plain Ethernet cable to your workstation.
959 The DHCP and TFTP servers require root privileges and C<novaboot>
960 uses C<sudo> command to obtain those. You can put the following to
961 I</etc/sudoers> to allow running the necessary commands without
964 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
965 your_login ALL=NOPASSWD: NOVABOOT
967 =item -i, --iso[=filename]
969 Generates the ISO image that boots NOVA system via GRUB. If no filename
970 is given, the image is stored under I<NAME>.iso, where I<NAME> is the name
971 of the novaboot script (see also B<--name>).
973 =item --server[=[[user@]server:]path]
975 Copy all files needed for booting to another location (implies B<-g>
976 unless B<--grub2> is given). The files will be copied (by B<rsync>
977 tool) to the directory I<path>. If the I<path> contains string $NAME,
978 it will be replaced with the name of the novaboot script (see also
983 If B<--server> is used and its value ends with $NAME, then after
984 copying the files, a new bootloader configuration file (e.g. menu.lst)
985 is created at I<path-wo-name>, i.e. the path specified by B<--server>
986 with $NAME part removed. The content of the file is created by
987 concatenating all files of the same name from all subdirectories of
988 I<path-wo-name> found on the "server".
990 =item --rsync-flags=I<flags>
992 Specifies which I<flags> are appended to F<rsync> command line when
993 copying files as a result of I<--server> option.
995 =item --scons[=scons command]
997 Runs I<scons> to build files that are not generated by novaboot
1002 =head2 Target power-on and reset phase
1006 =item --iprelay[=addr or cmd]
1008 If no I<cmd> is given, use IP relay to reset the machine and to get
1009 the serial output. The IP address of the relay is given by I<addr>
1010 parameter if specified or by $iprelay_addr variable in the
1013 If I<cmd> is one of "on" or "off", the IP relay is used to press power
1014 button for a short (in case of "on") or long (in case of "off") time.
1015 Then, novaboot exits.
1017 Note: This option is expected to work with HWG-ER02a IP relays.
1021 Synonym for --iprelay=on/off.
1023 =item -Q, --qemu=I<qemu-binary>
1025 The name of qemu binary to use. The default is 'qemu'.
1027 =item --qemu-append=I<flags>
1029 Append I<flags> to the default qemu flags (QEMU_FLAGS variable or
1030 C<-cpu coreduo -smp 2>).
1032 =item -q, --qemu-flags=I<flags>
1034 Replace the default qemu flags (QEMU_FLAGS variable or C<-cpu coreduo
1035 -smp 2>) with I<flags> specified here.
1039 =head2 Interaction with the bootloader on the target
1041 See B<--serial>. There will be new options soon.
1043 =head2 Target's output reception phase
1047 =item -s, --serial[=device]
1049 Use serial line to control GRUB bootloader and to see the output
1050 serial output of the machine. The default value is F</dev/ttyUSB0>.
1054 See also B<--iprelay>.
1056 =head2 Termination phase
1058 Daemons that were spwned (F<dhcpd> and F<tftpd>) are killed here.
1060 =head1 NOVABOOT SCRIPT SYNTAX
1062 The syntax tries to mimic POSIX shell syntax. The syntax is defined with the following rules.
1064 Lines starting with "#" are ignored.
1066 Lines that end with "\" are concatenated with the following line after
1067 removal of the final "\" and leading whitespace of the following line.
1069 Lines in the form I<VARIABLE=...> (i.e. matching '^[A-Z_]+=' regular
1070 expression) assign values to internal variables. See VARIABLES
1073 Otherwise, the first word on the line represents the filename
1074 (relative to the build directory (see B<--build-dir>) of the module to
1075 load and the remaining words are passed as the command line
1078 When the line ends with "<<WORD" then the subsequent lines until the
1079 line containing only WORD are copied literally to the file named on
1082 When the line ends with "< CMD" the command CMD is executed with
1083 C</bin/sh> and its standard output is stored in the file named on that
1084 line. The SRCDIR variable in CMD's environment is set to the absolute
1085 path of the directory containing the interpreted novaboot script.
1088 #!/usr/bin/env novaboot
1089 WVDESC=Example program
1090 bin/apps/sigma0.nul S0_DEFAULT script_start:1,1 \
1091 verbose hostkeyb:0,0x60,1,12,2
1093 hello.nulconfig <<EOF
1094 sigma0::mem:16 name::/s0/log name::/s0/timer name::/s0/fs/rom ||
1095 rom://bin/apps/hello.nul
1098 This example will load three modules: sigma0.nul, hello.nul and
1099 hello.nulconfig. sigma0 gets some command line parameters and
1100 hello.nulconfig file is generated on the fly from the lines between
1105 The following variables are interpreted in the novaboot script:
1111 Description of the wvtest-compliant program.
1113 =item WVTEST_TIMEOUT
1115 The timeout in seconds for WvTest harness. If no complete line appears
1116 in the test output within the time specified here, the test fails. It
1117 is necessary to specify this for long running tests that produce no
1118 intermediate output.
1122 Use a specific qemu binary (can be overriden with B<-Q>) and flags
1123 when booting this script under qemu. If QEMU_FLAGS variable is also
1124 specified flags specified in QEMU variable are replaced by those in
1129 Use specific qemu flags (can be overriden with B<-q>).
1131 =item HYPERVISOR_PARAMS
1133 Parameters passed to hypervisor. The default value is "serial", unless
1134 overriden in configuration file.
1138 The kernel to use instead of NOVA hypervisor specified in the
1139 configuration file. The value should contain the name of the kernel
1140 image as well as its command line parameters. If this variable is
1141 defined and non-empty, the variable HYPERVISOR_PARAMS is not used.
1145 =head1 CONFIGURATION FILE
1147 Novaboot can read its configuration from a file. Configuration file
1148 was necessary in early days of novaboot. Nowadays, an attempt is made
1149 to not use the configuration file because it makes certain novaboot
1150 scripts unusable on systems without (or with different) configuration
1151 file. The only recommended use of the configuration file is to specify
1152 custom_options (see bellow).
1154 If you decide to use the configuration file, its default location is
1155 ~/.novaboot, other location can be specified with the B<-c> switch or
1156 with the NOVABOOT_CONFIG environment variable. The configuration file
1157 has perl syntax and should set values of certain Perl variables. The
1158 current configuration can be dumped with the B<--dump-config> switch.
1159 Some configuration variables can be overriden by environment variables
1160 (see below) or by command line switches.
1162 Documentation of some configuration variables follows:
1168 Custom chainloaders to load before hypervisor and files specified in
1169 novaboot script. E.g. ('bin/boot/bender promisc', 'bin/boot/zapp').
1173 Hash of shortcuts to be used with the B<--target> option. If the hash
1174 contains, for instance, the following pair of values
1176 'mybox' => '--server=boot:/tftproot --serial=/dev/ttyUSB0 --grub',
1178 then the following two commands are equivalent:
1180 ./script --server=boot:/tftproot --serial=/dev/ttyUSB0 --grub
1185 =head1 ENVIRONMENT VARIABLES
1187 Some options can be specified not only via config file or command line
1188 but also through environment variables. Environment variables override
1189 the values from configuration file and command line parameters
1190 override the environment variables.
1194 =item NOVABOOT_CONFIG
1196 A name of default novaboot configuration file.
1198 =item NOVABOOT_BENDER
1200 Defining this variable has the same meaning as B<--bender> option.
1202 =item NOVABOOT_IPRELAY
1204 The IP address (and optionally the port) of the IP relay. This
1205 overrides $iprelay_addr variable from the configuration file.
1211 Michal Sojka <sojka@os.inf.tu-dresden.de>