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 "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=0;
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"]); }
156 if (defined $serial) {
157 $serial ||= "/dev/ttyUSB0";
160 if (defined $grub_config) {
161 $grub_config ||= "menu.lst";
164 if (defined $grub2_config) {
165 $grub2_config ||= "grub.cfg";
168 ## Parse the novaboot script(s)
174 my ($modules, $variables, $generated, $continuation);
176 if ($ARGV ne $last_fn) { # New script
177 die "Missing EOF in $last_fn" if $file;
178 die "Unfinished line in $last_fn" if $line;
180 push @scripts, { 'filename' => $ARGV,
181 'modules' => $modules = [],
182 'variables' => $variables = {},
183 'generated' => $generated = []};
187 next if /^#/ || /^\s*$/; # Skip comments and empty lines
189 foreach my $mod(@scriptmod) { eval $mod; }
191 print "$_\n" if $dump_opt;
193 if (/^([A-Z_]+)=(.*)$/) { # Internal variable
194 $$variables{$1} = $2;
197 if (/^([^ ]*)(.*?)[[:space:]]*<<([^ ]*)$/) { # Heredoc start
198 push @$modules, "$1$2";
200 push @$generated, {filename => $1, content => $file};
204 if ($file && $_ eq $EOF) { # Heredoc end
208 if ($file) { # Heredoc content
209 push @{$file}, "$_\n";
212 $_ =~ s/^[[:space:]]*// if ($continuation);
213 if (/\\$/) { # Line continuation
214 $line .= substr($_, 0, length($_)-1);
220 $line .= " $append" if ($append && scalar(@$modules) == 0);
222 if ($line =~ /^([^ ]*)(.*?)[[:space:]]*< ?(.*)$/) { # Command substitution
223 push @$modules, "$1$2";
224 push @$generated, {filename => $1, command => $3};
228 push @$modules, $line;
232 #print Dumper(\@scripts);
238 sub generate_configs($$$) {
239 my ($base, $generated, $filename) = @_;
240 if ($base) { $base = "$base/"; };
241 foreach my $g(@$generated) {
242 if (exists $$g{content}) {
243 my $config = $$g{content};
244 my $fn = $$g{filename};
245 open(my $f, '>', $fn) || die("$fn: $!");
246 map { s|\brom://([^ ]*)|$rom_prefix$base$1|g; print $f "$_"; } @{$config};
248 print "novaboot: Created $fn\n";
249 } elsif (exists $$g{command} && ! $no_file_gen) {
250 $ENV{SRCDIR} = dirname(File::Spec->rel2abs( $filename, $invocation_dir ));
251 system_verbose("( $$g{command} ) > $$g{filename}");
256 sub generate_grub_config($$$$;$)
258 my ($filename, $title, $base, $modules_ref, $preamble) = @_;
259 if ($base) { $base = "$base/"; };
260 open(my $fg, '>', $filename) or die "$filename: $!";
261 print $fg "$preamble\n" if $preamble;
262 my $endmark = ($serial || defined $iprelay) ? ';' : '';
263 print $fg "title $title$endmark\n" if $title;
264 #print $fg "root $base\n"; # root doesn't really work for (nd)
266 foreach (@$modules_ref) {
269 my ($kbin, $kcmd) = split(' ', $_, 2);
270 $kcmd = '' if !defined $kcmd;
271 print $fg "kernel ${base}$kbin $kcmd\n";
273 s|\brom://([^ ]*)|$rom_prefix$base$1|g; # Translate rom:// files - needed for vdisk parameter of sigma0
274 print $fg "module $base$_\n";
278 print("novaboot: Created $CFG::builddir/$filename\n");
282 sub generate_grub2_config($$$$;$$)
284 my ($filename, $title, $base, $modules_ref, $preamble, $prolog) = @_;
285 if ($base && substr($base,-1,1) ne '/') { $base = "$base/"; };
286 open(my $fg, '>', $filename) or die "$filename: $!";
287 print $fg "$preamble\n" if $preamble;
288 my $endmark = ($serial || defined $iprelay) ? ';' : '';
289 $title ||= 'novaboot';
290 print $fg "menuentry $title$endmark {\n";
291 print $fg "$prolog\n" if $prolog;
293 foreach (@$modules_ref) {
296 my ($kbin, $kcmd) = split(' ', $_, 2);
297 $kcmd = '' if !defined $kcmd;
298 print $fg " multiboot ${base}$kbin $kcmd\n";
301 # GRUB2 doesn't pass filename in multiboot info so we have to duplicate it here
302 $_ = join(' ', ($args[0], @args));
303 s|\brom://|$rom_prefix|g; # We do not need to translate path for GRUB2
304 print $fg " module $base$_\n";
309 print("novaboot: Created $CFG::builddir/$filename\n");
313 sub generate_pulsar_config($$)
315 my ($filename, $modules_ref) = @_;
316 open(my $fg, '>', $filename) or die "$filename: $!";
317 print $fg "root $CFG::pulsar_root\n" if $CFG::pulsar_root;
320 foreach (@$modules_ref) {
323 ($kbin, $kcmd) = split(' ', $_, 2);
324 $kcmd = '' if !defined $kcmd;
327 s|\brom://|$rom_prefix|g;
328 print $fg "load $_\n";
331 # Put kernel as last - this is needed for booting Linux and has no influence on non-Linux OSes
332 print $fg "exec $kbin $kcmd\n";
334 print("novaboot: Created $CFG::builddir/$filename\n");
340 print "novaboot: Running: ".join(' ', map("'$_'", @_))."\n";
344 sub system_verbose($)
347 print "novaboot: Running: $cmd\n";
348 my $ret = system($cmd);
349 if ($ret & 0x007f) { die("Command terminated by a signal"); }
350 if ($ret & 0xff00) {die("Command exit with non-zero exit code"); }
351 if ($ret) { die("Command failure $ret"); }
356 if (exists $variables->{WVDESC}) {
357 print "Testing \"$variables->{WVDESC}\" in $last_fn:\n";
358 } elsif ($last_fn =~ /\.wv$/) {
359 print "Testing \"all\" in $last_fn:\n";
362 ## Handle reset and power on/off
365 if (defined $iprelay) {
366 $iprelay =~ /([.0-9]+)(:([0-9]+))?/;
369 my $paddr = sockaddr_in($port, inet_aton($addr));
370 my $proto = getprotobyname('tcp');
371 socket($IPRELAY, PF_INET, SOCK_STREAM, $proto) || die "socket: $!";
372 print "novaboot: Connecting to IP relay... ";
373 connect($IPRELAY, $paddr) || die "connect: $!";
375 $IPRELAY->autoflush(1);
378 print $IPRELAY "\xFF\xF6";
380 local $SIG{ALRM} = sub { die "Relay AYT timeout"; };
381 my $ayt_reponse = "";
382 my $read = sysread($IPRELAY, $ayt_reponse, 100);
386 print "$ayt_reponse\n";
387 if ($ayt_reponse =~ /<iprelayd: not connected/) {
395 my ($relay, $onoff) = @_;
396 die unless ($relay == 1 || $relay == 2);
398 my $cmd = ($relay == 1 ? 0x5 : 0x6) | ($onoff ? 0x20 : 0x10);
399 return "\xFF\xFA\x2C\x32".chr($cmd)."\xFF\xF0";
403 my ($relay, $onoff) = @_;
404 die unless ($relay == 1 || $relay == 2);
405 my $cmd = ($relay == 1 ? 0xdf : 0xbf) | ($onoff ? 0x00 : 0xff);
406 return "\xFF\xFA\x2C\x97".chr($cmd)."\xFF\xF0";
410 my ($relay, $onoff, $can_giveup) = @_;
411 my $confirmation = '';
412 print $IPRELAY relaycmd($relay, $onoff);
414 # We use non-blocking I/O and polling here because for some
415 # reason read() on blocking FD returns only after all
416 # requested data is available. If we get during the first
417 # read() only a part of confirmation, we may get the rest
418 # after the system boots and print someting, which may be too
420 $IPRELAY->blocking(0);
422 alarm(20); # Timeout in seconds
424 local $SIG{ALRM} = sub {
425 if ($can_giveup) { print("Relay confirmation timeout - ignoring\n"); $giveup = 1;}
426 else {die "Relay confirmation timeout";}
429 while (($index=index($confirmation, relayconf($relay, $onoff))) < 0 && !$giveup) {
430 my $read = read($IPRELAY, $confirmation, 70, length($confirmation));
431 if (!defined($read)) {
432 die("IP relay: $!") unless $! == EAGAIN;
436 #use MIME::QuotedPrint;
437 #print "confirmation = ".encode_qp($confirmation)."\n";
440 $IPRELAY->blocking(1);
444 if ($iprelay && (defined $on_opt || defined $off_opt)) {
445 relay(1, 1); # Press power button
446 if (defined $on_opt) {
447 usleep(100000); # Short press
449 print "novaboot: Switching the target off...\n";
450 usleep(6000000); # Long press to switch off
452 print $IPRELAY relay(1, 0);
456 ## Figure out the location of builddir and chdir() there
458 $CFG::builddir = $builddir;
460 if (! defined $CFG::builddir) {
461 $CFG::builddir = ( $gittop || $ENV{'HOME'}."/nul" ) . "/build";
462 if (! -d $CFG::builddir) {
463 $CFG::builddir = $ENV{SRCDIR} = dirname(File::Spec->rel2abs( ${$scripts[0]}{filename}, $invocation_dir ));
468 chdir($CFG::builddir) or die "Can't change directory to $CFG::builddir: $!";
469 print "novaboot: Entering directory `$CFG::builddir'\n";
471 ## File generation phase
472 my (%files_iso, $menu_iso, $config_name, $filename);
474 foreach my $script (@scripts) {
475 $filename = $$script{filename};
476 $modules = $$script{modules};
477 $generated = $$script{generated};
478 $variables = $$script{variables};
480 ($config_name = $filename) =~ s#.*/##;
481 $config_name = $config_name_opt if (defined $config_name_opt);
484 if (exists $variables->{KERNEL}) {
485 $kernel = $variables->{KERNEL};
487 if ($CFG::hypervisor) {
488 $kernel = $CFG::hypervisor . " ";
489 if (exists $variables->{HYPERVISOR_PARAMS}) {
490 $kernel .= $variables->{HYPERVISOR_PARAMS};
492 $kernel .= $CFG::hypervisor_params;
496 @$modules = ($kernel, @$modules) if $kernel;
497 @$modules = (@CFG::chainloaders, @$modules);
498 @$modules = ("bin/boot/bender", @$modules) if ($bender || defined $ENV{'NOVABOOT_BENDER'});
501 ($prefix = $grub_prefix) =~ s/\$NAME/$config_name/ if defined $grub_prefix;
502 $prefix ||= $CFG::builddir;
503 # TODO: use $grub_prefix as first parameter if some switch is given
504 generate_configs('', $generated, $filename);
506 ### Generate bootloader configuration files
508 my @bootloader_configs;
509 push @bootloader_configs, generate_grub_config($grub_config, $config_name, $prefix, $modules, $grub_preamble) if (defined $grub_config);
510 push @bootloader_configs, generate_grub2_config($grub2_config, $config_name, $prefix, $modules, $grub_preamble, $grub2_prolog) if (defined $grub2_config);
511 push @bootloader_configs, generate_pulsar_config($pulsar_config = "config-$pulsar", $modules) if (defined $pulsar);
514 if (defined $scons) {
515 my @files = map({ ($file) = m/([^ ]*)/; $file; } @$modules);
516 # Filter-out generated files
517 my @to_build = grep({ my $file = $_; !scalar(grep($file eq $$_{filename}, @$generated)) } @files);
518 system_verbose($scons || $CFG::scons." ".join(" ", @to_build));
521 ### Copy files (using rsync)
522 if (defined $server) {
523 (my $real_server = $server) =~ s/\$NAME/$config_name/;
525 my ($hostname, $path) = split(":", $real_server, 2);
526 if (! defined $path) {
530 my $files = join(" ", map({ ($file) = m/([^ ]*)/; $file; } ( @$modules, @bootloader_configs)));
531 map({ my $file = (split)[0]; die "$file: $!" if ! -f $file; } @$modules);
532 my $istty = -t STDOUT && ($ENV{'TERM'} || 'dumb') ne 'dumb';
533 my $progress = $istty ? "--progress" : "";
534 system_verbose("rsync $progress -RLp $rsync_flags $files $real_server");
535 if ($server =~ m|/\$NAME$| && $concat) {
536 my $cmd = join("; ", map { "( cd $path/.. && cat */$_ > $_ )" } @bootloader_configs);
537 system_verbose($hostname ? "ssh $hostname '$cmd'" : $cmd);
541 ### Prepare ISO image generation
542 if (defined $iso_image) {
543 generate_configs("(cd)", $generated, $filename);
545 generate_grub_config(\$menu, $config_name, "(cd)", $modules);
546 $menu_iso .= "$menu\n";
547 map { ($file,undef) = split; $files_iso{$file} = 1; } @$modules;
551 ## Generate ISO image
552 if (defined $iso_image) {
553 open(my $fh, ">menu-iso.lst");
554 print $fh "timeout 5\n\n$menu_iso";
556 my $files = "boot/grub/menu.lst=menu-iso.lst " . join(" ", map("$_=$_", keys(%files_iso)));
557 $iso_image ||= "$config_name.iso";
558 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");
559 print("ISO image created: $CFG::builddir/$iso_image\n");
562 ## Boot the system using various methods and send serial output to stdout
564 if (scalar(@scripts) > 1 && ( defined $dhcp_tftp || defined $serial || defined $iprelay)) {
565 die "You cannot do this with multiple scripts simultaneously";
568 if ($variables->{WVTEST_TIMEOUT}) {
569 print "wvtest: timeout ", $variables->{WVTEST_TIMEOUT}, "\n";
574 $str =~ s/^\s+|\s+$//g;
580 if (!(defined $dhcp_tftp || defined $serial || defined $iprelay || defined $server || defined $iso_image)) {
582 $qemu ||= $variables->{QEMU} || $CFG::qemu;
583 my @qemu_flags = split(" ", $qemu);
584 $qemu = shift(@qemu_flags);
586 @qemu_flags = split(/ +/, trim($variables->{QEMU_FLAGS})) if exists $variables->{QEMU_FLAGS};
587 @qemu_flags = split(/ +/, trim($qemu_flags_cmd)) if $qemu_flags_cmd;
588 push(@qemu_flags, split(/ +/, trim($qemu_append || '')));
590 if (defined $iso_image) {
591 # Boot NOVA with grub (and test the iso image)
592 push(@qemu_flags, ('-cdrom', "$config_name.iso"));
594 # Boot NOVA without GRUB
596 # Non-patched qemu doesn't like commas, but NUL can live with pluses instead of commans
597 foreach (@$modules) {s/,/+/g;}
598 generate_configs("", $generated, $filename);
600 my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);
601 $kcmd = '' if !defined $kcmd;
603 @$modules = map { if (/\.dtb$/) { $dtb=$_; (); } else { $_ } } @$modules;
604 my $initrd = join ",", @$modules;
606 push(@qemu_flags, ('-kernel', $kbin, '-append', $kcmd));
607 push(@qemu_flags, ('-initrd', $initrd)) if $initrd;
608 push(@qemu_flags, ('-dtb', $dtb)) if $dtb;
610 push(@qemu_flags, qw(-serial stdio)); # Redirect serial output (for collecting test restuls)
611 exec_verbose(($qemu, '-name', $config_name, @qemu_flags));
614 ### Local DHCPD and TFTPD
616 my ($dhcpd_pid, $tftpd_pid);
618 if (defined $dhcp_tftp)
620 generate_configs("(nd)", $generated, $filename);
621 system_verbose('mkdir -p tftpboot');
622 generate_grub_config("tftpboot/os-menu.lst", $config_name, "(nd)", \@$modules, "timeout 0");
623 open(my $fh, '>', 'dhcpd.conf');
624 my $mac = `cat /sys/class/net/eth0/address`;
626 print $fh "subnet 10.23.23.0 netmask 255.255.255.0 {
627 range 10.23.23.10 10.23.23.100;
628 filename \"bin/boot/grub/pxegrub.pxe\";
629 next-server 10.23.23.1;
632 hardware ethernet $mac;
633 fixed-address 10.23.23.1;
636 system_verbose("sudo ip a add 10.23.23.1/24 dev eth0;
637 sudo ip l set dev eth0 up;
638 sudo touch dhcpd.leases");
641 if ($dhcpd_pid == 0) {
642 # This way, the spawned server are killed when this script is killed.
643 exec_verbose("sudo dhcpd -d -cf dhcpd.conf -lf dhcpd.leases -pf dhcpd.pid");
646 if ($tftpd_pid == 0) {
647 exec_verbose("sudo in.tftpd --foreground --secure -v -v -v $CFG::builddir");
649 $SIG{TERM} = sub { print "CHILDS KILLED\n"; kill 15, $dhcpd_pid, $tftpd_pid; };
652 ### Serial line or IP relay
654 if ($serial || defined $iprelay) {
656 if (defined $iprelay) {
657 print "novaboot: Reseting the test box... ";
658 relay(2, 1, 1); # Reset the machine
665 system("stty -F $serial raw -crtscts -onlcr 115200");
666 open($CONN, "+<", $serial) || die "open $serial: $!";
670 # Pass the NOVA output to stdout.
674 kill 15, $dhcpd_pid, $tftpd_pid if ($dhcp_tftp);
678 ### Wait for dhcpc or tftpd
679 if (defined $dhcp_tftp) {
681 if ($pid == $dhcpd_pid) { print "dhcpd exited!\n"; }
682 elsif ($pid == $tftpd_pid) { print "tftpd exited!\n"; }
683 else { print "wait returned: $pid\n"; }
684 kill(15, 0); # Kill current process group i.e. all remaining children
691 novaboot - A tool for booting various operating systems on various hardware or in qemu
695 B<novaboot> [ options ] [--] script...
697 B<./script> [ options ]
703 This program makes it easier to boot NOVA or other operating system
704 (OS) in different environments. It reads a so called novaboot script
705 and uses it either to boot the OS in an emulator (e.g. in qemu) or to
706 generate the configuration for a specific bootloader and optionally to
707 copy the necessary binaries and other needed files to proper
708 locations, perhaps on a remote server. In case the system is actually
709 booted, its serial output is redirected to standard output if that is
712 A typical way of using novaboot is to make the novaboot script
713 executable and set its first line to I<#!/usr/bin/env novaboot>. Then,
714 booting a particular OS configuration becomes the same as executing a
715 local program - the novaboot script.
717 With C<novaboot> you can:
723 Run an OS in Qemu. This is the default action when no other action is
724 specified by command line switches. Thus running C<novaboot ./script>
725 (or C<./script> as described above) will run Qemu and make it boot the
726 configuration specified in the I<script>.
730 Create a bootloader configuration file (currently supported
731 bootloaders are GRUB, GRUB2 and Pulsar) and copy it with all other
732 files needed for booting to another, perhaps remote, location.
734 ./script --server --iprelay=192.168.1.2
736 This command copies files to a TFTP server specified in the
737 configuration file and uses TCP/IP-controlled relay to reset the test
738 box and receive its serial output.
742 Run DHCP and TFTP server on developer's machine to PXE-boot NOVA from
747 When a PXE-bootable machine is connected via Ethernet to developer's
748 machine, it will boot the configuration described in I<script>.
752 Create bootable ISO images. E.g.
754 novaboot --iso -- script1 script2
756 The created ISO image will have GRUB bootloader installed on it and
757 the boot menu will allow selecting between I<script1> and I<script2>
762 =head1 PHASES AND OPTIONS
764 Novaboot perform its work in several phases. Each phase can be
765 influenced by several options, certain phases can be skipped. The list
766 of phases with the corresponding options follows.
768 =head2 Configuration reading phase
770 After starting, novaboot reads configuration files. By default, it
771 searches for files named F<.novaboot> starting from the directory of
772 the novaboot script (or working directory, see bellow) and continuing
773 upwards up to the root directory. The configuration files are read in
774 order from the root directory downwards with latter files overriding
775 settings from the former ones.
777 In certain cases, the location of the novaboot script cannot be
778 determined in this early phase. This happens either when the script is
779 read from the standard input or when novaboot is invoked explicitly
780 and options precede the script name, as in the example L</"4."> above.
781 In this case the current working directory is used as a starting point
782 for configuration file search.
786 =item -c, --config=<filename>
788 Use the specified configuration file instead of the default one(s).
792 =head2 Command line processing phase
798 Dump the current configuration to stdout end exits. Useful as an
799 initial template for a configuration file.
803 Print short (B<-h>) or long (B<--help>) help.
805 =item -t, --target=<target>
807 This option serves as a user configurable shortcut for other novaboot
808 options. The effect of this option is the same as the options stored
809 in the C<%targets> configuration variable under key I<target>. See
810 also L</"CONFIGURATION FILE">.
814 =head2 Script preprocessing phase
816 This phases allows to modify the parsed novaboot script before it is
817 used in the later phases.
821 =item -a, --append=<parameters>
823 Appends a string to the first "filename" line in the novaboot script.
824 This can be used to append parameters to the kernel's or root task's
829 Use F<bender> chainloader. Bender scans the PCI bus for PCI serial
830 ports and stores the information about them in the BIOS data area for
835 Prints the content of the novaboot script after removing comments and
836 evaluating all I<--scriptmod> expressions. Exit after reading (and
839 =item --scriptmod=I<perl expression>
841 When novaboot script is read, I<perl expression> is executed for every
842 line (in $_ variable). For example, C<novaboot
843 --scriptmod=s/sigma0/omega6/g> replaces every occurrence of I<sigma0>
844 in the script with I<omega6>.
846 When this option is present, it overrides I<$script_modifier> variable
847 from the configuration file, which has the same effect. If this option
848 is given multiple times all expressions are evaluated in the command
853 Strip I<rom://> prefix from command lines and generated config files.
854 The I<rom://> prefix is used by NUL. For NRE, it has to be stripped.
858 =head2 File generation phase
860 In this phase, files needed for booting are generated in a so called
861 I<build directory> (see TODO). In most cases configuration for a
862 bootloader is generated automatically by novaboot. It is also possible
863 to generate other files using I<heredoc> or I<"<"> syntax in novaboot
864 scripts. Finally, binaries can be generated in this phases by running
869 =item --build-dir=<directory>
871 Overrides the default build directory location.
873 The default build directory location is determined as follows:
875 If there is a configuration file, the value specified in the
876 I<$builddir> variable is used. Otherwise, if the current working
877 directory is inside git work tree and there is F<build> directory at
878 the top of that tree, it is used. Otherwise, if directory
879 F<~/nul/build> exists, it is used. Otherwise, it is the directory that
880 contains the first processed novaboot script.
882 =item -g, --grub[=I<filename>]
884 Generates grub bootloader menu file. If the I<filename> is not
885 specified, F<menu.lst> is used. The I<filename> is relative to the
886 build directory (see B<--build-dir>).
888 =item --grub-preamble=I<prefix>
890 Specifies the I<preable> that is at the beginning of the generated
891 GRUB or GRUB2 config files. This is useful for specifying GRUB's
894 =item --grub-prefix=I<prefix>
896 Specifies I<prefix> that is put in front of every file name in GRUB's
897 F<menu.lst>. The default value is the absolute path to the build directory.
899 If the I<prefix> contains string $NAME, it will be replaced with the
900 name of the novaboot script (see also B<--name>).
902 =item --grub2[=I<filename>]
904 Generate GRUB2 menuentry in I<filename>. If I<filename> is not
905 specified F<grub.cfg> is used. The content of the menuentry can be
906 customized with B<--grub-preable>, B<--grub2-prolog> or
907 B<--grub_prefix> options.
909 In order to use the the generated menuentry on your development
910 machine that uses GRUB2, append the following snippet to
911 F</etc/grub.d/40_custom> file and regenerate your grub configuration,
912 i.e. run update-grub on Debian/Ubuntu.
914 if [ -f /path/to/nul/build/grub.cfg ]; then
915 source /path/to/nul/build/grub.cfg
918 =item --grub2-prolog=I<prolog>
920 Specifies text I<preable> that is put at the begiging of the entry
923 =item --name=I<string>
925 Use the name I<string> instead of the name of the novaboot script.
926 This name is used for things like a title of grub menu or for the
927 server directory where the boot files are copied to.
931 Do not generate files on the fly (i.e. "<" syntax) except for the
932 files generated via "<<WORD" syntax.
934 =item -p, --pulsar[=mac]
936 Generates pulsar bootloader configuration file whose name is based on
937 the MAC address specified either on the command line or taken from
938 I<.novaboot> configuration file.
942 =head2 File deployment phase
944 In some setups, it is necessary to copy the files needed for booting
945 to a particular location, e.g. to a TFTP boot server or to the
950 =item -d, --dhcp-tftp
952 Turns your workstation into a DHCP and TFTP server so that NOVA
953 can be booted via PXE BIOS on a test machine directly connected by
954 a plain Ethernet cable to your workstation.
956 The DHCP and TFTP servers require root privileges and C<novaboot>
957 uses C<sudo> command to obtain those. You can put the following to
958 I</etc/sudoers> to allow running the necessary commands without
961 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
962 your_login ALL=NOPASSWD: NOVABOOT
964 =item -i, --iso[=filename]
966 Generates the ISO image that boots NOVA system via GRUB. If no filename
967 is given, the image is stored under I<NAME>.iso, where I<NAME> is the name
968 of the novaboot script (see also B<--name>).
970 =item --server[=[[user@]server:]path]
972 Copy all files needed for booting to another location (implies B<-g>
973 unless B<--grub2> is given). The files will be copied (by B<rsync>
974 tool) to the directory I<path>. If the I<path> contains string $NAME,
975 it will be replaced with the name of the novaboot script (see also
980 If B<--server> is used and its value ends with $NAME, then after
981 copying the files, a new bootloader configuration file (e.g. menu.lst)
982 is created at I<path-wo-name>, i.e. the path specified by B<--server>
983 with $NAME part removed. The content of the file is created by
984 concatenating all files of the same name from all subdirectories of
985 I<path-wo-name> found on the "server".
987 =item --rsync-flags=I<flags>
989 Specifies which I<flags> are appended to F<rsync> command line when
990 copying files as a result of I<--server> option.
992 =item --scons[=scons command]
994 Runs I<scons> to build files that are not generated by novaboot
999 =head2 Target power-on and reset phase
1003 =item --iprelay=I<addr[:port]>
1005 Use IP relay to reset the machine and to get the serial output. The IP
1006 address of the relay is given by I<addr> parameter.
1008 Note: This option is expected to work with HWG-ER02a IP relays.
1012 Switch on/off the target machine. Currently works only with
1015 =item -Q, --qemu=I<qemu-binary>
1017 The name of qemu binary to use. The default is 'qemu'.
1019 =item --qemu-append=I<flags>
1021 Append I<flags> to the default qemu flags (QEMU_FLAGS variable or
1022 C<-cpu coreduo -smp 2>).
1024 =item -q, --qemu-flags=I<flags>
1026 Replace the default qemu flags (QEMU_FLAGS variable or C<-cpu coreduo
1027 -smp 2>) with I<flags> specified here.
1031 =head2 Interaction with the bootloader on the target
1033 See B<--serial>. There will be new options soon.
1035 =head2 Target's output reception phase
1039 =item -s, --serial[=device]
1041 Use serial line to control GRUB bootloader and to see the output
1042 serial output of the machine. The default value is F</dev/ttyUSB0>.
1046 See also B<--iprelay>.
1048 =head2 Termination phase
1050 Daemons that were spwned (F<dhcpd> and F<tftpd>) are killed here.
1052 =head1 NOVABOOT SCRIPT SYNTAX
1054 The syntax tries to mimic POSIX shell syntax. The syntax is defined with the following rules.
1056 Lines starting with "#" are ignored.
1058 Lines that end with "\" are concatenated with the following line after
1059 removal of the final "\" and leading whitespace of the following line.
1061 Lines in the form I<VARIABLE=...> (i.e. matching '^[A-Z_]+=' regular
1062 expression) assign values to internal variables. See VARIABLES
1065 Otherwise, the first word on the line represents the filename
1066 (relative to the build directory (see B<--build-dir>) of the module to
1067 load and the remaining words are passed as the command line
1070 When the line ends with "<<WORD" then the subsequent lines until the
1071 line containing only WORD are copied literally to the file named on
1074 When the line ends with "< CMD" the command CMD is executed with
1075 C</bin/sh> and its standard output is stored in the file named on that
1076 line. The SRCDIR variable in CMD's environment is set to the absolute
1077 path of the directory containing the interpreted novaboot script.
1080 #!/usr/bin/env novaboot
1081 WVDESC=Example program
1082 bin/apps/sigma0.nul S0_DEFAULT script_start:1,1 \
1083 verbose hostkeyb:0,0x60,1,12,2
1085 hello.nulconfig <<EOF
1086 sigma0::mem:16 name::/s0/log name::/s0/timer name::/s0/fs/rom ||
1087 rom://bin/apps/hello.nul
1090 This example will load three modules: sigma0.nul, hello.nul and
1091 hello.nulconfig. sigma0 gets some command line parameters and
1092 hello.nulconfig file is generated on the fly from the lines between
1097 The following variables are interpreted in the novaboot script:
1103 Description of the wvtest-compliant program.
1105 =item WVTEST_TIMEOUT
1107 The timeout in seconds for WvTest harness. If no complete line appears
1108 in the test output within the time specified here, the test fails. It
1109 is necessary to specify this for long running tests that produce no
1110 intermediate output.
1114 Use a specific qemu binary (can be overriden with B<-Q>) and flags
1115 when booting this script under qemu. If QEMU_FLAGS variable is also
1116 specified flags specified in QEMU variable are replaced by those in
1121 Use specific qemu flags (can be overriden with B<-q>).
1123 =item HYPERVISOR_PARAMS
1125 Parameters passed to hypervisor. The default value is "serial", unless
1126 overriden in configuration file.
1130 The kernel to use instead of NOVA hypervisor specified in the
1131 configuration file. The value should contain the name of the kernel
1132 image as well as its command line parameters. If this variable is
1133 defined and non-empty, the variable HYPERVISOR_PARAMS is not used.
1137 =head1 CONFIGURATION FILE
1139 Novaboot can read its configuration from a file. Configuration file
1140 was necessary in early days of novaboot. Nowadays, an attempt is made
1141 to not use the configuration file because it makes certain novaboot
1142 scripts unusable on systems without (or with different) configuration
1143 file. The only recommended use of the configuration file is to specify
1144 custom_options (see bellow).
1146 If you decide to use the configuration file, it is looked up, by
1147 default, in files named F<.novaboot> as described in L</Configuration
1148 reading phase>. Alternatively, its location can be specified with the
1149 B<-c> switch or with the NOVABOOT_CONFIG environment variable. The
1150 configuration file has perl syntax and should set values of certain
1151 Perl variables. The current configuration can be dumped with the
1152 B<--dump-config> switch. Some configuration variables can be overriden
1153 by environment variables (see below) or by command line switches.
1155 Documentation of some configuration variables follows:
1161 Custom chainloaders to load before hypervisor and files specified in
1162 novaboot script. E.g. ('bin/boot/bender promisc', 'bin/boot/zapp').
1166 Hash of shortcuts to be used with the B<--target> option. If the hash
1167 contains, for instance, the following pair of values
1169 'mybox' => '--server=boot:/tftproot --serial=/dev/ttyUSB0 --grub',
1171 then the following two commands are equivalent:
1173 ./script --server=boot:/tftproot --serial=/dev/ttyUSB0 --grub
1178 =head1 ENVIRONMENT VARIABLES
1180 Some options can be specified not only via config file or command line
1181 but also through environment variables. Environment variables override
1182 the values from configuration file and command line parameters
1183 override the environment variables.
1187 =item NOVABOOT_CONFIG
1189 Name of the novaboot configuration file to use instead of the default
1192 =item NOVABOOT_BENDER
1194 Defining this variable has the same meaning as B<--bender> option.
1200 Michal Sojka <sojka@os.inf.tu-dresden.de>