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::qemu = 'qemu -cpu coreduo -smp 2';
44 @CFG::chainloaders = (); #('bin/boot/bender promisc'); # TODO: convert to option
45 $CFG::pulsar_root = ''; # TODO: convert to option
47 "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',
48 "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',
49 "localhost" => '--scriptmod=s/console=tty[A-Z0-9,]+// --server=/boot/novaboot/$NAME --grub2 --grub-prefix=/boot/novaboot/$NAME --grub2-prolog=" set root=\'(hd0,msdos1)\'"',
51 $CFG::scons = "scons -j2";
56 package CFG; # Put config data into a separate namespace
61 die("ERROR: Failure compiling '$cfg' - $@");
62 } elsif (! defined($rc)) {
63 die("ERROR: Failure reading '$cfg' - $!");
65 die("ERROR: Failure processing '$cfg'");
68 print STDERR "novaboot: Read $cfg\n";
73 # We don't use $0 here, because it points to the novaboot itself and
74 # not to the novaboot script. The problem with this approach is that
75 # when a script is run as "novaboot <options> <script>" then $ARGV[0]
76 # contains the first option. Hence the -f check.
77 my $dir = abs_path($invocation_dir . ((-f $ARGV[0]) ? '/'.dirname($ARGV[0]) : ''));
78 while (-d $dir && $dir ne "/") {
79 push @cfgs, "$dir/.novaboot" if -r "$dir/.novaboot";
80 $dir = abs_path($dir."/..");
83 my $cfg = $ENV{'NOVABOOT_CONFIG'};
84 Getopt::Long::Configure(qw/no_ignore_case pass_through/);
85 GetOptions ("config|c=s" => \$cfg);
86 read_config($_) foreach $cfg or reverse @cfgs;
88 ## Command line handling
90 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);
93 $rom_prefix = 'rom://';
95 Getopt::Long::Configure(qw/no_ignore_case no_pass_through/);
98 "append|a=s" => \$append,
99 "bender|b" => \$bender,
100 "build-dir=s" => \$builddir,
101 "concat" => \$concat,
102 "dhcp-tftp|d" => \$dhcp_tftp,
103 "dump" => \$dump_opt,
104 "dump-config" => \$dump_config,
105 "grub|g:s" => \$grub_config,
106 "grub-preamble=s"=> \$grub_preamble,
107 "grub-prefix=s" => \$grub_prefix,
108 "grub2:s" => \$grub2_config,
109 "grub2-prolog=s" => \$grub2_prolog,
110 "iprelay=s" => \$iprelay,
111 "iso|i:s" => \$iso_image,
112 "name=s" => \$config_name_opt,
113 "no-file-gen" => \$no_file_gen,
116 "pulsar|p:s" => \$pulsar,
117 "qemu|Q=s" => \$qemu,
118 "qemu-append=s" => \$qemu_append,
119 "qemu-flags|q=s" => \$qemu_flags_cmd,
120 "rsync-flags=s" => \$rsync_flags,
121 "scons:s" => \$scons,
122 "scriptmod=s" => \@scriptmod,
123 "serial|s:s" => \$serial,
124 "server:s" => \$server,
125 "strip-rom" => sub { $rom_prefix = ''; },
126 "target|t=s" => sub { my ($opt_name, $opt_value) = @_;
127 exists $CFG::targets{$opt_value} or die("Unknown target '$opt_value' (valid targets are: ".join(", ", sort keys(%CFG::targets)).")");
128 GetOptionsFromString($CFG::targets{$opt_value}, %opt_spec); },
132 GetOptions %opt_spec or pod2usage(2);
133 pod2usage(1) if $help;
134 pod2usage(-exitstatus => 0, -verbose => 2) if $man;
136 ### Sanitize configuration
138 if (defined $config_name_opt && scalar(@ARGV) > 1) { die "You cannot use --name with multiple scripts"; }
140 ### Dump sanitized configuration (if requested)
144 $Data::Dumper::Indent=1;
145 print "# This file is in perl syntax.\n";
146 foreach my $key(sort(keys(%CFG::))) { # See "Symbol Tables" in perlmod(1)
147 if (defined ${$CFG::{$key}}) { print Data::Dumper->Dump([${$CFG::{$key}}], ["*$key"]); }
148 if ( @{$CFG::{$key}}) { print Data::Dumper->Dump([\@{$CFG::{$key}}], ["*$key"]); }
149 if ( %{$CFG::{$key}}) { print Data::Dumper->Dump([\%{$CFG::{$key}}], ["*$key"]); }
155 if (defined $serial) {
156 $serial ||= "/dev/ttyUSB0";
159 if (defined $grub_config) {
160 $grub_config ||= "menu.lst";
163 if (defined $grub2_config) {
164 $grub2_config ||= "grub.cfg";
167 ## Parse the novaboot script(s)
173 my ($modules, $variables, $generated, $continuation);
175 if ($ARGV ne $last_fn) { # New script
176 die "Missing EOF in $last_fn" if $file;
177 die "Unfinished line in $last_fn" if $line;
179 push @scripts, { 'filename' => $ARGV,
180 'modules' => $modules = [],
181 'variables' => $variables = {},
182 'generated' => $generated = []};
186 next if /^#/ || /^\s*$/; # Skip comments and empty lines
188 foreach my $mod(@scriptmod) { eval $mod; }
190 print "$_\n" if $dump_opt;
192 if (/^([A-Z_]+)=(.*)$/) { # Internal variable
193 $$variables{$1} = $2;
196 if (/^([^ ]*)(.*?)[[:space:]]*<<([^ ]*)$/) { # Heredoc start
197 push @$modules, "$1$2";
199 push @$generated, {filename => $1, content => $file};
203 if ($file && $_ eq $EOF) { # Heredoc end
207 if ($file) { # Heredoc content
208 push @{$file}, "$_\n";
211 $_ =~ s/^[[:space:]]*// if ($continuation);
212 if (/\\$/) { # Line continuation
213 $line .= substr($_, 0, length($_)-1);
219 $line .= " $append" if ($append && scalar(@$modules) == 0);
221 if ($line =~ /^([^ ]*)(.*?)[[:space:]]*< ?(.*)$/) { # Command substitution
222 push @$modules, "$1$2";
223 push @$generated, {filename => $1, command => $3};
227 push @$modules, $line;
231 #print Dumper(\@scripts);
237 sub generate_configs($$$) {
238 my ($base, $generated, $filename) = @_;
239 if ($base) { $base = "$base/"; };
240 foreach my $g(@$generated) {
241 if (exists $$g{content}) {
242 my $config = $$g{content};
243 my $fn = $$g{filename};
244 open(my $f, '>', $fn) || die("$fn: $!");
245 map { s|\brom://([^ ]*)|$rom_prefix$base$1|g; print $f "$_"; } @{$config};
247 print "novaboot: Created $fn\n";
248 } elsif (exists $$g{command} && ! $no_file_gen) {
249 $ENV{SRCDIR} = dirname(File::Spec->rel2abs( $filename, $invocation_dir ));
250 system_verbose("( $$g{command} ) > $$g{filename}");
255 sub generate_grub_config($$$$;$)
257 my ($filename, $title, $base, $modules_ref, $preamble) = @_;
258 if ($base) { $base = "$base/"; };
259 open(my $fg, '>', $filename) or die "$filename: $!";
260 print $fg "$preamble\n" if $preamble;
261 my $endmark = ($serial || defined $iprelay) ? ';' : '';
262 print $fg "title $title$endmark\n" if $title;
263 #print $fg "root $base\n"; # root doesn't really work for (nd)
265 foreach (@$modules_ref) {
268 my ($kbin, $kcmd) = split(' ', $_, 2);
269 $kcmd = '' if !defined $kcmd;
270 print $fg "kernel ${base}$kbin $kcmd\n";
272 s|\brom://([^ ]*)|$rom_prefix$base$1|g; # Translate rom:// files - needed for vdisk parameter of sigma0
273 print $fg "module $base$_\n";
277 print("novaboot: Created $CFG::builddir/$filename\n");
281 sub generate_grub2_config($$$$;$$)
283 my ($filename, $title, $base, $modules_ref, $preamble, $prolog) = @_;
284 if ($base && substr($base,-1,1) ne '/') { $base = "$base/"; };
285 open(my $fg, '>', $filename) or die "$filename: $!";
286 print $fg "$preamble\n" if $preamble;
287 my $endmark = ($serial || defined $iprelay) ? ';' : '';
288 $title ||= 'novaboot';
289 print $fg "menuentry $title$endmark {\n";
290 print $fg "$prolog\n" if $prolog;
292 foreach (@$modules_ref) {
295 my ($kbin, $kcmd) = split(' ', $_, 2);
296 $kcmd = '' if !defined $kcmd;
297 print $fg " multiboot ${base}$kbin $kcmd\n";
300 # GRUB2 doesn't pass filename in multiboot info so we have to duplicate it here
301 $_ = join(' ', ($args[0], @args));
302 s|\brom://|$rom_prefix|g; # We do not need to translate path for GRUB2
303 print $fg " module $base$_\n";
308 print("novaboot: Created $CFG::builddir/$filename\n");
312 sub generate_pulsar_config($$)
314 my ($filename, $modules_ref) = @_;
315 open(my $fg, '>', $filename) or die "$filename: $!";
316 print $fg "root $CFG::pulsar_root\n" if $CFG::pulsar_root;
319 foreach (@$modules_ref) {
322 ($kbin, $kcmd) = split(' ', $_, 2);
323 $kcmd = '' if !defined $kcmd;
326 s|\brom://|$rom_prefix|g;
327 print $fg "load $_\n";
330 # Put kernel as last - this is needed for booting Linux and has no influence on non-Linux OSes
331 print $fg "exec $kbin $kcmd\n";
333 print("novaboot: Created $CFG::builddir/$filename\n");
339 print "novaboot: Running: ".join(' ', map("'$_'", @_))."\n";
343 sub system_verbose($)
346 print "novaboot: Running: $cmd\n";
347 my $ret = system($cmd);
348 if ($ret & 0x007f) { die("Command terminated by a signal"); }
349 if ($ret & 0xff00) {die("Command exit with non-zero exit code"); }
350 if ($ret) { die("Command failure $ret"); }
355 if (exists $variables->{WVDESC}) {
356 print "Testing \"$variables->{WVDESC}\" in $last_fn:\n";
357 } elsif ($last_fn =~ /\.wv$/) {
358 print "Testing \"all\" in $last_fn:\n";
361 ## Handle reset and power on/off
364 if (defined $iprelay) {
365 $iprelay =~ /([.0-9]+)(:([0-9]+))?/;
368 my $paddr = sockaddr_in($port, inet_aton($addr));
369 my $proto = getprotobyname('tcp');
370 socket($IPRELAY, PF_INET, SOCK_STREAM, $proto) || die "socket: $!";
371 print "novaboot: Connecting to IP relay... ";
372 connect($IPRELAY, $paddr) || die "connect: $!";
374 $IPRELAY->autoflush(1);
377 print $IPRELAY "\xFF\xF6";
379 local $SIG{ALRM} = sub { die "Relay AYT timeout"; };
380 my $ayt_reponse = "";
381 my $read = sysread($IPRELAY, $ayt_reponse, 100);
385 print "$ayt_reponse\n";
386 if ($ayt_reponse =~ /<iprelayd: not connected/) {
394 my ($relay, $onoff) = @_;
395 die unless ($relay == 1 || $relay == 2);
397 my $cmd = ($relay == 1 ? 0x5 : 0x6) | ($onoff ? 0x20 : 0x10);
398 return "\xFF\xFA\x2C\x32".chr($cmd)."\xFF\xF0";
402 my ($relay, $onoff) = @_;
403 die unless ($relay == 1 || $relay == 2);
404 my $cmd = ($relay == 1 ? 0xdf : 0xbf) | ($onoff ? 0x00 : 0xff);
405 return "\xFF\xFA\x2C\x97".chr($cmd)."\xFF\xF0";
409 my ($relay, $onoff, $can_giveup) = @_;
410 my $confirmation = '';
411 print $IPRELAY relaycmd($relay, $onoff);
413 # We use non-blocking I/O and polling here because for some
414 # reason read() on blocking FD returns only after all
415 # requested data is available. If we get during the first
416 # read() only a part of confirmation, we may get the rest
417 # after the system boots and print someting, which may be too
419 $IPRELAY->blocking(0);
421 alarm(20); # Timeout in seconds
423 local $SIG{ALRM} = sub {
424 if ($can_giveup) { print("Relay confirmation timeout - ignoring\n"); $giveup = 1;}
425 else {die "Relay confirmation timeout";}
428 while (($index=index($confirmation, relayconf($relay, $onoff))) < 0 && !$giveup) {
429 my $read = read($IPRELAY, $confirmation, 70, length($confirmation));
430 if (!defined($read)) {
431 die("IP relay: $!") unless $! == EAGAIN;
435 #use MIME::QuotedPrint;
436 #print "confirmation = ".encode_qp($confirmation)."\n";
439 $IPRELAY->blocking(1);
443 if ($iprelay && (defined $on_opt || defined $off_opt)) {
444 relay(1, 1); # Press power button
445 if (defined $on_opt) {
446 usleep(100000); # Short press
448 print "novaboot: Switching the target off...\n";
449 usleep(6000000); # Long press to switch off
451 print $IPRELAY relay(1, 0);
455 ## Figure out the location of builddir and chdir() there
457 $CFG::builddir = $builddir;
459 if (! defined $CFG::builddir) {
460 $CFG::builddir = ( $gittop || $ENV{'HOME'}."/nul" ) . "/build";
461 if (! -d $CFG::builddir) {
462 $CFG::builddir = $ENV{SRCDIR} = dirname(File::Spec->rel2abs( ${$scripts[0]}{filename}, $invocation_dir ));
467 chdir($CFG::builddir) or die "Can't change directory to $CFG::builddir: $!";
468 print "novaboot: Entering directory `$CFG::builddir'\n";
470 ## File generation phase
471 my (%files_iso, $menu_iso, $config_name, $filename);
473 foreach my $script (@scripts) {
474 $filename = $$script{filename};
475 $modules = $$script{modules};
476 $generated = $$script{generated};
477 $variables = $$script{variables};
479 ($config_name = $filename) =~ s#.*/##;
480 $config_name = $config_name_opt if (defined $config_name_opt);
483 if (exists $variables->{KERNEL}) {
484 $kernel = $variables->{KERNEL};
486 if ($CFG::hypervisor) {
487 $kernel = $CFG::hypervisor . " ";
488 if (exists $variables->{HYPERVISOR_PARAMS}) {
489 $kernel .= $variables->{HYPERVISOR_PARAMS};
491 $kernel .= $CFG::hypervisor_params;
495 @$modules = ($kernel, @$modules) if $kernel;
496 @$modules = (@CFG::chainloaders, @$modules);
497 @$modules = ("bin/boot/bender", @$modules) if ($bender || defined $ENV{'NOVABOOT_BENDER'});
500 ($prefix = $grub_prefix) =~ s/\$NAME/$config_name/ if defined $grub_prefix;
501 $prefix ||= $CFG::builddir;
502 # TODO: use $grub_prefix as first parameter if some switch is given
503 generate_configs('', $generated, $filename);
505 ### Generate bootloader configuration files
507 my @bootloader_configs;
508 push @bootloader_configs, generate_grub_config($grub_config, $config_name, $prefix, $modules, $grub_preamble) if (defined $grub_config);
509 push @bootloader_configs, generate_grub2_config($grub2_config, $config_name, $prefix, $modules, $grub_preamble, $grub2_prolog) if (defined $grub2_config);
510 push @bootloader_configs, generate_pulsar_config($pulsar_config = "config-$pulsar", $modules) if (defined $pulsar);
513 if (defined $scons) {
514 my @files = map({ ($file) = m/([^ ]*)/; $file; } @$modules);
515 # Filter-out generated files
516 my @to_build = grep({ my $file = $_; !scalar(grep($file eq $$_{filename}, @$generated)) } @files);
517 system_verbose($scons || $CFG::scons." ".join(" ", @to_build));
520 ### Copy files (using rsync)
521 if (defined $server) {
522 (my $real_server = $server) =~ s/\$NAME/$config_name/;
524 my ($hostname, $path) = split(":", $real_server, 2);
525 if (! defined $path) {
529 my $files = join(" ", map({ ($file) = m/([^ ]*)/; $file; } ( @$modules, @bootloader_configs)));
530 map({ my $file = (split)[0]; die "$file: $!" if ! -f $file; } @$modules);
531 my $istty = -t STDOUT && ($ENV{'TERM'} || 'dumb') ne 'dumb';
532 my $progress = $istty ? "--progress" : "";
533 system_verbose("rsync $progress -RLp $rsync_flags $files $real_server");
534 if ($server =~ m|/\$NAME$| && $concat) {
535 my $cmd = join("; ", map { "( cd $path/.. && cat */$_ > $_ )" } @bootloader_configs);
536 system_verbose($hostname ? "ssh $hostname '$cmd'" : $cmd);
540 ### Prepare ISO image generation
541 if (defined $iso_image) {
542 generate_configs("(cd)", $generated, $filename);
544 generate_grub_config(\$menu, $config_name, "(cd)", $modules);
545 $menu_iso .= "$menu\n";
546 map { ($file,undef) = split; $files_iso{$file} = 1; } @$modules;
550 ## Generate ISO image
551 if (defined $iso_image) {
552 open(my $fh, ">menu-iso.lst");
553 print $fh "timeout 5\n\n$menu_iso";
555 my $files = "boot/grub/menu.lst=menu-iso.lst " . join(" ", map("$_=$_", keys(%files_iso)));
556 $iso_image ||= "$config_name.iso";
557 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");
558 print("ISO image created: $CFG::builddir/$iso_image\n");
561 ## Boot the system using various methods and send serial output to stdout
563 if (scalar(@scripts) > 1 && ( defined $dhcp_tftp || defined $serial || defined $iprelay)) {
564 die "You cannot do this with multiple scripts simultaneously";
567 if ($variables->{WVTEST_TIMEOUT}) {
568 print "wvtest: timeout ", $variables->{WVTEST_TIMEOUT}, "\n";
573 $str =~ s/^\s+|\s+$//g;
579 if (!(defined $dhcp_tftp || defined $serial || defined $iprelay || defined $server || defined $iso_image)) {
581 $qemu ||= $variables->{QEMU} || $CFG::qemu;
582 my @qemu_flags = split(" ", $qemu);
583 $qemu = shift(@qemu_flags);
585 @qemu_flags = split(/ +/, trim($variables->{QEMU_FLAGS})) if exists $variables->{QEMU_FLAGS};
586 @qemu_flags = split(/ +/, trim($qemu_flags_cmd)) if $qemu_flags_cmd;
587 push(@qemu_flags, split(/ +/, trim($qemu_append || '')));
589 if (defined $iso_image) {
590 # Boot NOVA with grub (and test the iso image)
591 push(@qemu_flags, ('-cdrom', "$config_name.iso"));
593 # Boot NOVA without GRUB
595 # Non-patched qemu doesn't like commas, but NUL can live with pluses instead of commans
596 foreach (@$modules) {s/,/+/g;}
597 generate_configs("", $generated, $filename);
599 my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);
600 $kcmd = '' if !defined $kcmd;
602 @$modules = map { if (/\.dtb$/) { $dtb=$_; (); } else { $_ } } @$modules;
603 my $initrd = join ",", @$modules;
605 push(@qemu_flags, ('-kernel', $kbin, '-append', $kcmd));
606 push(@qemu_flags, ('-initrd', $initrd)) if $initrd;
607 push(@qemu_flags, ('-dtb', $dtb)) if $dtb;
609 push(@qemu_flags, qw(-serial stdio)); # Redirect serial output (for collecting test restuls)
610 exec_verbose(($qemu, '-name', $config_name, @qemu_flags));
613 ### Local DHCPD and TFTPD
615 my ($dhcpd_pid, $tftpd_pid);
617 if (defined $dhcp_tftp)
619 generate_configs("(nd)", $generated, $filename);
620 system_verbose('mkdir -p tftpboot');
621 generate_grub_config("tftpboot/os-menu.lst", $config_name, "(nd)", \@$modules, "timeout 0");
622 open(my $fh, '>', 'dhcpd.conf');
623 my $mac = `cat /sys/class/net/eth0/address`;
625 print $fh "subnet 10.23.23.0 netmask 255.255.255.0 {
626 range 10.23.23.10 10.23.23.100;
627 filename \"bin/boot/grub/pxegrub.pxe\";
628 next-server 10.23.23.1;
631 hardware ethernet $mac;
632 fixed-address 10.23.23.1;
635 system_verbose("sudo ip a add 10.23.23.1/24 dev eth0;
636 sudo ip l set dev eth0 up;
637 sudo touch dhcpd.leases");
640 if ($dhcpd_pid == 0) {
641 # This way, the spawned server are killed when this script is killed.
642 exec_verbose("sudo dhcpd -d -cf dhcpd.conf -lf dhcpd.leases -pf dhcpd.pid");
645 if ($tftpd_pid == 0) {
646 exec_verbose("sudo in.tftpd --foreground --secure -v -v -v $CFG::builddir");
648 $SIG{TERM} = sub { print "CHILDS KILLED\n"; kill 15, $dhcpd_pid, $tftpd_pid; };
651 ### Serial line or IP relay
653 if ($serial || defined $iprelay) {
655 if (defined $iprelay) {
656 print "novaboot: Reseting the test box... ";
657 relay(2, 1, 1); # Reset the machine
664 system("stty -F $serial raw -crtscts -onlcr 115200");
665 open($CONN, "+<", $serial) || die "open $serial: $!";
669 # Pass the NOVA output to stdout.
673 kill 15, $dhcpd_pid, $tftpd_pid if ($dhcp_tftp);
677 ### Wait for dhcpc or tftpd
678 if (defined $dhcp_tftp) {
680 if ($pid == $dhcpd_pid) { print "dhcpd exited!\n"; }
681 elsif ($pid == $tftpd_pid) { print "tftpd exited!\n"; }
682 else { print "wait returned: $pid\n"; }
683 kill(15, 0); # Kill current process group i.e. all remaining children
690 novaboot - A tool for booting various operating systems on various hardware or in qemu
694 B<novaboot> [ options ] [--] script...
696 B<./script> [ options ]
702 This program makes it easier to boot NOVA or other operating system
703 (OS) in different environments. It reads a so called novaboot script
704 and uses it either to boot the OS in an emulator (e.g. in qemu) or to
705 generate the configuration for a specific bootloader and optionally to
706 copy the necessary binaries and other needed files to proper
707 locations, perhaps on a remote server. In case the system is actually
708 booted, its serial output is redirected to standard output if that is
711 A typical way of using novaboot is to make the novaboot script
712 executable and set its first line to I<#!/usr/bin/env novaboot>. Then,
713 booting a particular OS configuration becomes the same as executing a
714 local program - the novaboot script.
716 With C<novaboot> you can:
722 Run an OS in Qemu. This is the default action when no other action is
723 specified by command line switches. Thus running C<novaboot ./script>
724 (or C<./script> as described above) will run Qemu and make it boot the
725 configuration specified in the I<script>.
729 Create a bootloader configuration file (currently supported
730 bootloaders are GRUB, GRUB2 and Pulsar) and copy it with all other
731 files needed for booting to another, perhaps remote, location.
733 ./script --server --iprelay=192.168.1.2
735 This command copies files to a TFTP server specified in the
736 configuration file and uses TCP/IP-controlled relay to reset the test
737 box and receive its serial output.
741 Run DHCP and TFTP server on developer's machine to PXE-boot NOVA from
746 When a PXE-bootable machine is connected via Ethernet to developer's
747 machine, it will boot the configuration described in I<script>.
751 Create bootable ISO images. E.g.
753 novaboot --iso -- script1 script2
755 The created ISO image will have GRUB bootloader installed on it and
756 the boot menu will allow selecting between I<script1> and I<script2>
761 =head1 PHASES AND OPTIONS
763 Novaboot perform its work in several phases. Each phase can be
764 influenced by several options, certain phases can be skipped. The list
765 of phases with the corresponding options follows.
767 =head2 Configuration reading phase
769 After starting, novaboot reads configuration files. By default, it
770 searches for files named F<.novaboot> starting from the directory of
771 the novaboot script (or working directory, see bellow) and continuing
772 upwards up to the root directory. The configuration files are read in
773 order from the root directory downwards with latter files overriding
774 settings from the former ones.
776 In certain cases, the location of the novaboot script cannot be
777 determined in this early phase. This happens either when the script is
778 read from the standard input or when novaboot is invoked explicitly
779 and options precede the script name, as in the example L</"4."> above.
780 In this case the current working directory is used as a starting point
781 for configuration file search.
785 =item -c, --config=<filename>
787 Use the specified configuration file instead of the default one(s).
791 =head2 Command line processing phase
797 Dump the current configuration to stdout end exits. Useful as an
798 initial template for a configuration file.
802 Print short (B<-h>) or long (B<--help>) help.
804 =item -t, --target=<target>
806 This option serves as a user configurable shortcut for other novaboot
807 options. The effect of this option is the same as the options stored
808 in the C<%targets> configuration variable under key I<target>. See
809 also L</"CONFIGURATION FILE">.
813 =head2 Script preprocessing phase
815 This phases allows to modify the parsed novaboot script before it is
816 used in the later phases.
820 =item -a, --append=<parameters>
822 Appends a string to the first "filename" line in the novaboot script.
823 This can be used to append parameters to the kernel's or root task's
828 Use F<bender> chainloader. Bender scans the PCI bus for PCI serial
829 ports and stores the information about them in the BIOS data area for
834 Prints the content of the novaboot script after removing comments and
835 evaluating all I<--scriptmod> expressions. Exit after reading (and
838 =item --scriptmod=I<perl expression>
840 When novaboot script is read, I<perl expression> is executed for every
841 line (in $_ variable). For example, C<novaboot
842 --scriptmod=s/sigma0/omega6/g> replaces every occurrence of I<sigma0>
843 in the script with I<omega6>.
845 When this option is present, it overrides I<$script_modifier> variable
846 from the configuration file, which has the same effect. If this option
847 is given multiple times all expressions are evaluated in the command
852 Strip I<rom://> prefix from command lines and generated config files.
853 The I<rom://> prefix is used by NUL. For NRE, it has to be stripped.
857 =head2 File generation phase
859 In this phase, files needed for booting are generated in a so called
860 I<build directory> (see TODO). In most cases configuration for a
861 bootloader is generated automatically by novaboot. It is also possible
862 to generate other files using I<heredoc> or I<"<"> syntax in novaboot
863 scripts. Finally, binaries can be generated in this phases by running
868 =item --build-dir=<directory>
870 Overrides the default build directory location.
872 The default build directory location is determined as follows:
874 If there is a configuration file, the value specified in the
875 I<$builddir> variable is used. Otherwise, if the current working
876 directory is inside git work tree and there is F<build> directory at
877 the top of that tree, it is used. Otherwise, if directory
878 F<~/nul/build> exists, it is used. Otherwise, it is the directory that
879 contains the first processed novaboot script.
881 =item -g, --grub[=I<filename>]
883 Generates grub bootloader menu file. If the I<filename> is not
884 specified, F<menu.lst> is used. The I<filename> is relative to the
885 build directory (see B<--build-dir>).
887 =item --grub-preamble=I<prefix>
889 Specifies the I<preable> that is at the beginning of the generated
890 GRUB or GRUB2 config files. This is useful for specifying GRUB's
893 =item --grub-prefix=I<prefix>
895 Specifies I<prefix> that is put in front of every file name in GRUB's
896 F<menu.lst>. The default value is the absolute path to the build directory.
898 If the I<prefix> contains string $NAME, it will be replaced with the
899 name of the novaboot script (see also B<--name>).
901 =item --grub2[=I<filename>]
903 Generate GRUB2 menuentry in I<filename>. If I<filename> is not
904 specified F<grub.cfg> is used. The content of the menuentry can be
905 customized with B<--grub-preable>, B<--grub2-prolog> or
906 B<--grub_prefix> options.
908 In order to use the the generated menuentry on your development
909 machine that uses GRUB2, append the following snippet to
910 F</etc/grub.d/40_custom> file and regenerate your grub configuration,
911 i.e. run update-grub on Debian/Ubuntu.
913 if [ -f /path/to/nul/build/grub.cfg ]; then
914 source /path/to/nul/build/grub.cfg
917 =item --grub2-prolog=I<prolog>
919 Specifies text I<preable> that is put at the begiging of the entry
922 =item --name=I<string>
924 Use the name I<string> instead of the name of the novaboot script.
925 This name is used for things like a title of grub menu or for the
926 server directory where the boot files are copied to.
930 Do not generate files on the fly (i.e. "<" syntax) except for the
931 files generated via "<<WORD" syntax.
933 =item -p, --pulsar[=mac]
935 Generates pulsar bootloader configuration file whose name is based on
936 the MAC address specified either on the command line or taken from
937 I<.novaboot> configuration file.
941 =head2 File deployment phase
943 In some setups, it is necessary to copy the files needed for booting
944 to a particular location, e.g. to a TFTP boot server or to the
949 =item -d, --dhcp-tftp
951 Turns your workstation into a DHCP and TFTP server so that NOVA
952 can be booted via PXE BIOS on a test machine directly connected by
953 a plain Ethernet cable to your workstation.
955 The DHCP and TFTP servers require root privileges and C<novaboot>
956 uses C<sudo> command to obtain those. You can put the following to
957 I</etc/sudoers> to allow running the necessary commands without
960 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
961 your_login ALL=NOPASSWD: NOVABOOT
963 =item -i, --iso[=filename]
965 Generates the ISO image that boots NOVA system via GRUB. If no filename
966 is given, the image is stored under I<NAME>.iso, where I<NAME> is the name
967 of the novaboot script (see also B<--name>).
969 =item --server[=[[user@]server:]path]
971 Copy all files needed for booting to another location (implies B<-g>
972 unless B<--grub2> is given). The files will be copied (by B<rsync>
973 tool) to the directory I<path>. If the I<path> contains string $NAME,
974 it will be replaced with the name of the novaboot script (see also
979 If B<--server> is used and its value ends with $NAME, then after
980 copying the files, a new bootloader configuration file (e.g. menu.lst)
981 is created at I<path-wo-name>, i.e. the path specified by B<--server>
982 with $NAME part removed. The content of the file is created by
983 concatenating all files of the same name from all subdirectories of
984 I<path-wo-name> found on the "server".
986 =item --rsync-flags=I<flags>
988 Specifies which I<flags> are appended to F<rsync> command line when
989 copying files as a result of I<--server> option.
991 =item --scons[=scons command]
993 Runs I<scons> to build files that are not generated by novaboot
998 =head2 Target power-on and reset phase
1002 =item --iprelay=I<addr[:port]>
1004 Use IP relay to reset the machine and to get the serial output. The IP
1005 address of the relay is given by I<addr> parameter.
1007 Note: This option is expected to work with HWG-ER02a IP relays.
1011 Switch on/off the target machine. Currently works only with
1014 =item -Q, --qemu=I<qemu-binary>
1016 The name of qemu binary to use. The default is 'qemu'.
1018 =item --qemu-append=I<flags>
1020 Append I<flags> to the default qemu flags (QEMU_FLAGS variable or
1021 C<-cpu coreduo -smp 2>).
1023 =item -q, --qemu-flags=I<flags>
1025 Replace the default qemu flags (QEMU_FLAGS variable or C<-cpu coreduo
1026 -smp 2>) with I<flags> specified here.
1030 =head2 Interaction with the bootloader on the target
1032 See B<--serial>. There will be new options soon.
1034 =head2 Target's output reception phase
1038 =item -s, --serial[=device]
1040 Use serial line to control GRUB bootloader and to see the output
1041 serial output of the machine. The default value is F</dev/ttyUSB0>.
1045 See also B<--iprelay>.
1047 =head2 Termination phase
1049 Daemons that were spwned (F<dhcpd> and F<tftpd>) are killed here.
1051 =head1 NOVABOOT SCRIPT SYNTAX
1053 The syntax tries to mimic POSIX shell syntax. The syntax is defined with the following rules.
1055 Lines starting with "#" are ignored.
1057 Lines that end with "\" are concatenated with the following line after
1058 removal of the final "\" and leading whitespace of the following line.
1060 Lines in the form I<VARIABLE=...> (i.e. matching '^[A-Z_]+=' regular
1061 expression) assign values to internal variables. See VARIABLES
1064 Otherwise, the first word on the line represents the filename
1065 (relative to the build directory (see B<--build-dir>) of the module to
1066 load and the remaining words are passed as the command line
1069 When the line ends with "<<WORD" then the subsequent lines until the
1070 line containing only WORD are copied literally to the file named on
1073 When the line ends with "< CMD" the command CMD is executed with
1074 C</bin/sh> and its standard output is stored in the file named on that
1075 line. The SRCDIR variable in CMD's environment is set to the absolute
1076 path of the directory containing the interpreted novaboot script.
1079 #!/usr/bin/env novaboot
1080 WVDESC=Example program
1081 bin/apps/sigma0.nul S0_DEFAULT script_start:1,1 \
1082 verbose hostkeyb:0,0x60,1,12,2
1084 hello.nulconfig <<EOF
1085 sigma0::mem:16 name::/s0/log name::/s0/timer name::/s0/fs/rom ||
1086 rom://bin/apps/hello.nul
1089 This example will load three modules: sigma0.nul, hello.nul and
1090 hello.nulconfig. sigma0 gets some command line parameters and
1091 hello.nulconfig file is generated on the fly from the lines between
1096 The following variables are interpreted in the novaboot script:
1102 Description of the wvtest-compliant program.
1104 =item WVTEST_TIMEOUT
1106 The timeout in seconds for WvTest harness. If no complete line appears
1107 in the test output within the time specified here, the test fails. It
1108 is necessary to specify this for long running tests that produce no
1109 intermediate output.
1113 Use a specific qemu binary (can be overriden with B<-Q>) and flags
1114 when booting this script under qemu. If QEMU_FLAGS variable is also
1115 specified flags specified in QEMU variable are replaced by those in
1120 Use specific qemu flags (can be overriden with B<-q>).
1122 =item HYPERVISOR_PARAMS
1124 Parameters passed to hypervisor. The default value is "serial", unless
1125 overriden in configuration file.
1129 The kernel to use instead of NOVA hypervisor specified in the
1130 configuration file. The value should contain the name of the kernel
1131 image as well as its command line parameters. If this variable is
1132 defined and non-empty, the variable HYPERVISOR_PARAMS is not used.
1136 =head1 CONFIGURATION FILE
1138 Novaboot can read its configuration from a file. Configuration file
1139 was necessary in early days of novaboot. Nowadays, an attempt is made
1140 to not use the configuration file because it makes certain novaboot
1141 scripts unusable on systems without (or with different) configuration
1142 file. The only recommended use of the configuration file is to specify
1143 custom_options (see bellow).
1145 If you decide to use the configuration file, it is looked up, by
1146 default, in files named F<.novaboot> as described in L</Configuration
1147 reading phase>. Alternatively, its location can be specified with the
1148 B<-c> switch or with the NOVABOOT_CONFIG environment variable. The
1149 configuration file has perl syntax and should set values of certain
1150 Perl variables. The current configuration can be dumped with the
1151 B<--dump-config> switch. Some configuration variables can be overriden
1152 by environment variables (see below) or by command line switches.
1154 Documentation of some configuration variables follows:
1160 Custom chainloaders to load before hypervisor and files specified in
1161 novaboot script. E.g. ('bin/boot/bender promisc', 'bin/boot/zapp').
1165 Hash of shortcuts to be used with the B<--target> option. If the hash
1166 contains, for instance, the following pair of values
1168 'mybox' => '--server=boot:/tftproot --serial=/dev/ttyUSB0 --grub',
1170 then the following two commands are equivalent:
1172 ./script --server=boot:/tftproot --serial=/dev/ttyUSB0 --grub
1177 =head1 ENVIRONMENT VARIABLES
1179 Some options can be specified not only via config file or command line
1180 but also through environment variables. Environment variables override
1181 the values from configuration file and command line parameters
1182 override the environment variables.
1186 =item NOVABOOT_CONFIG
1188 Name of the novaboot configuration file to use instead of the default
1191 =item NOVABOOT_BENDER
1193 Defining this variable has the same meaning as B<--bender> option.
1199 Michal Sojka <sojka@os.inf.tu-dresden.de>