3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation, either version 2 of the License, or
6 # (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18 use Getopt::Long qw(GetOptionsFromString);
23 use Time::HiRes("usleep");
27 use POSIX qw(:errno_h);
28 use Cwd qw(getcwd abs_path);
33 my $invocation_dir = getcwd();
35 chomp(my $gittop = `git rev-parse --show-toplevel 2>/dev/null`);
37 ## Configuration file handling
39 # Default configuration
40 $CFG::hypervisor = "";
41 $CFG::hypervisor_params = "serial";
42 $CFG::genisoimage = "genisoimage";
43 $CFG::iprelay_addr = '141.76.48.80:2324'; #'141.76.48.252';
45 @CFG::chainloaders = (); #('bin/boot/bender promisc'); # TODO: convert to option
46 $CFG::pulsar_root = ''; # TODO: convert to option
47 $CFG::pulsar_mac = '52-54-00-12-34-56';
49 "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',
50 "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',
51 "localhost" => '--scriptmod=s/console=tty[A-Z0-9,]+// --server=/boot/novaboot/$NAME --grub2 --grub-prefix=/boot/novaboot/$NAME --grub2-prolog=" set root=\'(hd0,msdos1)\'"',
53 $CFG::scons = "scons -j2";
55 my @qemu_flags = qw(-cpu coreduo -smp 2);
59 package CFG; # Put config data into a separate namespace
64 die("ERROR: Failure compiling '$cfg' - $@");
65 } elsif (! defined($rc)) {
66 die("ERROR: Failure reading '$cfg' - $!");
68 die("ERROR: Failure processing '$cfg'");
71 print "novaboot: Read $cfg\n";
76 # We don't use $0 here, because it points to the novaboot itself and
77 # not to the novaboot script. The problem with this approach is that
78 # when a script is run as "novaboot <options> <script>" then $ARGV[0]
79 # contains the first option. Hence the -f check.
80 my $dir = abs_path($invocation_dir . ((-f $ARGV[0]) ? '/'.dirname($ARGV[0]) : ''));
81 while (-d $dir && $dir ne "/") {
82 push @cfgs, "$dir/.novaboot" if -r "$dir/.novaboot";
83 $dir = abs_path($dir."/..");
86 my $cfg = $ENV{'NOVABOOT_CONFIG'};
87 Getopt::Long::Configure(qw/no_ignore_case pass_through/);
88 GetOptions ("config|c=s" => \$cfg);
89 read_config($_) foreach $cfg or reverse @cfgs;
91 ## Command line handling
93 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);
96 $rom_prefix = 'rom://';
98 Getopt::Long::Configure(qw/no_ignore_case no_pass_through/);
101 "append|a=s" => \$append,
102 "bender|b" => \$bender,
103 "build-dir=s" => \$builddir,
104 "concat" => \$concat,
105 "dhcp-tftp|d" => \$dhcp_tftp,
106 "dump" => \$dump_opt,
107 "dump-config" => \$dump_config,
108 "grub|g:s" => \$grub_config,
109 "grub-preamble=s"=> \$grub_preamble,
110 "grub-prefix=s" => \$grub_prefix,
111 "grub2:s" => \$grub2_config,
112 "grub2-prolog=s" => \$grub2_prolog,
113 "iprelay:s" => \$iprelay,
114 "iso|i:s" => \$iso_image,
115 "name=s" => \$config_name_opt,
116 "no-file-gen" => \$no_file_gen,
119 "pulsar|p:s" => \$pulsar,
120 "qemu|Q=s" => \$qemu,
121 "qemu-append:s" => \$qemu_append,
122 "qemu-flags|q=s" => \$qemu_flags_cmd,
123 "rsync-flags=s" => \$rsync_flags,
124 "scons:s" => \$scons,
125 "scriptmod=s" => \@scriptmod,
126 "serial|s:s" => \$serial,
127 "server:s" => \$server,
128 "strip-rom" => sub { $rom_prefix = ''; },
129 "target|t=s" => sub { my ($opt_name, $opt_value) = @_;
130 exists $CFG::targets{$opt_value} or die("Unknown target '$opt_value' (valid targets are: ".join(", ", sort keys(%CFG::targets)).")");
131 GetOptionsFromString($CFG::targets{$opt_value}, %opt_spec); },
135 GetOptions %opt_spec or pod2usage(2);
136 pod2usage(1) if $help;
137 pod2usage(-exitstatus => 0, -verbose => 2) if $man;
139 ### Sanitize configuration
141 $CFG::iprelay_addr = $ENV{'NOVABOOT_IPRELAY'} if $ENV{'NOVABOOT_IPRELAY'};
142 if ($iprelay && $iprelay ne "on" && $iprelay ne "off") { $CFG::iprelay_addr = $iprelay; }
144 if (defined $config_name_opt && scalar(@ARGV) > 1) { die "You cannot use --name with multiple scripts"; }
146 if ($qemu) { $CFG::qemu = $qemu; }
150 $CFG::pulsar_mac = $pulsar;
153 if ($scons) { $CFG::scons = $scons; }
155 ### Dump sanitized configuration (if requested)
159 $Data::Dumper::Indent=0;
160 print "# This file is in perl syntax.\n";
161 foreach my $key(sort(keys(%CFG::))) { # See "Symbol Tables" in perlmod(1)
162 if (defined ${$CFG::{$key}}) { print Data::Dumper->Dump([${$CFG::{$key}}], ["*$key"]); }
163 if ( @{$CFG::{$key}}) { print Data::Dumper->Dump([\@{$CFG::{$key}}], ["*$key"]); }
164 if ( %{$CFG::{$key}}) { print Data::Dumper->Dump([\%{$CFG::{$key}}], ["*$key"]); }
171 if (defined $serial) {
172 $serial ||= "/dev/ttyUSB0";
175 if (defined $grub_config) {
176 $grub_config ||= "menu.lst";
179 if (defined $grub2_config) {
180 $grub2_config ||= "grub.cfg";
183 if ($on_opt) { $iprelay="on"; }
184 if ($off_opt) { $iprelay="off"; }
186 ## Parse the novaboot script(s)
192 my ($modules, $variables, $generated, $continuation);
194 if ($ARGV ne $last_fn) { # New script
195 die "Missing EOF in $last_fn" if $file;
196 die "Unfinished line in $last_fn" if $line;
198 push @scripts, { 'filename' => $ARGV,
199 'modules' => $modules = [],
200 'variables' => $variables = {},
201 'generated' => $generated = []};
205 next if /^#/ || /^\s*$/; # Skip comments and empty lines
207 foreach my $mod(@scriptmod) { eval $mod; }
209 print "$_\n" if $dump_opt;
211 if (/^([A-Z_]+)=(.*)$/) { # Internal variable
212 $$variables{$1} = $2;
215 if (/^([^ ]*)(.*?)[[:space:]]*<<([^ ]*)$/) { # Heredoc start
216 push @$modules, "$1$2";
218 push @$generated, {filename => $1, content => $file};
222 if ($file && $_ eq $EOF) { # Heredoc end
226 if ($file) { # Heredoc content
227 push @{$file}, "$_\n";
230 $_ =~ s/^[[:space:]]*// if ($continuation);
231 if (/\\$/) { # Line continuation
232 $line .= substr($_, 0, length($_)-1);
238 $line .= " $append" if ($append && scalar(@$modules) == 0);
240 if ($line =~ /^([^ ]*)(.*?)[[:space:]]*< ?(.*)$/) { # Command substitution
241 push @$modules, "$1$2";
242 push @$generated, {filename => $1, command => $3};
246 push @$modules, $line;
250 #print Dumper(\@scripts);
256 sub generate_configs($$$) {
257 my ($base, $generated, $filename) = @_;
258 if ($base) { $base = "$base/"; };
259 foreach my $g(@$generated) {
260 if (exists $$g{content}) {
261 my $config = $$g{content};
262 my $fn = $$g{filename};
263 open(my $f, '>', $fn) || die("$fn: $!");
264 map { s|\brom://([^ ]*)|$rom_prefix$base$1|g; print $f "$_"; } @{$config};
266 print "novaboot: Created $fn\n";
267 } elsif (exists $$g{command} && ! $no_file_gen) {
268 $ENV{SRCDIR} = dirname(File::Spec->rel2abs( $filename, $invocation_dir ));
269 system_verbose("( $$g{command} ) > $$g{filename}");
274 sub generate_grub_config($$$$;$)
276 my ($filename, $title, $base, $modules_ref, $preamble) = @_;
277 if ($base) { $base = "$base/"; };
278 open(my $fg, '>', $filename) or die "$filename: $!";
279 print $fg "$preamble\n" if $preamble;
280 my $endmark = ($serial || defined $iprelay) ? ';' : '';
281 print $fg "title $title$endmark\n" if $title;
282 #print $fg "root $base\n"; # root doesn't really work for (nd)
284 foreach (@$modules_ref) {
287 my ($kbin, $kcmd) = split(' ', $_, 2);
288 $kcmd = '' if !defined $kcmd;
289 print $fg "kernel ${base}$kbin $kcmd\n";
291 s|\brom://([^ ]*)|$rom_prefix$base$1|g; # Translate rom:// files - needed for vdisk parameter of sigma0
292 print $fg "module $base$_\n";
296 print("novaboot: Created $CFG::builddir/$filename\n");
300 sub generate_grub2_config($$$$;$$)
302 my ($filename, $title, $base, $modules_ref, $preamble, $prolog) = @_;
303 if ($base && substr($base,-1,1) ne '/') { $base = "$base/"; };
304 open(my $fg, '>', $filename) or die "$filename: $!";
305 print $fg "$preamble\n" if $preamble;
306 my $endmark = ($serial || defined $iprelay) ? ';' : '';
307 $title ||= 'novaboot';
308 print $fg "menuentry $title$endmark {\n";
309 print $fg "$prolog\n" if $prolog;
311 foreach (@$modules_ref) {
314 my ($kbin, $kcmd) = split(' ', $_, 2);
315 $kcmd = '' if !defined $kcmd;
316 print $fg " multiboot ${base}$kbin $kcmd\n";
319 # GRUB2 doesn't pass filename in multiboot info so we have to duplicate it here
320 $_ = join(' ', ($args[0], @args));
321 s|\brom://|$rom_prefix|g; # We do not need to translate path for GRUB2
322 print $fg " module $base$_\n";
327 print("novaboot: Created $CFG::builddir/$filename\n");
331 sub generate_pulsar_config($$)
333 my ($filename, $modules_ref) = @_;
334 open(my $fg, '>', $filename) or die "$filename: $!";
335 print $fg "root $CFG::pulsar_root\n" if $CFG::pulsar_root;
338 foreach (@$modules_ref) {
341 ($kbin, $kcmd) = split(' ', $_, 2);
342 $kcmd = '' if !defined $kcmd;
345 s|\brom://|$rom_prefix|g;
346 print $fg "load $_\n";
349 # Put kernel as last - this is needed for booting Linux and has no influence on non-Linux OSes
350 print $fg "exec $kbin $kcmd\n";
352 print("novaboot: Created $CFG::builddir/$filename\n");
358 print "novaboot: Running: ".join(' ', map("'$_'", @_))."\n";
362 sub system_verbose($)
365 print "novaboot: Running: $cmd\n";
366 my $ret = system($cmd);
367 if ($ret & 0x007f) { die("Command terminated by a signal"); }
368 if ($ret & 0xff00) {die("Command exit with non-zero exit code"); }
369 if ($ret) { die("Command failure $ret"); }
374 if (exists $variables->{WVDESC}) {
375 print "Testing \"$variables->{WVDESC}\" in $last_fn:\n";
376 } elsif ($last_fn =~ /\.wv$/) {
377 print "Testing \"all\" in $last_fn:\n";
380 ## Handle reset and power on/off
383 if (defined $iprelay) {
384 $CFG::iprelay_addr =~ /([.0-9]+)(:([0-9]+))?/;
387 my $paddr = sockaddr_in($port, inet_aton($addr));
388 my $proto = getprotobyname('tcp');
389 socket($IPRELAY, PF_INET, SOCK_STREAM, $proto) || die "socket: $!";
390 print "novaboot: Connecting to IP relay... ";
391 connect($IPRELAY, $paddr) || die "connect: $!";
393 $IPRELAY->autoflush(1);
396 print $IPRELAY "\xFF\xF6";
398 local $SIG{ALRM} = sub { die "Relay AYT timeout"; };
399 my $ayt_reponse = "";
400 my $read = sysread($IPRELAY, $ayt_reponse, 100);
404 print "$ayt_reponse\n";
405 if ($ayt_reponse =~ /<iprelayd: not connected/) {
413 my ($relay, $onoff) = @_;
414 die unless ($relay == 1 || $relay == 2);
416 my $cmd = ($relay == 1 ? 0x5 : 0x6) | ($onoff ? 0x20 : 0x10);
417 return "\xFF\xFA\x2C\x32".chr($cmd)."\xFF\xF0";
421 my ($relay, $onoff) = @_;
422 die unless ($relay == 1 || $relay == 2);
423 my $cmd = ($relay == 1 ? 0xdf : 0xbf) | ($onoff ? 0x00 : 0xff);
424 return "\xFF\xFA\x2C\x97".chr($cmd)."\xFF\xF0";
428 my ($relay, $onoff, $can_giveup) = @_;
429 my $confirmation = '';
430 print $IPRELAY relaycmd($relay, $onoff);
432 # We use non-blocking I/O and polling here because for some
433 # reason read() on blocking FD returns only after all
434 # requested data is available. If we get during the first
435 # read() only a part of confirmation, we may get the rest
436 # after the system boots and print someting, which may be too
438 $IPRELAY->blocking(0);
440 alarm(20); # Timeout in seconds
442 local $SIG{ALRM} = sub {
443 if ($can_giveup) { print("Relay confirmation timeout - ignoring\n"); $giveup = 1;}
444 else {die "Relay confirmation timeout";}
447 while (($index=index($confirmation, relayconf($relay, $onoff))) < 0 && !$giveup) {
448 my $read = read($IPRELAY, $confirmation, 70, length($confirmation));
449 if (!defined($read)) {
450 die("IP relay: $!") unless $! == EAGAIN;
454 #use MIME::QuotedPrint;
455 #print "confirmation = ".encode_qp($confirmation)."\n";
458 $IPRELAY->blocking(1);
462 if ($iprelay && ($iprelay eq "on" || $iprelay eq "off")) {
463 relay(1, 1); # Press power button
464 if ($iprelay eq "on") {
465 usleep(100000); # Short press
467 usleep(6000000); # Long press to switch off
469 print $IPRELAY relay(1, 0);
473 ## Figure out the location of builddir and chdir() there
475 $CFG::builddir = $builddir;
477 if (! defined $CFG::builddir) {
478 $CFG::builddir = ( $gittop || $ENV{'HOME'}."/nul" ) . "/build";
479 if (! -d $CFG::builddir) {
480 $CFG::builddir = $ENV{SRCDIR} = dirname(File::Spec->rel2abs( ${$scripts[0]}{filename}, $invocation_dir ));
485 chdir($CFG::builddir) or die "Can't change directory to $CFG::builddir: $!";
486 print "novaboot: Entering directory `$CFG::builddir'\n";
488 ## File generation phase
489 my (%files_iso, $menu_iso, $config_name, $filename);
491 foreach my $script (@scripts) {
492 $filename = $$script{filename};
493 $modules = $$script{modules};
494 $generated = $$script{generated};
495 $variables = $$script{variables};
497 ($config_name = $filename) =~ s#.*/##;
498 $config_name = $config_name_opt if (defined $config_name_opt);
501 if (exists $variables->{KERNEL}) {
502 $kernel = $variables->{KERNEL};
504 if ($CFG::hypervisor) {
505 $kernel = $CFG::hypervisor . " ";
506 if (exists $variables->{HYPERVISOR_PARAMS}) {
507 $kernel .= $variables->{HYPERVISOR_PARAMS};
509 $kernel .= $CFG::hypervisor_params;
513 @$modules = ($kernel, @$modules) if $kernel;
514 @$modules = (@CFG::chainloaders, @$modules);
515 @$modules = ("bin/boot/bender", @$modules) if ($bender || defined $ENV{'NOVABOOT_BENDER'});
518 ($prefix = $grub_prefix) =~ s/\$NAME/$config_name/ if defined $grub_prefix;
519 $prefix ||= $CFG::builddir;
520 # TODO: use $grub_prefix as first parameter if some switch is given
521 generate_configs('', $generated, $filename);
523 ### Generate bootloader configuration files
525 my @bootloader_configs;
526 push @bootloader_configs, generate_grub_config($grub_config, $config_name, $prefix, $modules, $grub_preamble) if (defined $grub_config);
527 push @bootloader_configs, generate_grub2_config($grub2_config, $config_name, $prefix, $modules, $grub_preamble, $grub2_prolog) if (defined $grub2_config);
528 push @bootloader_configs, generate_pulsar_config($pulsar_config = "config-$CFG::pulsar_mac", $modules) if (defined $pulsar);
531 if (defined $scons) {
532 my @files = map({ ($file) = m/([^ ]*)/; $file; } @$modules);
533 # Filter-out generated files
534 my @to_build = grep({ my $file = $_; !scalar(grep($file eq $$_{filename}, @$generated)) } @files);
535 system_verbose($CFG::scons." ".join(" ", @to_build));
538 ### Copy files (using rsync)
539 if (defined $server) {
540 (my $real_server = $server) =~ s/\$NAME/$config_name/;
542 my ($hostname, $path) = split(":", $real_server, 2);
543 if (! defined $path) {
547 my $files = join(" ", map({ ($file) = m/([^ ]*)/; $file; } ( @$modules, @bootloader_configs)));
548 map({ my $file = (split)[0]; die "$file: $!" if ! -f $file; } @$modules);
549 my $istty = -t STDOUT && ($ENV{'TERM'} || 'dumb') ne 'dumb';
550 my $progress = $istty ? "--progress" : "";
551 system_verbose("rsync $progress -RLp $rsync_flags $files $real_server");
552 if ($server =~ m|/\$NAME$| && $concat) {
553 my $cmd = join("; ", map { "( cd $path/.. && cat */$_ > $_ )" } @bootloader_configs);
554 system_verbose($hostname ? "ssh $hostname '$cmd'" : $cmd);
558 ### Prepare ISO image generation
559 if (defined $iso_image) {
560 generate_configs("(cd)", $generated, $filename);
562 generate_grub_config(\$menu, $config_name, "(cd)", $modules);
563 $menu_iso .= "$menu\n";
564 map { ($file,undef) = split; $files_iso{$file} = 1; } @$modules;
568 ## Generate ISO image
569 if (defined $iso_image) {
570 open(my $fh, ">menu-iso.lst");
571 print $fh "timeout 5\n\n$menu_iso";
573 my $files = "boot/grub/menu.lst=menu-iso.lst " . join(" ", map("$_=$_", keys(%files_iso)));
574 $iso_image ||= "$config_name.iso";
575 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");
576 print("ISO image created: $CFG::builddir/$iso_image\n");
579 ## Boot the system using various methods and send serial output to stdout
581 if (scalar(@scripts) > 1 && ( defined $dhcp_tftp || defined $serial || defined $iprelay)) {
582 die "You cannot do this with multiple scripts simultaneously";
585 if ($variables->{WVTEST_TIMEOUT}) {
586 print "wvtest: timeout ", $variables->{WVTEST_TIMEOUT}, "\n";
591 $str =~ s/^\s+|\s+$//g;
597 if (!(defined $dhcp_tftp || defined $serial || defined $iprelay || defined $server || defined $iso_image)) {
599 if (!$qemu && $variables->{QEMU}) {
600 @qemu_flags = split(" ", $variables->{QEMU});
601 $CFG::qemu = shift(@qemu_flags);
604 @qemu_flags = split(/ +/, trim($variables->{QEMU_FLAGS})) if exists $variables->{QEMU_FLAGS};
605 @qemu_flags = split(/ +/, trim($qemu_flags_cmd)) if $qemu_flags_cmd;
606 push(@qemu_flags, split(/ +/, trim($qemu_append)));
608 if (defined $iso_image) {
609 # Boot NOVA with grub (and test the iso image)
610 push(@qemu_flags, ('-cdrom', "$config_name.iso"));
612 # Boot NOVA without GRUB
614 # Non-patched qemu doesn't like commas, but NUL can live with pluses instead of commans
615 foreach (@$modules) {s/,/+/g;}
616 generate_configs("", $generated, $filename);
618 my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);
619 $kcmd = '' if !defined $kcmd;
621 @$modules = map { if (/\.dtb$/) { $dtb=$_; (); } else { $_ } } @$modules;
622 my $initrd = join ",", @$modules;
624 push(@qemu_flags, ('-kernel', $kbin, '-append', $kcmd));
625 push(@qemu_flags, ('-initrd', $initrd)) if $initrd;
626 push(@qemu_flags, ('-dtb', $dtb)) if $dtb;
628 push(@qemu_flags, qw(-serial stdio)); # Redirect serial output (for collecting test restuls)
629 exec_verbose(($CFG::qemu, '-name', $config_name, @qemu_flags));
632 ### Local DHCPD and TFTPD
634 my ($dhcpd_pid, $tftpd_pid);
636 if (defined $dhcp_tftp)
638 generate_configs("(nd)", $generated, $filename);
639 system_verbose('mkdir -p tftpboot');
640 generate_grub_config("tftpboot/os-menu.lst", $config_name, "(nd)", \@$modules, "timeout 0");
641 open(my $fh, '>', 'dhcpd.conf');
642 my $mac = `cat /sys/class/net/eth0/address`;
644 print $fh "subnet 10.23.23.0 netmask 255.255.255.0 {
645 range 10.23.23.10 10.23.23.100;
646 filename \"bin/boot/grub/pxegrub.pxe\";
647 next-server 10.23.23.1;
650 hardware ethernet $mac;
651 fixed-address 10.23.23.1;
654 system_verbose("sudo ip a add 10.23.23.1/24 dev eth0;
655 sudo ip l set dev eth0 up;
656 sudo touch dhcpd.leases");
659 if ($dhcpd_pid == 0) {
660 # This way, the spawned server are killed when this script is killed.
661 exec_verbose("sudo dhcpd -d -cf dhcpd.conf -lf dhcpd.leases -pf dhcpd.pid");
664 if ($tftpd_pid == 0) {
665 exec_verbose("sudo in.tftpd --foreground --secure -v -v -v $CFG::builddir");
667 $SIG{TERM} = sub { print "CHILDS KILLED\n"; kill 15, $dhcpd_pid, $tftpd_pid; };
670 ### Serial line or IP relay
672 if ($serial || defined $iprelay) {
674 if (defined $iprelay) {
675 print "novaboot: Reseting the test box... ";
676 relay(2, 1, 1); # Reset the machine
683 system("stty -F $serial raw -crtscts -onlcr 115200");
684 open($CONN, "+<", $serial) || die "open $serial: $!";
688 # Pass the NOVA output to stdout.
692 kill 15, $dhcpd_pid, $tftpd_pid if ($dhcp_tftp);
696 ### Wait for dhcpc or tftpd
697 if (defined $dhcp_tftp) {
699 if ($pid == $dhcpd_pid) { print "dhcpd exited!\n"; }
700 elsif ($pid == $tftpd_pid) { print "tftpd exited!\n"; }
701 else { print "wait returned: $pid\n"; }
702 kill(15, 0); # Kill current process group i.e. all remaining children
709 novaboot - A tool for booting various operating systems on various hardware or in qemu
713 B<novaboot> [ options ] [--] script...
715 B<./script> [ options ]
721 This program makes it easier to boot NOVA or other operating system
722 (OS) in different environments. It reads a so called novaboot script
723 and uses it either to boot the OS in an emulator (e.g. in qemu) or to
724 generate the configuration for a specific bootloader and optionally to
725 copy the necessary binaries and other needed files to proper
726 locations, perhaps on a remote server. In case the system is actually
727 booted, its serial output is redirected to standard output if that is
730 A typical way of using novaboot is to make the novaboot script
731 executable and set its first line to I<#!/usr/bin/env novaboot>. Then,
732 booting a particular OS configuration becomes the same as executing a
733 local program - the novaboot script.
735 With C<novaboot> you can:
741 Run an OS in Qemu. This is the default action when no other action is
742 specified by command line switches. Thus running C<novaboot ./script>
743 (or C<./script> as described above) will run Qemu and make it boot the
744 configuration specified in the I<script>.
748 Create a bootloader configuration file (currently supported
749 bootloaders are GRUB, GRUB2 and Pulsar) and copy it with all other
750 files needed for booting to another, perhaps remote, location.
752 ./script --server --iprelay
754 This command copies files to a TFTP server specified in the
755 configuration file and uses TCP/IP-controlled relay to reset the test
756 box and receive its serial output.
760 Run DHCP and TFTP server on developer's machine to PXE-boot NOVA from
765 When a PXE-bootable machine is connected via Ethernet to developer's
766 machine, it will boot the configuration described in I<script>.
770 Create bootable ISO images. E.g.
772 novaboot --iso -- script1 script2
774 The created ISO image will have GRUB bootloader installed on it and
775 the boot menu will allow selecting between I<script1> and I<script2>
780 =head1 PHASES AND OPTIONS
782 Novaboot perform its work in several phases. Each phase can be
783 influenced by several options, certain phases can be skipped. The list
784 of phases with the corresponding options follows.
786 =head2 Configuration reading phase
788 After starting, novaboot reads configuration files. By default, it
789 searches for files named F<.novaboot> starting from the directory of
790 the novaboot script (or working directory, see bellow) and continuing
791 upwards up to the root directory. The configuration files are read in
792 order from the root directory downwards with latter files overriding
793 settings from the former ones.
795 In certain cases, the location of the novaboot script cannot be
796 determined in this early phase. This happens either when the script is
797 read from the standard input or when novaboot is invoked explicitly
798 and options precede the script name, as in the example L</"4."> above.
799 In this case the current working directory is used as a starting point
800 for configuration file search.
804 =item -c, --config=<filename>
806 Use the specified configuration file instead of the default one(s).
810 =head2 Command line processing phase
816 Dump the current configuration to stdout end exits. Useful as an
817 initial template for a configuration file.
821 Print short (B<-h>) or long (B<--help>) help.
823 =item -t, --target=<target>
825 This option serves as a user configurable shortcut for other novaboot
826 options. The effect of this option is the same as the options stored
827 in the C<%targets> configuration variable under key I<target>. See
828 also L</"CONFIGURATION FILE">.
832 =head2 Script preprocessing phase
834 This phases allows to modify the parsed novaboot script before it is
835 used in the later phases.
839 =item -a, --append=<parameters>
841 Appends a string to the first "filename" line in the novaboot script.
842 This can be used to append parameters to the kernel's or root task's
847 Use F<bender> chainloader. Bender scans the PCI bus for PCI serial
848 ports and stores the information about them in the BIOS data area for
853 Prints the content of the novaboot script after removing comments and
854 evaluating all I<--scriptmod> expressions. Exit after reading (and
857 =item --scriptmod=I<perl expression>
859 When novaboot script is read, I<perl expression> is executed for every
860 line (in $_ variable). For example, C<novaboot
861 --scriptmod=s/sigma0/omega6/g> replaces every occurrence of I<sigma0>
862 in the script with I<omega6>.
864 When this option is present, it overrides I<$script_modifier> variable
865 from the configuration file, which has the same effect. If this option
866 is given multiple times all expressions are evaluated in the command
871 Strip I<rom://> prefix from command lines and generated config files.
872 The I<rom://> prefix is used by NUL. For NRE, it has to be stripped.
876 =head2 File generation phase
878 In this phase, files needed for booting are generated in a so called
879 I<build directory> (see TODO). In most cases configuration for a
880 bootloader is generated automatically by novaboot. It is also possible
881 to generate other files using I<heredoc> or I<"<"> syntax in novaboot
882 scripts. Finally, binaries can be generated in this phases by running
887 =item --build-dir=<directory>
889 Overrides the default build directory location.
891 The default build directory location is determined as follows:
893 If there is a configuration file, the value specified in the
894 I<$builddir> variable is used. Otherwise, if the current working
895 directory is inside git work tree and there is F<build> directory at
896 the top of that tree, it is used. Otherwise, if directory
897 F<~/nul/build> exists, it is used. Otherwise, it is the directory that
898 contains the first processed novaboot script.
900 =item -g, --grub[=I<filename>]
902 Generates grub bootloader menu file. If the I<filename> is not
903 specified, F<menu.lst> is used. The I<filename> is relative to the
904 build directory (see B<--build-dir>).
906 =item --grub-preamble=I<prefix>
908 Specifies the I<preable> that is at the beginning of the generated
909 GRUB or GRUB2 config files. This is useful for specifying GRUB's
912 =item --grub-prefix=I<prefix>
914 Specifies I<prefix> that is put in front of every file name in GRUB's
915 F<menu.lst>. The default value is the absolute path to the build directory.
917 If the I<prefix> contains string $NAME, it will be replaced with the
918 name of the novaboot script (see also B<--name>).
920 =item --grub2[=I<filename>]
922 Generate GRUB2 menuentry in I<filename>. If I<filename> is not
923 specified F<grub.cfg> is used. The content of the menuentry can be
924 customized with B<--grub-preable>, B<--grub2-prolog> or
925 B<--grub_prefix> options.
927 In order to use the the generated menuentry on your development
928 machine that uses GRUB2, append the following snippet to
929 F</etc/grub.d/40_custom> file and regenerate your grub configuration,
930 i.e. run update-grub on Debian/Ubuntu.
932 if [ -f /path/to/nul/build/grub.cfg ]; then
933 source /path/to/nul/build/grub.cfg
936 =item --grub2-prolog=I<prolog>
938 Specifies text I<preable> that is put at the begiging of the entry
941 =item --name=I<string>
943 Use the name I<string> instead of the name of the novaboot script.
944 This name is used for things like a title of grub menu or for the
945 server directory where the boot files are copied to.
949 Do not generate files on the fly (i.e. "<" syntax) except for the
950 files generated via "<<WORD" syntax.
952 =item -p, --pulsar[=mac]
954 Generates pulsar bootloader configuration file whose name is based on
955 the MAC address specified either on the command line or taken from
956 I<.novaboot> configuration file.
960 =head2 File deployment phase
962 In some setups, it is necessary to copy the files needed for booting
963 to a particular location, e.g. to a TFTP boot server or to the
968 =item -d, --dhcp-tftp
970 Turns your workstation into a DHCP and TFTP server so that NOVA
971 can be booted via PXE BIOS on a test machine directly connected by
972 a plain Ethernet cable to your workstation.
974 The DHCP and TFTP servers require root privileges and C<novaboot>
975 uses C<sudo> command to obtain those. You can put the following to
976 I</etc/sudoers> to allow running the necessary commands without
979 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
980 your_login ALL=NOPASSWD: NOVABOOT
982 =item -i, --iso[=filename]
984 Generates the ISO image that boots NOVA system via GRUB. If no filename
985 is given, the image is stored under I<NAME>.iso, where I<NAME> is the name
986 of the novaboot script (see also B<--name>).
988 =item --server[=[[user@]server:]path]
990 Copy all files needed for booting to another location (implies B<-g>
991 unless B<--grub2> is given). The files will be copied (by B<rsync>
992 tool) to the directory I<path>. If the I<path> contains string $NAME,
993 it will be replaced with the name of the novaboot script (see also
998 If B<--server> is used and its value ends with $NAME, then after
999 copying the files, a new bootloader configuration file (e.g. menu.lst)
1000 is created at I<path-wo-name>, i.e. the path specified by B<--server>
1001 with $NAME part removed. The content of the file is created by
1002 concatenating all files of the same name from all subdirectories of
1003 I<path-wo-name> found on the "server".
1005 =item --rsync-flags=I<flags>
1007 Specifies which I<flags> are appended to F<rsync> command line when
1008 copying files as a result of I<--server> option.
1010 =item --scons[=scons command]
1012 Runs I<scons> to build files that are not generated by novaboot
1017 =head2 Target power-on and reset phase
1021 =item --iprelay[=addr or cmd]
1023 If no I<cmd> is given, use IP relay to reset the machine and to get
1024 the serial output. The IP address of the relay is given by I<addr>
1025 parameter if specified or by $iprelay_addr variable in the
1028 If I<cmd> is one of "on" or "off", the IP relay is used to press power
1029 button for a short (in case of "on") or long (in case of "off") time.
1030 Then, novaboot exits.
1032 Note: This option is expected to work with HWG-ER02a IP relays.
1036 Synonym for --iprelay=on/off.
1038 =item -Q, --qemu=I<qemu-binary>
1040 The name of qemu binary to use. The default is 'qemu'.
1042 =item --qemu-append=I<flags>
1044 Append I<flags> to the default qemu flags (QEMU_FLAGS variable or
1045 C<-cpu coreduo -smp 2>).
1047 =item -q, --qemu-flags=I<flags>
1049 Replace the default qemu flags (QEMU_FLAGS variable or C<-cpu coreduo
1050 -smp 2>) with I<flags> specified here.
1054 =head2 Interaction with the bootloader on the target
1056 See B<--serial>. There will be new options soon.
1058 =head2 Target's output reception phase
1062 =item -s, --serial[=device]
1064 Use serial line to control GRUB bootloader and to see the output
1065 serial output of the machine. The default value is F</dev/ttyUSB0>.
1069 See also B<--iprelay>.
1071 =head2 Termination phase
1073 Daemons that were spwned (F<dhcpd> and F<tftpd>) are killed here.
1075 =head1 NOVABOOT SCRIPT SYNTAX
1077 The syntax tries to mimic POSIX shell syntax. The syntax is defined with the following rules.
1079 Lines starting with "#" are ignored.
1081 Lines that end with "\" are concatenated with the following line after
1082 removal of the final "\" and leading whitespace of the following line.
1084 Lines in the form I<VARIABLE=...> (i.e. matching '^[A-Z_]+=' regular
1085 expression) assign values to internal variables. See VARIABLES
1088 Otherwise, the first word on the line represents the filename
1089 (relative to the build directory (see B<--build-dir>) of the module to
1090 load and the remaining words are passed as the command line
1093 When the line ends with "<<WORD" then the subsequent lines until the
1094 line containing only WORD are copied literally to the file named on
1097 When the line ends with "< CMD" the command CMD is executed with
1098 C</bin/sh> and its standard output is stored in the file named on that
1099 line. The SRCDIR variable in CMD's environment is set to the absolute
1100 path of the directory containing the interpreted novaboot script.
1103 #!/usr/bin/env novaboot
1104 WVDESC=Example program
1105 bin/apps/sigma0.nul S0_DEFAULT script_start:1,1 \
1106 verbose hostkeyb:0,0x60,1,12,2
1108 hello.nulconfig <<EOF
1109 sigma0::mem:16 name::/s0/log name::/s0/timer name::/s0/fs/rom ||
1110 rom://bin/apps/hello.nul
1113 This example will load three modules: sigma0.nul, hello.nul and
1114 hello.nulconfig. sigma0 gets some command line parameters and
1115 hello.nulconfig file is generated on the fly from the lines between
1120 The following variables are interpreted in the novaboot script:
1126 Description of the wvtest-compliant program.
1128 =item WVTEST_TIMEOUT
1130 The timeout in seconds for WvTest harness. If no complete line appears
1131 in the test output within the time specified here, the test fails. It
1132 is necessary to specify this for long running tests that produce no
1133 intermediate output.
1137 Use a specific qemu binary (can be overriden with B<-Q>) and flags
1138 when booting this script under qemu. If QEMU_FLAGS variable is also
1139 specified flags specified in QEMU variable are replaced by those in
1144 Use specific qemu flags (can be overriden with B<-q>).
1146 =item HYPERVISOR_PARAMS
1148 Parameters passed to hypervisor. The default value is "serial", unless
1149 overriden in configuration file.
1153 The kernel to use instead of NOVA hypervisor specified in the
1154 configuration file. The value should contain the name of the kernel
1155 image as well as its command line parameters. If this variable is
1156 defined and non-empty, the variable HYPERVISOR_PARAMS is not used.
1160 =head1 CONFIGURATION FILE
1162 Novaboot can read its configuration from a file. Configuration file
1163 was necessary in early days of novaboot. Nowadays, an attempt is made
1164 to not use the configuration file because it makes certain novaboot
1165 scripts unusable on systems without (or with different) configuration
1166 file. The only recommended use of the configuration file is to specify
1167 custom_options (see bellow).
1169 If you decide to use the configuration file, it is looked up, by
1170 default, in files named F<.novaboot> as described in L</Configuration
1171 reading phase>. Alternatively, its location can be specified with the
1172 B<-c> switch or with the NOVABOOT_CONFIG environment variable. The
1173 configuration file has perl syntax and should set values of certain
1174 Perl variables. The current configuration can be dumped with the
1175 B<--dump-config> switch. Some configuration variables can be overriden
1176 by environment variables (see below) or by command line switches.
1178 Documentation of some configuration variables follows:
1184 Custom chainloaders to load before hypervisor and files specified in
1185 novaboot script. E.g. ('bin/boot/bender promisc', 'bin/boot/zapp').
1189 Hash of shortcuts to be used with the B<--target> option. If the hash
1190 contains, for instance, the following pair of values
1192 'mybox' => '--server=boot:/tftproot --serial=/dev/ttyUSB0 --grub',
1194 then the following two commands are equivalent:
1196 ./script --server=boot:/tftproot --serial=/dev/ttyUSB0 --grub
1201 =head1 ENVIRONMENT VARIABLES
1203 Some options can be specified not only via config file or command line
1204 but also through environment variables. Environment variables override
1205 the values from configuration file and command line parameters
1206 override the environment variables.
1210 =item NOVABOOT_CONFIG
1212 Name of the novaboot configuration file to use instead of the default
1215 =item NOVABOOT_BENDER
1217 Defining this variable has the same meaning as B<--bender> option.
1219 =item NOVABOOT_IPRELAY
1221 The IP address (and optionally the port) of the IP relay. This
1222 overrides $iprelay_addr variable from the configuration file.
1228 Michal Sojka <sojka@os.inf.tu-dresden.de>