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";
44 @CFG::chainloaders = (); #('bin/boot/bender promisc'); # TODO: convert to option
45 $CFG::pulsar_root = ''; # TODO: convert to option
46 $CFG::pulsar_mac = '52-54-00-12-34-56';
48 "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',
49 "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',
50 "localhost" => '--scriptmod=s/console=tty[A-Z0-9,]+// --server=/boot/novaboot/$NAME --grub2 --grub-prefix=/boot/novaboot/$NAME --grub2-prolog=" set root=\'(hd0,msdos1)\'"',
52 $CFG::scons = "scons -j2";
54 my @qemu_flags = qw(-cpu coreduo -smp 2);
58 package CFG; # Put config data into a separate namespace
63 die("ERROR: Failure compiling '$cfg' - $@");
64 } elsif (! defined($rc)) {
65 die("ERROR: Failure reading '$cfg' - $!");
67 die("ERROR: Failure processing '$cfg'");
70 print "novaboot: Read $cfg\n";
75 # We don't use $0 here, because it points to the novaboot itself and
76 # not to the novaboot script. The problem with this approach is that
77 # when a script is run as "novaboot <options> <script>" then $ARGV[0]
78 # contains the first option. Hence the -f check.
79 my $dir = abs_path($invocation_dir . ((-f $ARGV[0]) ? '/'.dirname($ARGV[0]) : ''));
80 while (-d $dir && $dir ne "/") {
81 push @cfgs, "$dir/.novaboot" if -r "$dir/.novaboot";
82 $dir = abs_path($dir."/..");
85 my $cfg = $ENV{'NOVABOOT_CONFIG'};
86 Getopt::Long::Configure(qw/no_ignore_case pass_through/);
87 GetOptions ("config|c=s" => \$cfg);
88 read_config($_) foreach $cfg or reverse @cfgs;
90 ## Command line handling
92 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);
95 $rom_prefix = 'rom://';
97 Getopt::Long::Configure(qw/no_ignore_case no_pass_through/);
100 "append|a=s" => \$append,
101 "bender|b" => \$bender,
102 "build-dir=s" => \$builddir,
103 "concat" => \$concat,
104 "dhcp-tftp|d" => \$dhcp_tftp,
105 "dump" => \$dump_opt,
106 "dump-config" => \$dump_config,
107 "grub|g:s" => \$grub_config,
108 "grub-preamble=s"=> \$grub_preamble,
109 "grub-prefix=s" => \$grub_prefix,
110 "grub2:s" => \$grub2_config,
111 "grub2-prolog=s" => \$grub2_prolog,
112 "iprelay=s" => \$iprelay,
113 "iso|i:s" => \$iso_image,
114 "name=s" => \$config_name_opt,
115 "no-file-gen" => \$no_file_gen,
118 "pulsar|p:s" => \$pulsar,
119 "qemu|Q=s" => \$qemu,
120 "qemu-append:s" => \$qemu_append,
121 "qemu-flags|q=s" => \$qemu_flags_cmd,
122 "rsync-flags=s" => \$rsync_flags,
123 "scons:s" => \$scons,
124 "scriptmod=s" => \@scriptmod,
125 "serial|s:s" => \$serial,
126 "server:s" => \$server,
127 "strip-rom" => sub { $rom_prefix = ''; },
128 "target|t=s" => sub { my ($opt_name, $opt_value) = @_;
129 exists $CFG::targets{$opt_value} or die("Unknown target '$opt_value' (valid targets are: ".join(", ", sort keys(%CFG::targets)).")");
130 GetOptionsFromString($CFG::targets{$opt_value}, %opt_spec); },
134 GetOptions %opt_spec or pod2usage(2);
135 pod2usage(1) if $help;
136 pod2usage(-exitstatus => 0, -verbose => 2) if $man;
138 ### Sanitize configuration
140 if (defined $config_name_opt && scalar(@ARGV) > 1) { die "You cannot use --name with multiple scripts"; }
142 if ($qemu) { $CFG::qemu = $qemu; }
146 $CFG::pulsar_mac = $pulsar;
149 if ($scons) { $CFG::scons = $scons; }
151 ### Dump sanitized configuration (if requested)
155 $Data::Dumper::Indent=0;
156 print "# This file is in perl syntax.\n";
157 foreach my $key(sort(keys(%CFG::))) { # See "Symbol Tables" in perlmod(1)
158 if (defined ${$CFG::{$key}}) { print Data::Dumper->Dump([${$CFG::{$key}}], ["*$key"]); }
159 if ( @{$CFG::{$key}}) { print Data::Dumper->Dump([\@{$CFG::{$key}}], ["*$key"]); }
160 if ( %{$CFG::{$key}}) { print Data::Dumper->Dump([\%{$CFG::{$key}}], ["*$key"]); }
167 if (defined $serial) {
168 $serial ||= "/dev/ttyUSB0";
171 if (defined $grub_config) {
172 $grub_config ||= "menu.lst";
175 if (defined $grub2_config) {
176 $grub2_config ||= "grub.cfg";
179 ## Parse the novaboot script(s)
185 my ($modules, $variables, $generated, $continuation);
187 if ($ARGV ne $last_fn) { # New script
188 die "Missing EOF in $last_fn" if $file;
189 die "Unfinished line in $last_fn" if $line;
191 push @scripts, { 'filename' => $ARGV,
192 'modules' => $modules = [],
193 'variables' => $variables = {},
194 'generated' => $generated = []};
198 next if /^#/ || /^\s*$/; # Skip comments and empty lines
200 foreach my $mod(@scriptmod) { eval $mod; }
202 print "$_\n" if $dump_opt;
204 if (/^([A-Z_]+)=(.*)$/) { # Internal variable
205 $$variables{$1} = $2;
208 if (/^([^ ]*)(.*?)[[:space:]]*<<([^ ]*)$/) { # Heredoc start
209 push @$modules, "$1$2";
211 push @$generated, {filename => $1, content => $file};
215 if ($file && $_ eq $EOF) { # Heredoc end
219 if ($file) { # Heredoc content
220 push @{$file}, "$_\n";
223 $_ =~ s/^[[:space:]]*// if ($continuation);
224 if (/\\$/) { # Line continuation
225 $line .= substr($_, 0, length($_)-1);
231 $line .= " $append" if ($append && scalar(@$modules) == 0);
233 if ($line =~ /^([^ ]*)(.*?)[[:space:]]*< ?(.*)$/) { # Command substitution
234 push @$modules, "$1$2";
235 push @$generated, {filename => $1, command => $3};
239 push @$modules, $line;
243 #print Dumper(\@scripts);
249 sub generate_configs($$$) {
250 my ($base, $generated, $filename) = @_;
251 if ($base) { $base = "$base/"; };
252 foreach my $g(@$generated) {
253 if (exists $$g{content}) {
254 my $config = $$g{content};
255 my $fn = $$g{filename};
256 open(my $f, '>', $fn) || die("$fn: $!");
257 map { s|\brom://([^ ]*)|$rom_prefix$base$1|g; print $f "$_"; } @{$config};
259 print "novaboot: Created $fn\n";
260 } elsif (exists $$g{command} && ! $no_file_gen) {
261 $ENV{SRCDIR} = dirname(File::Spec->rel2abs( $filename, $invocation_dir ));
262 system_verbose("( $$g{command} ) > $$g{filename}");
267 sub generate_grub_config($$$$;$)
269 my ($filename, $title, $base, $modules_ref, $preamble) = @_;
270 if ($base) { $base = "$base/"; };
271 open(my $fg, '>', $filename) or die "$filename: $!";
272 print $fg "$preamble\n" if $preamble;
273 my $endmark = ($serial || defined $iprelay) ? ';' : '';
274 print $fg "title $title$endmark\n" if $title;
275 #print $fg "root $base\n"; # root doesn't really work for (nd)
277 foreach (@$modules_ref) {
280 my ($kbin, $kcmd) = split(' ', $_, 2);
281 $kcmd = '' if !defined $kcmd;
282 print $fg "kernel ${base}$kbin $kcmd\n";
284 s|\brom://([^ ]*)|$rom_prefix$base$1|g; # Translate rom:// files - needed for vdisk parameter of sigma0
285 print $fg "module $base$_\n";
289 print("novaboot: Created $CFG::builddir/$filename\n");
293 sub generate_grub2_config($$$$;$$)
295 my ($filename, $title, $base, $modules_ref, $preamble, $prolog) = @_;
296 if ($base && substr($base,-1,1) ne '/') { $base = "$base/"; };
297 open(my $fg, '>', $filename) or die "$filename: $!";
298 print $fg "$preamble\n" if $preamble;
299 my $endmark = ($serial || defined $iprelay) ? ';' : '';
300 $title ||= 'novaboot';
301 print $fg "menuentry $title$endmark {\n";
302 print $fg "$prolog\n" if $prolog;
304 foreach (@$modules_ref) {
307 my ($kbin, $kcmd) = split(' ', $_, 2);
308 $kcmd = '' if !defined $kcmd;
309 print $fg " multiboot ${base}$kbin $kcmd\n";
312 # GRUB2 doesn't pass filename in multiboot info so we have to duplicate it here
313 $_ = join(' ', ($args[0], @args));
314 s|\brom://|$rom_prefix|g; # We do not need to translate path for GRUB2
315 print $fg " module $base$_\n";
320 print("novaboot: Created $CFG::builddir/$filename\n");
324 sub generate_pulsar_config($$)
326 my ($filename, $modules_ref) = @_;
327 open(my $fg, '>', $filename) or die "$filename: $!";
328 print $fg "root $CFG::pulsar_root\n" if $CFG::pulsar_root;
331 foreach (@$modules_ref) {
334 ($kbin, $kcmd) = split(' ', $_, 2);
335 $kcmd = '' if !defined $kcmd;
338 s|\brom://|$rom_prefix|g;
339 print $fg "load $_\n";
342 # Put kernel as last - this is needed for booting Linux and has no influence on non-Linux OSes
343 print $fg "exec $kbin $kcmd\n";
345 print("novaboot: Created $CFG::builddir/$filename\n");
351 print "novaboot: Running: ".join(' ', map("'$_'", @_))."\n";
355 sub system_verbose($)
358 print "novaboot: Running: $cmd\n";
359 my $ret = system($cmd);
360 if ($ret & 0x007f) { die("Command terminated by a signal"); }
361 if ($ret & 0xff00) {die("Command exit with non-zero exit code"); }
362 if ($ret) { die("Command failure $ret"); }
367 if (exists $variables->{WVDESC}) {
368 print "Testing \"$variables->{WVDESC}\" in $last_fn:\n";
369 } elsif ($last_fn =~ /\.wv$/) {
370 print "Testing \"all\" in $last_fn:\n";
373 ## Handle reset and power on/off
376 if (defined $iprelay) {
377 $iprelay =~ /([.0-9]+)(:([0-9]+))?/;
380 my $paddr = sockaddr_in($port, inet_aton($addr));
381 my $proto = getprotobyname('tcp');
382 socket($IPRELAY, PF_INET, SOCK_STREAM, $proto) || die "socket: $!";
383 print "novaboot: Connecting to IP relay... ";
384 connect($IPRELAY, $paddr) || die "connect: $!";
386 $IPRELAY->autoflush(1);
389 print $IPRELAY "\xFF\xF6";
391 local $SIG{ALRM} = sub { die "Relay AYT timeout"; };
392 my $ayt_reponse = "";
393 my $read = sysread($IPRELAY, $ayt_reponse, 100);
397 print "$ayt_reponse\n";
398 if ($ayt_reponse =~ /<iprelayd: not connected/) {
406 my ($relay, $onoff) = @_;
407 die unless ($relay == 1 || $relay == 2);
409 my $cmd = ($relay == 1 ? 0x5 : 0x6) | ($onoff ? 0x20 : 0x10);
410 return "\xFF\xFA\x2C\x32".chr($cmd)."\xFF\xF0";
414 my ($relay, $onoff) = @_;
415 die unless ($relay == 1 || $relay == 2);
416 my $cmd = ($relay == 1 ? 0xdf : 0xbf) | ($onoff ? 0x00 : 0xff);
417 return "\xFF\xFA\x2C\x97".chr($cmd)."\xFF\xF0";
421 my ($relay, $onoff, $can_giveup) = @_;
422 my $confirmation = '';
423 print $IPRELAY relaycmd($relay, $onoff);
425 # We use non-blocking I/O and polling here because for some
426 # reason read() on blocking FD returns only after all
427 # requested data is available. If we get during the first
428 # read() only a part of confirmation, we may get the rest
429 # after the system boots and print someting, which may be too
431 $IPRELAY->blocking(0);
433 alarm(20); # Timeout in seconds
435 local $SIG{ALRM} = sub {
436 if ($can_giveup) { print("Relay confirmation timeout - ignoring\n"); $giveup = 1;}
437 else {die "Relay confirmation timeout";}
440 while (($index=index($confirmation, relayconf($relay, $onoff))) < 0 && !$giveup) {
441 my $read = read($IPRELAY, $confirmation, 70, length($confirmation));
442 if (!defined($read)) {
443 die("IP relay: $!") unless $! == EAGAIN;
447 #use MIME::QuotedPrint;
448 #print "confirmation = ".encode_qp($confirmation)."\n";
451 $IPRELAY->blocking(1);
455 if ($iprelay && (defined $on_opt || defined $off_opt)) {
456 relay(1, 1); # Press power button
457 if (defined $on_opt) {
458 usleep(100000); # Short press
460 print "novaboot: Switching the target off...\n";
461 usleep(6000000); # Long press to switch off
463 print $IPRELAY relay(1, 0);
467 ## Figure out the location of builddir and chdir() there
469 $CFG::builddir = $builddir;
471 if (! defined $CFG::builddir) {
472 $CFG::builddir = ( $gittop || $ENV{'HOME'}."/nul" ) . "/build";
473 if (! -d $CFG::builddir) {
474 $CFG::builddir = $ENV{SRCDIR} = dirname(File::Spec->rel2abs( ${$scripts[0]}{filename}, $invocation_dir ));
479 chdir($CFG::builddir) or die "Can't change directory to $CFG::builddir: $!";
480 print "novaboot: Entering directory `$CFG::builddir'\n";
482 ## File generation phase
483 my (%files_iso, $menu_iso, $config_name, $filename);
485 foreach my $script (@scripts) {
486 $filename = $$script{filename};
487 $modules = $$script{modules};
488 $generated = $$script{generated};
489 $variables = $$script{variables};
491 ($config_name = $filename) =~ s#.*/##;
492 $config_name = $config_name_opt if (defined $config_name_opt);
495 if (exists $variables->{KERNEL}) {
496 $kernel = $variables->{KERNEL};
498 if ($CFG::hypervisor) {
499 $kernel = $CFG::hypervisor . " ";
500 if (exists $variables->{HYPERVISOR_PARAMS}) {
501 $kernel .= $variables->{HYPERVISOR_PARAMS};
503 $kernel .= $CFG::hypervisor_params;
507 @$modules = ($kernel, @$modules) if $kernel;
508 @$modules = (@CFG::chainloaders, @$modules);
509 @$modules = ("bin/boot/bender", @$modules) if ($bender || defined $ENV{'NOVABOOT_BENDER'});
512 ($prefix = $grub_prefix) =~ s/\$NAME/$config_name/ if defined $grub_prefix;
513 $prefix ||= $CFG::builddir;
514 # TODO: use $grub_prefix as first parameter if some switch is given
515 generate_configs('', $generated, $filename);
517 ### Generate bootloader configuration files
519 my @bootloader_configs;
520 push @bootloader_configs, generate_grub_config($grub_config, $config_name, $prefix, $modules, $grub_preamble) if (defined $grub_config);
521 push @bootloader_configs, generate_grub2_config($grub2_config, $config_name, $prefix, $modules, $grub_preamble, $grub2_prolog) if (defined $grub2_config);
522 push @bootloader_configs, generate_pulsar_config($pulsar_config = "config-$CFG::pulsar_mac", $modules) if (defined $pulsar);
525 if (defined $scons) {
526 my @files = map({ ($file) = m/([^ ]*)/; $file; } @$modules);
527 # Filter-out generated files
528 my @to_build = grep({ my $file = $_; !scalar(grep($file eq $$_{filename}, @$generated)) } @files);
529 system_verbose($CFG::scons." ".join(" ", @to_build));
532 ### Copy files (using rsync)
533 if (defined $server) {
534 (my $real_server = $server) =~ s/\$NAME/$config_name/;
536 my ($hostname, $path) = split(":", $real_server, 2);
537 if (! defined $path) {
541 my $files = join(" ", map({ ($file) = m/([^ ]*)/; $file; } ( @$modules, @bootloader_configs)));
542 map({ my $file = (split)[0]; die "$file: $!" if ! -f $file; } @$modules);
543 my $istty = -t STDOUT && ($ENV{'TERM'} || 'dumb') ne 'dumb';
544 my $progress = $istty ? "--progress" : "";
545 system_verbose("rsync $progress -RLp $rsync_flags $files $real_server");
546 if ($server =~ m|/\$NAME$| && $concat) {
547 my $cmd = join("; ", map { "( cd $path/.. && cat */$_ > $_ )" } @bootloader_configs);
548 system_verbose($hostname ? "ssh $hostname '$cmd'" : $cmd);
552 ### Prepare ISO image generation
553 if (defined $iso_image) {
554 generate_configs("(cd)", $generated, $filename);
556 generate_grub_config(\$menu, $config_name, "(cd)", $modules);
557 $menu_iso .= "$menu\n";
558 map { ($file,undef) = split; $files_iso{$file} = 1; } @$modules;
562 ## Generate ISO image
563 if (defined $iso_image) {
564 open(my $fh, ">menu-iso.lst");
565 print $fh "timeout 5\n\n$menu_iso";
567 my $files = "boot/grub/menu.lst=menu-iso.lst " . join(" ", map("$_=$_", keys(%files_iso)));
568 $iso_image ||= "$config_name.iso";
569 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");
570 print("ISO image created: $CFG::builddir/$iso_image\n");
573 ## Boot the system using various methods and send serial output to stdout
575 if (scalar(@scripts) > 1 && ( defined $dhcp_tftp || defined $serial || defined $iprelay)) {
576 die "You cannot do this with multiple scripts simultaneously";
579 if ($variables->{WVTEST_TIMEOUT}) {
580 print "wvtest: timeout ", $variables->{WVTEST_TIMEOUT}, "\n";
585 $str =~ s/^\s+|\s+$//g;
591 if (!(defined $dhcp_tftp || defined $serial || defined $iprelay || defined $server || defined $iso_image)) {
593 if (!$qemu && $variables->{QEMU}) {
594 @qemu_flags = split(" ", $variables->{QEMU});
595 $CFG::qemu = shift(@qemu_flags);
598 @qemu_flags = split(/ +/, trim($variables->{QEMU_FLAGS})) if exists $variables->{QEMU_FLAGS};
599 @qemu_flags = split(/ +/, trim($qemu_flags_cmd)) if $qemu_flags_cmd;
600 push(@qemu_flags, split(/ +/, trim($qemu_append)));
602 if (defined $iso_image) {
603 # Boot NOVA with grub (and test the iso image)
604 push(@qemu_flags, ('-cdrom', "$config_name.iso"));
606 # Boot NOVA without GRUB
608 # Non-patched qemu doesn't like commas, but NUL can live with pluses instead of commans
609 foreach (@$modules) {s/,/+/g;}
610 generate_configs("", $generated, $filename);
612 my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);
613 $kcmd = '' if !defined $kcmd;
615 @$modules = map { if (/\.dtb$/) { $dtb=$_; (); } else { $_ } } @$modules;
616 my $initrd = join ",", @$modules;
618 push(@qemu_flags, ('-kernel', $kbin, '-append', $kcmd));
619 push(@qemu_flags, ('-initrd', $initrd)) if $initrd;
620 push(@qemu_flags, ('-dtb', $dtb)) if $dtb;
622 push(@qemu_flags, qw(-serial stdio)); # Redirect serial output (for collecting test restuls)
623 exec_verbose(($CFG::qemu, '-name', $config_name, @qemu_flags));
626 ### Local DHCPD and TFTPD
628 my ($dhcpd_pid, $tftpd_pid);
630 if (defined $dhcp_tftp)
632 generate_configs("(nd)", $generated, $filename);
633 system_verbose('mkdir -p tftpboot');
634 generate_grub_config("tftpboot/os-menu.lst", $config_name, "(nd)", \@$modules, "timeout 0");
635 open(my $fh, '>', 'dhcpd.conf');
636 my $mac = `cat /sys/class/net/eth0/address`;
638 print $fh "subnet 10.23.23.0 netmask 255.255.255.0 {
639 range 10.23.23.10 10.23.23.100;
640 filename \"bin/boot/grub/pxegrub.pxe\";
641 next-server 10.23.23.1;
644 hardware ethernet $mac;
645 fixed-address 10.23.23.1;
648 system_verbose("sudo ip a add 10.23.23.1/24 dev eth0;
649 sudo ip l set dev eth0 up;
650 sudo touch dhcpd.leases");
653 if ($dhcpd_pid == 0) {
654 # This way, the spawned server are killed when this script is killed.
655 exec_verbose("sudo dhcpd -d -cf dhcpd.conf -lf dhcpd.leases -pf dhcpd.pid");
658 if ($tftpd_pid == 0) {
659 exec_verbose("sudo in.tftpd --foreground --secure -v -v -v $CFG::builddir");
661 $SIG{TERM} = sub { print "CHILDS KILLED\n"; kill 15, $dhcpd_pid, $tftpd_pid; };
664 ### Serial line or IP relay
666 if ($serial || defined $iprelay) {
668 if (defined $iprelay) {
669 print "novaboot: Reseting the test box... ";
670 relay(2, 1, 1); # Reset the machine
677 system("stty -F $serial raw -crtscts -onlcr 115200");
678 open($CONN, "+<", $serial) || die "open $serial: $!";
682 # Pass the NOVA output to stdout.
686 kill 15, $dhcpd_pid, $tftpd_pid if ($dhcp_tftp);
690 ### Wait for dhcpc or tftpd
691 if (defined $dhcp_tftp) {
693 if ($pid == $dhcpd_pid) { print "dhcpd exited!\n"; }
694 elsif ($pid == $tftpd_pid) { print "tftpd exited!\n"; }
695 else { print "wait returned: $pid\n"; }
696 kill(15, 0); # Kill current process group i.e. all remaining children
703 novaboot - A tool for booting various operating systems on various hardware or in qemu
707 B<novaboot> [ options ] [--] script...
709 B<./script> [ options ]
715 This program makes it easier to boot NOVA or other operating system
716 (OS) in different environments. It reads a so called novaboot script
717 and uses it either to boot the OS in an emulator (e.g. in qemu) or to
718 generate the configuration for a specific bootloader and optionally to
719 copy the necessary binaries and other needed files to proper
720 locations, perhaps on a remote server. In case the system is actually
721 booted, its serial output is redirected to standard output if that is
724 A typical way of using novaboot is to make the novaboot script
725 executable and set its first line to I<#!/usr/bin/env novaboot>. Then,
726 booting a particular OS configuration becomes the same as executing a
727 local program - the novaboot script.
729 With C<novaboot> you can:
735 Run an OS in Qemu. This is the default action when no other action is
736 specified by command line switches. Thus running C<novaboot ./script>
737 (or C<./script> as described above) will run Qemu and make it boot the
738 configuration specified in the I<script>.
742 Create a bootloader configuration file (currently supported
743 bootloaders are GRUB, GRUB2 and Pulsar) and copy it with all other
744 files needed for booting to another, perhaps remote, location.
746 ./script --server --iprelay=192.168.1.2
748 This command copies files to a TFTP server specified in the
749 configuration file and uses TCP/IP-controlled relay to reset the test
750 box and receive its serial output.
754 Run DHCP and TFTP server on developer's machine to PXE-boot NOVA from
759 When a PXE-bootable machine is connected via Ethernet to developer's
760 machine, it will boot the configuration described in I<script>.
764 Create bootable ISO images. E.g.
766 novaboot --iso -- script1 script2
768 The created ISO image will have GRUB bootloader installed on it and
769 the boot menu will allow selecting between I<script1> and I<script2>
774 =head1 PHASES AND OPTIONS
776 Novaboot perform its work in several phases. Each phase can be
777 influenced by several options, certain phases can be skipped. The list
778 of phases with the corresponding options follows.
780 =head2 Configuration reading phase
782 After starting, novaboot reads configuration files. By default, it
783 searches for files named F<.novaboot> starting from the directory of
784 the novaboot script (or working directory, see bellow) and continuing
785 upwards up to the root directory. The configuration files are read in
786 order from the root directory downwards with latter files overriding
787 settings from the former ones.
789 In certain cases, the location of the novaboot script cannot be
790 determined in this early phase. This happens either when the script is
791 read from the standard input or when novaboot is invoked explicitly
792 and options precede the script name, as in the example L</"4."> above.
793 In this case the current working directory is used as a starting point
794 for configuration file search.
798 =item -c, --config=<filename>
800 Use the specified configuration file instead of the default one(s).
804 =head2 Command line processing phase
810 Dump the current configuration to stdout end exits. Useful as an
811 initial template for a configuration file.
815 Print short (B<-h>) or long (B<--help>) help.
817 =item -t, --target=<target>
819 This option serves as a user configurable shortcut for other novaboot
820 options. The effect of this option is the same as the options stored
821 in the C<%targets> configuration variable under key I<target>. See
822 also L</"CONFIGURATION FILE">.
826 =head2 Script preprocessing phase
828 This phases allows to modify the parsed novaboot script before it is
829 used in the later phases.
833 =item -a, --append=<parameters>
835 Appends a string to the first "filename" line in the novaboot script.
836 This can be used to append parameters to the kernel's or root task's
841 Use F<bender> chainloader. Bender scans the PCI bus for PCI serial
842 ports and stores the information about them in the BIOS data area for
847 Prints the content of the novaboot script after removing comments and
848 evaluating all I<--scriptmod> expressions. Exit after reading (and
851 =item --scriptmod=I<perl expression>
853 When novaboot script is read, I<perl expression> is executed for every
854 line (in $_ variable). For example, C<novaboot
855 --scriptmod=s/sigma0/omega6/g> replaces every occurrence of I<sigma0>
856 in the script with I<omega6>.
858 When this option is present, it overrides I<$script_modifier> variable
859 from the configuration file, which has the same effect. If this option
860 is given multiple times all expressions are evaluated in the command
865 Strip I<rom://> prefix from command lines and generated config files.
866 The I<rom://> prefix is used by NUL. For NRE, it has to be stripped.
870 =head2 File generation phase
872 In this phase, files needed for booting are generated in a so called
873 I<build directory> (see TODO). In most cases configuration for a
874 bootloader is generated automatically by novaboot. It is also possible
875 to generate other files using I<heredoc> or I<"<"> syntax in novaboot
876 scripts. Finally, binaries can be generated in this phases by running
881 =item --build-dir=<directory>
883 Overrides the default build directory location.
885 The default build directory location is determined as follows:
887 If there is a configuration file, the value specified in the
888 I<$builddir> variable is used. Otherwise, if the current working
889 directory is inside git work tree and there is F<build> directory at
890 the top of that tree, it is used. Otherwise, if directory
891 F<~/nul/build> exists, it is used. Otherwise, it is the directory that
892 contains the first processed novaboot script.
894 =item -g, --grub[=I<filename>]
896 Generates grub bootloader menu file. If the I<filename> is not
897 specified, F<menu.lst> is used. The I<filename> is relative to the
898 build directory (see B<--build-dir>).
900 =item --grub-preamble=I<prefix>
902 Specifies the I<preable> that is at the beginning of the generated
903 GRUB or GRUB2 config files. This is useful for specifying GRUB's
906 =item --grub-prefix=I<prefix>
908 Specifies I<prefix> that is put in front of every file name in GRUB's
909 F<menu.lst>. The default value is the absolute path to the build directory.
911 If the I<prefix> contains string $NAME, it will be replaced with the
912 name of the novaboot script (see also B<--name>).
914 =item --grub2[=I<filename>]
916 Generate GRUB2 menuentry in I<filename>. If I<filename> is not
917 specified F<grub.cfg> is used. The content of the menuentry can be
918 customized with B<--grub-preable>, B<--grub2-prolog> or
919 B<--grub_prefix> options.
921 In order to use the the generated menuentry on your development
922 machine that uses GRUB2, append the following snippet to
923 F</etc/grub.d/40_custom> file and regenerate your grub configuration,
924 i.e. run update-grub on Debian/Ubuntu.
926 if [ -f /path/to/nul/build/grub.cfg ]; then
927 source /path/to/nul/build/grub.cfg
930 =item --grub2-prolog=I<prolog>
932 Specifies text I<preable> that is put at the begiging of the entry
935 =item --name=I<string>
937 Use the name I<string> instead of the name of the novaboot script.
938 This name is used for things like a title of grub menu or for the
939 server directory where the boot files are copied to.
943 Do not generate files on the fly (i.e. "<" syntax) except for the
944 files generated via "<<WORD" syntax.
946 =item -p, --pulsar[=mac]
948 Generates pulsar bootloader configuration file whose name is based on
949 the MAC address specified either on the command line or taken from
950 I<.novaboot> configuration file.
954 =head2 File deployment phase
956 In some setups, it is necessary to copy the files needed for booting
957 to a particular location, e.g. to a TFTP boot server or to the
962 =item -d, --dhcp-tftp
964 Turns your workstation into a DHCP and TFTP server so that NOVA
965 can be booted via PXE BIOS on a test machine directly connected by
966 a plain Ethernet cable to your workstation.
968 The DHCP and TFTP servers require root privileges and C<novaboot>
969 uses C<sudo> command to obtain those. You can put the following to
970 I</etc/sudoers> to allow running the necessary commands without
973 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
974 your_login ALL=NOPASSWD: NOVABOOT
976 =item -i, --iso[=filename]
978 Generates the ISO image that boots NOVA system via GRUB. If no filename
979 is given, the image is stored under I<NAME>.iso, where I<NAME> is the name
980 of the novaboot script (see also B<--name>).
982 =item --server[=[[user@]server:]path]
984 Copy all files needed for booting to another location (implies B<-g>
985 unless B<--grub2> is given). The files will be copied (by B<rsync>
986 tool) to the directory I<path>. If the I<path> contains string $NAME,
987 it will be replaced with the name of the novaboot script (see also
992 If B<--server> is used and its value ends with $NAME, then after
993 copying the files, a new bootloader configuration file (e.g. menu.lst)
994 is created at I<path-wo-name>, i.e. the path specified by B<--server>
995 with $NAME part removed. The content of the file is created by
996 concatenating all files of the same name from all subdirectories of
997 I<path-wo-name> found on the "server".
999 =item --rsync-flags=I<flags>
1001 Specifies which I<flags> are appended to F<rsync> command line when
1002 copying files as a result of I<--server> option.
1004 =item --scons[=scons command]
1006 Runs I<scons> to build files that are not generated by novaboot
1011 =head2 Target power-on and reset phase
1015 =item --iprelay=I<addr[:port]>
1017 Use IP relay to reset the machine and to get the serial output. The IP
1018 address of the relay is given by I<addr> parameter.
1020 Note: This option is expected to work with HWG-ER02a IP relays.
1024 Switch on/off the target machine. Currently works only with
1027 =item -Q, --qemu=I<qemu-binary>
1029 The name of qemu binary to use. The default is 'qemu'.
1031 =item --qemu-append=I<flags>
1033 Append I<flags> to the default qemu flags (QEMU_FLAGS variable or
1034 C<-cpu coreduo -smp 2>).
1036 =item -q, --qemu-flags=I<flags>
1038 Replace the default qemu flags (QEMU_FLAGS variable or C<-cpu coreduo
1039 -smp 2>) with I<flags> specified here.
1043 =head2 Interaction with the bootloader on the target
1045 See B<--serial>. There will be new options soon.
1047 =head2 Target's output reception phase
1051 =item -s, --serial[=device]
1053 Use serial line to control GRUB bootloader and to see the output
1054 serial output of the machine. The default value is F</dev/ttyUSB0>.
1058 See also B<--iprelay>.
1060 =head2 Termination phase
1062 Daemons that were spwned (F<dhcpd> and F<tftpd>) are killed here.
1064 =head1 NOVABOOT SCRIPT SYNTAX
1066 The syntax tries to mimic POSIX shell syntax. The syntax is defined with the following rules.
1068 Lines starting with "#" are ignored.
1070 Lines that end with "\" are concatenated with the following line after
1071 removal of the final "\" and leading whitespace of the following line.
1073 Lines in the form I<VARIABLE=...> (i.e. matching '^[A-Z_]+=' regular
1074 expression) assign values to internal variables. See VARIABLES
1077 Otherwise, the first word on the line represents the filename
1078 (relative to the build directory (see B<--build-dir>) of the module to
1079 load and the remaining words are passed as the command line
1082 When the line ends with "<<WORD" then the subsequent lines until the
1083 line containing only WORD are copied literally to the file named on
1086 When the line ends with "< CMD" the command CMD is executed with
1087 C</bin/sh> and its standard output is stored in the file named on that
1088 line. The SRCDIR variable in CMD's environment is set to the absolute
1089 path of the directory containing the interpreted novaboot script.
1092 #!/usr/bin/env novaboot
1093 WVDESC=Example program
1094 bin/apps/sigma0.nul S0_DEFAULT script_start:1,1 \
1095 verbose hostkeyb:0,0x60,1,12,2
1097 hello.nulconfig <<EOF
1098 sigma0::mem:16 name::/s0/log name::/s0/timer name::/s0/fs/rom ||
1099 rom://bin/apps/hello.nul
1102 This example will load three modules: sigma0.nul, hello.nul and
1103 hello.nulconfig. sigma0 gets some command line parameters and
1104 hello.nulconfig file is generated on the fly from the lines between
1109 The following variables are interpreted in the novaboot script:
1115 Description of the wvtest-compliant program.
1117 =item WVTEST_TIMEOUT
1119 The timeout in seconds for WvTest harness. If no complete line appears
1120 in the test output within the time specified here, the test fails. It
1121 is necessary to specify this for long running tests that produce no
1122 intermediate output.
1126 Use a specific qemu binary (can be overriden with B<-Q>) and flags
1127 when booting this script under qemu. If QEMU_FLAGS variable is also
1128 specified flags specified in QEMU variable are replaced by those in
1133 Use specific qemu flags (can be overriden with B<-q>).
1135 =item HYPERVISOR_PARAMS
1137 Parameters passed to hypervisor. The default value is "serial", unless
1138 overriden in configuration file.
1142 The kernel to use instead of NOVA hypervisor specified in the
1143 configuration file. The value should contain the name of the kernel
1144 image as well as its command line parameters. If this variable is
1145 defined and non-empty, the variable HYPERVISOR_PARAMS is not used.
1149 =head1 CONFIGURATION FILE
1151 Novaboot can read its configuration from a file. Configuration file
1152 was necessary in early days of novaboot. Nowadays, an attempt is made
1153 to not use the configuration file because it makes certain novaboot
1154 scripts unusable on systems without (or with different) configuration
1155 file. The only recommended use of the configuration file is to specify
1156 custom_options (see bellow).
1158 If you decide to use the configuration file, it is looked up, by
1159 default, in files named F<.novaboot> as described in L</Configuration
1160 reading phase>. Alternatively, its location can be specified with the
1161 B<-c> switch or with the NOVABOOT_CONFIG environment variable. The
1162 configuration file has perl syntax and should set values of certain
1163 Perl variables. The current configuration can be dumped with the
1164 B<--dump-config> switch. Some configuration variables can be overriden
1165 by environment variables (see below) or by command line switches.
1167 Documentation of some configuration variables follows:
1173 Custom chainloaders to load before hypervisor and files specified in
1174 novaboot script. E.g. ('bin/boot/bender promisc', 'bin/boot/zapp').
1178 Hash of shortcuts to be used with the B<--target> option. If the hash
1179 contains, for instance, the following pair of values
1181 'mybox' => '--server=boot:/tftproot --serial=/dev/ttyUSB0 --grub',
1183 then the following two commands are equivalent:
1185 ./script --server=boot:/tftproot --serial=/dev/ttyUSB0 --grub
1190 =head1 ENVIRONMENT VARIABLES
1192 Some options can be specified not only via config file or command line
1193 but also through environment variables. Environment variables override
1194 the values from configuration file and command line parameters
1195 override the environment variables.
1199 =item NOVABOOT_CONFIG
1201 Name of the novaboot configuration file to use instead of the default
1204 =item NOVABOOT_BENDER
1206 Defining this variable has the same meaning as B<--bender> option.
1212 Michal Sojka <sojka@os.inf.tu-dresden.de>