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 warnings (exists $ENV{NOVABOOT_TEST} ? (FATAL => 'all') : ());
19 use Getopt::Long qw(GetOptionsFromString);
24 use Time::HiRes("usleep");
28 use POSIX qw(:errno_h);
29 use Cwd qw(getcwd abs_path);
34 my $invocation_dir = getcwd();
36 ## Configuration file handling
38 # Default configuration
39 $CFG::hypervisor = "";
40 $CFG::hypervisor_params = "serial";
41 $CFG::genisoimage = "genisoimage";
42 $CFG::qemu = 'qemu -cpu coreduo -smp 2';
44 "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',
45 "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 --iprelay=147.32.86.92:2324',
46 "localhost" => '--scriptmod=s/console=tty[A-Z0-9,]+// --server=/boot/novaboot/$NAME --grub2 --grub-prefix=/boot/novaboot/$NAME --grub2-prolog=" set root=\'(hd0,msdos1)\'"',
48 $CFG::scons = "scons -j2";
55 package CFG; # Put config data into a separate namespace
60 die("ERROR: Failure compiling '$cfg' - $@");
61 } elsif (! defined($rc)) {
62 die("ERROR: Failure reading '$cfg' - $!");
64 die("ERROR: Failure processing '$cfg'");
67 $builddir = File::Spec->rel2abs($CFG::builddir, dirname($cfg)) if defined $CFG::builddir;
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 = File::Spec->rel2abs($ARGV[0] && -f $ARGV[0] ? dirname($ARGV[0]) : '', $invocation_dir);
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, @chainloaders, $concat, $config_name_opt, $dhcp_tftp, $dump_opt, $dump_config, $gen_only, $grub_config, $grub_prefix, $grub_preamble, $grub2_prolog, $grub2_config, $help, $iprelay, $iso_image, $man, $no_file_gen, $off_opt, $on_opt, $pulsar, $pulsar_root, $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" => sub { my ($n, $v) = @_; $builddir = File::Spec->rel2abs($v); },
101 "concat" => \$concat,
102 "chainloader=s" => \@chainloaders,
103 "dhcp-tftp|d" => \$dhcp_tftp,
104 "dump" => \$dump_opt,
105 "dump-config" => \$dump_config,
106 "gen-only" => \$gen_only,
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 "pulsar-root=s" => \$pulsar_root,
120 "qemu|Q=s" => \$qemu,
121 "qemu-append=s" => \$qemu_append,
122 "qemu-flags|q=s" => \$qemu_flags_cmd,
123 "rsync-flags=s" => \$rsync_flags,
124 "scons:s" => \$scons,
125 "scriptmod=s" => \@scriptmod,
126 "serial|s:s" => \$serial,
127 "server:s" => \$server,
128 "strip-rom" => sub { $rom_prefix = ''; },
129 "target|t=s" => sub { my ($opt_name, $opt_value) = @_;
130 exists $CFG::targets{$opt_value} or die("Unknown target '$opt_value' (valid targets are: ".join(", ", sort keys(%CFG::targets)).")");
131 GetOptionsFromString($CFG::targets{$opt_value}, %opt_spec); },
135 GetOptions %opt_spec or pod2usage(2);
136 pod2usage(1) if $help;
137 pod2usage(-exitstatus => 0, -verbose => 2) if $man;
139 ### Dump sanitized configuration (if requested)
143 $Data::Dumper::Indent=1;
144 print "# This file is in perl syntax.\n";
145 foreach my $key(sort(keys(%CFG::))) { # See "Symbol Tables" in perlmod(1)
146 if (defined ${$CFG::{$key}}) { print Data::Dumper->Dump([${$CFG::{$key}}], ["*$key"]); }
147 if ( @{$CFG::{$key}}) { print Data::Dumper->Dump([\@{$CFG::{$key}}], ["*$key"]); }
148 if ( %{$CFG::{$key}}) { print Data::Dumper->Dump([\%{$CFG::{$key}}], ["*$key"]); }
154 ### Sanitize configuration
156 if (defined $config_name_opt && scalar(@ARGV) > 1) { die "You cannot use --name with multiple scripts"; }
159 if (defined $serial) { $serial ||= "/dev/ttyUSB0"; }
160 if (defined $grub_config) { $grub_config ||= "menu.lst"; }
161 if (defined $grub2_config) { $grub2_config ||= "grub.cfg"; }
163 ## Parse the novaboot script(s)
169 my ($modules, $variables, $generated, $continuation);
171 if ($ARGV ne $last_fn) { # New script
172 die "Missing EOF in $last_fn" if $file;
173 die "Unfinished line in $last_fn" if $line;
175 push @scripts, { 'filename' => $ARGV,
176 'modules' => $modules = [],
177 'variables' => $variables = {},
178 'generated' => $generated = []};
182 next if /^#/ || /^\s*$/; # Skip comments and empty lines
184 foreach my $mod(@scriptmod) { eval $mod; }
186 print "$_\n" if $dump_opt;
188 if (/^([A-Z_]+)=(.*)$/) { # Internal variable
189 $$variables{$1} = $2;
192 if (/^([^ ]*)(.*?)[[:space:]]*<<([^ ]*)$/) { # Heredoc start
193 push @$modules, "$1$2";
195 push @$generated, {filename => $1, content => $file};
199 if ($file && $_ eq $EOF) { # Heredoc end
203 if ($file) { # Heredoc content
204 push @{$file}, "$_\n";
207 $_ =~ s/^[[:space:]]*// if ($continuation);
208 if (/\\$/) { # Line continuation
209 $line .= substr($_, 0, length($_)-1);
215 $line .= " $append" if ($append && scalar(@$modules) == 0);
217 if ($line =~ /^([^ ]*)(.*?)[[:space:]]*< ?(.*)$/) { # Command substitution
218 push @$modules, "$1$2";
219 push @$generated, {filename => $1, command => $3};
223 push @$modules, $line;
227 #print Dumper(\@scripts);
233 sub generate_configs($$$) {
234 my ($base, $generated, $filename) = @_;
235 if ($base) { $base = "$base/"; };
236 foreach my $g(@$generated) {
237 if (exists $$g{content}) {
238 my $config = $$g{content};
239 my $fn = $$g{filename};
240 open(my $f, '>', $fn) || die("$fn: $!");
241 map { s|\brom://([^ ]*)|$rom_prefix$base$1|g; print $f "$_"; } @{$config};
243 print "novaboot: Created $fn\n";
244 } elsif (exists $$g{command} && ! $no_file_gen) {
245 $ENV{SRCDIR} = dirname(File::Spec->rel2abs( $filename, $invocation_dir ));
246 system_verbose("( $$g{command} ) > $$g{filename}");
251 sub generate_grub_config($$$$;$)
253 my ($filename, $title, $base, $modules_ref, $preamble) = @_;
254 if ($base) { $base = "$base/"; };
255 open(my $fg, '>', $filename) or die "$filename: $!";
256 print $fg "$preamble\n" if $preamble;
257 print $fg "title $title\n" if $title;
258 #print $fg "root $base\n"; # root doesn't really work for (nd)
260 foreach (@$modules_ref) {
263 my ($kbin, $kcmd) = split(' ', $_, 2);
264 $kcmd = '' if !defined $kcmd;
265 print $fg "kernel ${base}$kbin $kcmd\n";
267 s|\brom://([^ ]*)|$rom_prefix$base$1|g; # Translate rom:// files - needed for vdisk parameter of sigma0
268 print $fg "module $base$_\n";
272 print("novaboot: Created $builddir/$filename\n");
276 sub generate_grub2_config($$$$;$$)
278 my ($filename, $title, $base, $modules_ref, $preamble, $prolog) = @_;
279 if ($base && substr($base,-1,1) ne '/') { $base = "$base/"; };
280 open(my $fg, '>', $filename) or die "$filename: $!";
281 print $fg "$preamble\n" if $preamble;
282 $title ||= 'novaboot';
283 print $fg "menuentry $title {\n";
284 print $fg "$prolog\n" if $prolog;
286 foreach (@$modules_ref) {
289 my ($kbin, $kcmd) = split(' ', $_, 2);
290 $kcmd = '' if !defined $kcmd;
291 print $fg " multiboot ${base}$kbin $kcmd\n";
294 # GRUB2 doesn't pass filename in multiboot info so we have to duplicate it here
295 $_ = join(' ', ($args[0], @args));
296 s|\brom://|$rom_prefix|g; # We do not need to translate path for GRUB2
297 print $fg " module $base$_\n";
302 print("novaboot: Created $builddir/$filename\n");
306 sub generate_pulsar_config($$)
308 my ($filename, $modules_ref) = @_;
309 open(my $fg, '>', $filename) or die "$filename: $!";
310 print $fg "root $pulsar_root\n" if defined $pulsar_root;
313 foreach (@$modules_ref) {
316 ($kbin, $kcmd) = split(' ', $_, 2);
317 $kcmd = '' if !defined $kcmd;
320 s|\brom://|$rom_prefix|g;
321 print $fg "load $_\n";
324 # Put kernel as last - this is needed for booting Linux and has no influence on non-Linux OSes
325 print $fg "exec $kbin $kcmd\n";
327 print("novaboot: Created $builddir/$filename\n");
333 print "novaboot: Running: ".join(' ', map("'$_'", @_))."\n";
337 sub system_verbose($)
340 print "novaboot: Running: $cmd\n";
341 my $ret = system($cmd);
342 if ($ret & 0x007f) { die("Command terminated by a signal"); }
343 if ($ret & 0xff00) {die("Command exit with non-zero exit code"); }
344 if ($ret) { die("Command failure $ret"); }
349 if (exists $variables->{WVDESC}) {
350 print "Testing \"$variables->{WVDESC}\" in $last_fn:\n";
351 } elsif ($last_fn =~ /\.wv$/) {
352 print "Testing \"all\" in $last_fn:\n";
355 ## Handle reset and power on/off - TODO: move after the file generation phase
358 if (defined $iprelay) {
359 $iprelay =~ /([.0-9]+)(:([0-9]+))?/;
362 my $paddr = sockaddr_in($port, inet_aton($addr));
363 my $proto = getprotobyname('tcp');
364 socket($IPRELAY, PF_INET, SOCK_STREAM, $proto) || die "socket: $!";
365 print "novaboot: Connecting to IP relay... ";
366 connect($IPRELAY, $paddr) || die "connect: $!";
368 $IPRELAY->autoflush(1);
371 print $IPRELAY "\xFF\xF6";
373 local $SIG{ALRM} = sub { die "Relay AYT timeout"; };
374 my $ayt_reponse = "";
375 my $read = sysread($IPRELAY, $ayt_reponse, 100);
379 print "$ayt_reponse\n";
380 if ($ayt_reponse =~ /<iprelayd: not connected/) {
388 my ($relay, $onoff) = @_;
389 die unless ($relay == 1 || $relay == 2);
391 my $cmd = ($relay == 1 ? 0x5 : 0x6) | ($onoff ? 0x20 : 0x10);
392 return "\xFF\xFA\x2C\x32".chr($cmd)."\xFF\xF0";
396 my ($relay, $onoff) = @_;
397 die unless ($relay == 1 || $relay == 2);
398 my $cmd = ($relay == 1 ? 0xdf : 0xbf) | ($onoff ? 0x00 : 0xff);
399 return "\xFF\xFA\x2C\x97".chr($cmd)."\xFF\xF0";
403 my ($relay, $onoff, $can_giveup) = @_;
404 my $confirmation = '';
405 print $IPRELAY relaycmd($relay, $onoff);
407 # We use non-blocking I/O and polling here because for some
408 # reason read() on blocking FD returns only after all
409 # requested data is available. If we get during the first
410 # read() only a part of confirmation, we may get the rest
411 # after the system boots and print someting, which may be too
413 $IPRELAY->blocking(0);
415 alarm(20); # Timeout in seconds
417 local $SIG{ALRM} = sub {
418 if ($can_giveup) { print("Relay confirmation timeout - ignoring\n"); $giveup = 1;}
419 else {die "Relay confirmation timeout";}
422 while (($index=index($confirmation, relayconf($relay, $onoff))) < 0 && !$giveup) {
423 my $read = read($IPRELAY, $confirmation, 70, length($confirmation));
424 if (!defined($read)) {
425 die("IP relay: $!") unless $! == EAGAIN;
429 #use MIME::QuotedPrint;
430 #print "confirmation = ".encode_qp($confirmation)."\n";
433 $IPRELAY->blocking(1);
437 if ($iprelay && (defined $on_opt || defined $off_opt)) {
438 relay(1, 1); # Press power button
439 if (defined $on_opt) {
440 usleep(100000); # Short press
442 print "novaboot: Switching the target off...\n";
443 usleep(6000000); # Long press to switch off
445 print $IPRELAY relay(1, 0);
449 $builddir ||= dirname(File::Spec->rel2abs( ${$scripts[0]}{filename}));
450 chdir($builddir) or die "Can't change directory to $builddir: $!";
451 print "novaboot: Entering directory `$builddir'\n";
453 ## File generation phase
454 my (%files_iso, $menu_iso, $config_name, $filename);
456 foreach my $script (@scripts) {
457 $filename = $$script{filename};
458 $modules = $$script{modules};
459 $generated = $$script{generated};
460 $variables = $$script{variables};
462 ($config_name = $filename) =~ s#.*/##;
463 $config_name = $config_name_opt if (defined $config_name_opt);
465 if (exists $variables->{BUILDDIR}) {
466 $builddir = File::Spec->rel2abs($variables->{BUILDDIR});
467 chdir($builddir) or die "Can't change directory to $builddir: $!";
468 print "novaboot: Entering directory `$builddir'\n";
472 if (exists $variables->{KERNEL}) {
473 $kernel = $variables->{KERNEL};
475 if ($CFG::hypervisor) {
476 $kernel = $CFG::hypervisor . " ";
477 if (exists $variables->{HYPERVISOR_PARAMS}) {
478 $kernel .= $variables->{HYPERVISOR_PARAMS};
480 $kernel .= $CFG::hypervisor_params;
484 @$modules = ($kernel, @$modules) if $kernel;
485 @$modules = (@chainloaders, @$modules);
486 @$modules = ("bin/boot/bender", @$modules) if ($bender || defined $ENV{'NOVABOOT_BENDER'});
489 ($prefix = $grub_prefix) =~ s/\$NAME/$config_name/ if defined $grub_prefix;
490 $prefix ||= $builddir;
491 # TODO: use $grub_prefix as first parameter if some switch is given
492 generate_configs('', $generated, $filename);
494 ### Generate bootloader configuration files
495 my @bootloader_configs;
496 push @bootloader_configs, generate_grub_config($grub_config, $config_name, $prefix, $modules, $grub_preamble) if (defined $grub_config);
497 push @bootloader_configs, generate_grub2_config($grub2_config, $config_name, $prefix, $modules, $grub_preamble, $grub2_prolog) if (defined $grub2_config);
498 push @bootloader_configs, generate_pulsar_config('config-'.($pulsar||'novaboot'), $modules) if (defined $pulsar);
501 if (defined $scons) {
502 my @files = map({ ($file) = m/([^ ]*)/; $file; } @$modules);
503 # Filter-out generated files
504 my @to_build = grep({ my $file = $_; !scalar(grep($file eq $$_{filename}, @$generated)) } @files);
505 system_verbose($scons || $CFG::scons." ".join(" ", @to_build));
508 ### Copy files (using rsync)
509 if (defined $server && !defined($gen_only)) {
510 (my $real_server = $server) =~ s/\$NAME/$config_name/;
512 my ($hostname, $path) = split(":", $real_server, 2);
513 if (! defined $path) {
517 my $files = join(" ", map({ ($file) = m/([^ ]*)/; $file; } ( @$modules, @bootloader_configs)));
518 map({ my $file = (split)[0]; die "$file: $!" if ! -f $file; } @$modules);
519 my $istty = -t STDOUT && ($ENV{'TERM'} || 'dumb') ne 'dumb';
520 my $progress = $istty ? "--progress" : "";
521 system_verbose("rsync $progress -RLp $rsync_flags $files $real_server");
522 if ($server =~ m|/\$NAME$| && $concat) {
523 my $cmd = join("; ", map { "( cd $path/.. && cat */$_ > $_ )" } @bootloader_configs);
524 system_verbose($hostname ? "ssh $hostname '$cmd'" : $cmd);
528 ### Prepare ISO image generation
529 if (defined $iso_image) {
530 generate_configs("(cd)", $generated, $filename);
532 generate_grub_config(\$menu, $config_name, "(cd)", $modules);
533 $menu_iso .= "$menu\n";
534 map { ($file,undef) = split; $files_iso{$file} = 1; } @$modules;
538 ## Generate ISO image
539 if (defined $iso_image) {
540 open(my $fh, ">menu-iso.lst");
541 print $fh "timeout 5\n\n$menu_iso";
543 my $files = "boot/grub/menu.lst=menu-iso.lst " . join(" ", map("$_=$_", keys(%files_iso)));
544 $iso_image ||= "$config_name.iso";
545 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");
546 print("ISO image created: $builddir/$iso_image\n");
549 exit(0) if defined $gen_only;
551 ## Boot the system using various methods and send serial output to stdout
553 if (scalar(@scripts) > 1 && ( defined $dhcp_tftp || defined $serial || defined $iprelay)) {
554 die "You cannot do this with multiple scripts simultaneously";
557 if ($variables->{WVTEST_TIMEOUT}) {
558 print "wvtest: timeout ", $variables->{WVTEST_TIMEOUT}, "\n";
563 $str =~ s/^\s+|\s+$//g;
569 if (!(defined $dhcp_tftp || defined $serial || defined $iprelay || defined $server || defined $iso_image)) {
571 $qemu ||= $variables->{QEMU} || $CFG::qemu;
572 my @qemu_flags = split(" ", $qemu);
573 $qemu = shift(@qemu_flags);
575 @qemu_flags = split(/ +/, trim($variables->{QEMU_FLAGS})) if exists $variables->{QEMU_FLAGS};
576 @qemu_flags = split(/ +/, trim($qemu_flags_cmd)) if $qemu_flags_cmd;
577 push(@qemu_flags, split(/ +/, trim($qemu_append || '')));
579 if (defined $iso_image) {
580 # Boot NOVA with grub (and test the iso image)
581 push(@qemu_flags, ('-cdrom', "$config_name.iso"));
583 # Boot NOVA without GRUB
585 # Non-patched qemu doesn't like commas, but NUL can live with pluses instead of commans
586 foreach (@$modules) {s/,/+/g;}
587 generate_configs("", $generated, $filename);
589 my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);
590 $kcmd = '' if !defined $kcmd;
592 @$modules = map { if (/\.dtb$/) { $dtb=$_; (); } else { $_ } } @$modules;
593 my $initrd = join ",", @$modules;
595 push(@qemu_flags, ('-kernel', $kbin, '-append', $kcmd));
596 push(@qemu_flags, ('-initrd', $initrd)) if $initrd;
597 push(@qemu_flags, ('-dtb', $dtb)) if $dtb;
599 push(@qemu_flags, qw(-serial stdio)); # Redirect serial output (for collecting test restuls)
600 exec_verbose(($qemu, '-name', $config_name, @qemu_flags));
603 ### Local DHCPD and TFTPD
605 my ($dhcpd_pid, $tftpd_pid);
607 if (defined $dhcp_tftp)
609 generate_configs("(nd)", $generated, $filename);
610 system_verbose('mkdir -p tftpboot');
611 generate_grub_config("tftpboot/os-menu.lst", $config_name, "(nd)", \@$modules, "timeout 0");
612 open(my $fh, '>', 'dhcpd.conf');
613 my $mac = `cat /sys/class/net/eth0/address`;
615 print $fh "subnet 10.23.23.0 netmask 255.255.255.0 {
616 range 10.23.23.10 10.23.23.100;
617 filename \"bin/boot/grub/pxegrub.pxe\";
618 next-server 10.23.23.1;
621 hardware ethernet $mac;
622 fixed-address 10.23.23.1;
625 system_verbose("sudo ip a add 10.23.23.1/24 dev eth0;
626 sudo ip l set dev eth0 up;
627 sudo touch dhcpd.leases");
630 if ($dhcpd_pid == 0) {
631 # This way, the spawned server are killed when this script is killed.
632 exec_verbose("sudo dhcpd -d -cf dhcpd.conf -lf dhcpd.leases -pf dhcpd.pid");
635 if ($tftpd_pid == 0) {
636 exec_verbose("sudo in.tftpd --foreground --secure -v -v -v $builddir");
638 $SIG{TERM} = sub { print "CHILDS KILLED\n"; kill 15, $dhcpd_pid, $tftpd_pid; };
641 ### Serial line or IP relay
643 if ($serial || defined $iprelay) {
645 if (defined $iprelay) {
646 print "novaboot: Reseting the test box... ";
647 relay(2, 1, 1); # Reset the machine
654 system("stty -F $serial raw -crtscts -onlcr 115200");
655 open($CONN, "+<", $serial) || die "open $serial: $!";
659 # Pass the NOVA output to stdout.
663 kill 15, $dhcpd_pid, $tftpd_pid if ($dhcp_tftp);
667 ### Wait for dhcpc or tftpd
668 if (defined $dhcp_tftp) {
670 if ($pid == $dhcpd_pid) { print "dhcpd exited!\n"; }
671 elsif ($pid == $tftpd_pid) { print "tftpd exited!\n"; }
672 else { print "wait returned: $pid\n"; }
673 kill(15, 0); # Kill current process group i.e. all remaining children
680 novaboot - A tool for booting various operating systems on various hardware or in qemu
684 B<novaboot> [ options ] [--] script...
686 B<./script> [ options ]
692 This program makes it easier to boot NOVA or other operating system
693 (OS) in different environments. It reads a so called novaboot script
694 and uses it either to boot the OS in an emulator (e.g. in qemu) or to
695 generate the configuration for a specific bootloader and optionally to
696 copy the necessary binaries and other needed files to proper
697 locations, perhaps on a remote server. In case the system is actually
698 booted, its serial output is redirected to standard output if that is
701 A typical way of using novaboot is to make the novaboot script
702 executable and set its first line to I<#!/usr/bin/env novaboot>. Then,
703 booting a particular OS configuration becomes the same as executing a
704 local program - the novaboot script.
706 With C<novaboot> you can:
712 Run an OS in Qemu. This is the default action when no other action is
713 specified by command line switches. Thus running C<novaboot ./script>
714 (or C<./script> as described above) will run Qemu and make it boot the
715 configuration specified in the I<script>.
719 Create a bootloader configuration file (currently supported
720 bootloaders are GRUB, GRUB2 and Pulsar) and copy it with all other
721 files needed for booting to another, perhaps remote, location.
723 ./script --server --iprelay=192.168.1.2
725 This command copies files to a TFTP server specified in the
726 configuration file and uses TCP/IP-controlled relay to reset the test
727 box and receive its serial output.
731 Run DHCP and TFTP server on developer's machine to PXE-boot NOVA from
736 When a PXE-bootable machine is connected via Ethernet to developer's
737 machine, it will boot the configuration described in I<script>.
741 Create bootable ISO images. E.g.
743 novaboot --iso -- script1 script2
745 The created ISO image will have GRUB bootloader installed on it and
746 the boot menu will allow selecting between I<script1> and I<script2>
751 =head1 PHASES AND OPTIONS
753 Novaboot performs its work in several phases. Each phase can be
754 influenced by several options, certain phases can be skipped. The list
755 of phases (in the execution order) and the corresponding options
758 =head2 Configuration reading phase
760 After starting, novaboot reads configuration files. By default, it
761 searches for files named F<.novaboot> starting from the directory of
762 the novaboot script (or working directory, see bellow) and continuing
763 upwards up to the root directory. The configuration files are read in
764 order from the root directory downwards with latter files overriding
765 settings from the former ones.
767 In certain cases, the location of the novaboot script cannot be
768 determined in this early phase. This happens either when the script is
769 read from the standard input or when novaboot is invoked explicitly
770 and options precede the script name, as in the example L</"4."> above.
771 In this case the current working directory is used as a starting point
772 for configuration file search.
776 =item -c, --config=<filename>
778 Use the specified configuration file instead of the default one(s).
782 =head2 Command line processing phase
788 Dump the current configuration to stdout end exits. Useful as an
789 initial template for a configuration file.
793 Print short (B<-h>) or long (B<--help>) help.
795 =item -t, --target=<target>
797 This option serves as a user configurable shortcut for other novaboot
798 options. The effect of this option is the same as the options stored
799 in the C<%targets> configuration variable under key I<target>. See
800 also L</"CONFIGURATION FILE">.
804 =head2 Script preprocessing phase
806 This phases allows to modify the parsed novaboot script before it is
807 used in the later phases.
811 =item -a, --append=<parameters>
813 Appends a string to the first "filename" line in the novaboot script.
814 This can be used to append parameters to the kernel's or root task's
819 Use F<bender> chainloader. Bender scans the PCI bus for PCI serial
820 ports and stores the information about them in the BIOS data area for
823 =item --chainloader=<chainloader>
825 Chainloader that is loaded before the kernel and other files specified
826 in the novaboot script. E.g. 'bin/boot/bender promisc'.
830 Prints the content of the novaboot script after removing comments and
831 evaluating all I<--scriptmod> expressions. Exit after reading (and
834 =item --scriptmod=I<perl expression>
836 When novaboot script is read, I<perl expression> is executed for every
837 line (in $_ variable). For example, C<novaboot
838 --scriptmod=s/sigma0/omega6/g> replaces every occurrence of I<sigma0>
839 in the script with I<omega6>.
841 When this option is present, it overrides I<$script_modifier> variable
842 from the configuration file, which has the same effect. If this option
843 is given multiple times all expressions are evaluated in the command
848 Strip I<rom://> prefix from command lines and generated config files.
849 The I<rom://> prefix is used by NUL. For NRE, it has to be stripped.
853 =head2 File generation phase
855 In this phase, files needed for booting are generated in a so called
856 I<build directory> (see TODO). In most cases configuration for a
857 bootloader is generated automatically by novaboot. It is also possible
858 to generate other files using I<heredoc> or I<"<"> syntax in novaboot
859 scripts. Finally, binaries can be generated in this phases by running
864 =item --build-dir=<directory>
866 Overrides the default build directory location.
868 The default build directory location is determined as follows: If the
869 configuration file defines the C<$builddir> variable, its value is
870 used. Otherwise, it is the directory that contains the first processed
873 =item -g, --grub[=I<filename>]
875 Generates grub bootloader menu file. If the I<filename> is not
876 specified, F<menu.lst> is used. The I<filename> is relative to the
877 build directory (see B<--build-dir>).
879 =item --grub-preamble=I<prefix>
881 Specifies the I<preable> that is at the beginning of the generated
882 GRUB or GRUB2 config files. This is useful for specifying GRUB's
885 =item --grub-prefix=I<prefix>
887 Specifies I<prefix> that is put in front of every file name in GRUB's
888 F<menu.lst>. The default value is the absolute path to the build directory.
890 If the I<prefix> contains string $NAME, it will be replaced with the
891 name of the novaboot script (see also B<--name>).
893 =item --grub2[=I<filename>]
895 Generate GRUB2 menuentry in I<filename>. If I<filename> is not
896 specified F<grub.cfg> is used. The content of the menuentry can be
897 customized with B<--grub-preable>, B<--grub2-prolog> or
898 B<--grub_prefix> options.
900 In order to use the the generated menuentry on your development
901 machine that uses GRUB2, append the following snippet to
902 F</etc/grub.d/40_custom> file and regenerate your grub configuration,
903 i.e. run update-grub on Debian/Ubuntu.
905 if [ -f /path/to/nul/build/grub.cfg ]; then
906 source /path/to/nul/build/grub.cfg
909 =item --grub2-prolog=I<prolog>
911 Specifies text I<preable> that is put at the begiging of the entry
914 =item --name=I<string>
916 Use the name I<string> instead of the name of the novaboot script.
917 This name is used for things like a title of grub menu or for the
918 server directory where the boot files are copied to.
922 Do not generate files on the fly (i.e. "<" syntax) except for the
923 files generated via "<<WORD" syntax.
925 =item -p, --pulsar[=mac]
927 Generates pulsar bootloader configuration file named F<config-I<mac>>
928 The I<mac> string is typically a MAC address and defaults to
931 =item --scons[=scons command]
933 Runs I<scons> to build files that are not generated by novaboot
938 Exit novaboot after file generation phase.
942 =head2 Target connection check
944 If supported by the target, the connection to it is made and it is
945 checked whether the target is not occupied by another novaboot
948 =head2 File deployment phase
950 In some setups, it is necessary to copy the files needed for booting
951 to a particular location, e.g. to a TFTP boot server or to the
956 =item -d, --dhcp-tftp
958 Turns your workstation into a DHCP and TFTP server so that NOVA
959 can be booted via PXE BIOS on a test machine directly connected by
960 a plain Ethernet cable to your workstation.
962 The DHCP and TFTP servers require root privileges and C<novaboot>
963 uses C<sudo> command to obtain those. You can put the following to
964 I</etc/sudoers> to allow running the necessary commands without
967 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
968 your_login ALL=NOPASSWD: NOVABOOT
970 =item -i, --iso[=filename]
972 Generates the ISO image that boots NOVA system via GRUB. If no filename
973 is given, the image is stored under I<NAME>.iso, where I<NAME> is the name
974 of the novaboot script (see also B<--name>).
976 =item --server[=[[user@]server:]path]
978 Copy all files needed for booting to another location (implies B<-g>
979 unless B<--grub2> is given). The files will be copied (by B<rsync>
980 tool) to the directory I<path>. If the I<path> contains string $NAME,
981 it will be replaced with the name of the novaboot script (see also
986 If B<--server> is used and its value ends with $NAME, then after
987 copying the files, a new bootloader configuration file (e.g. menu.lst)
988 is created at I<path-wo-name>, i.e. the path specified by B<--server>
989 with $NAME part removed. The content of the file is created by
990 concatenating all files of the same name from all subdirectories of
991 I<path-wo-name> found on the "server".
993 =item --rsync-flags=I<flags>
995 Specifies which I<flags> are appended to F<rsync> command line when
996 copying files as a result of I<--server> option.
1000 =head2 Target power-on and reset phase
1004 =item --iprelay=I<addr[:port]>
1006 Use IP relay to reset the machine and to get the serial output. The IP
1007 address of the relay is given by I<addr> parameter.
1009 Note: This option is expected to work with HWG-ER02a IP relays.
1013 Switch on/off the target machine. Currently works only with
1016 =item -Q, --qemu=I<qemu-binary>
1018 The name of qemu binary to use. The default is 'qemu'.
1020 =item --qemu-append=I<flags>
1022 Append I<flags> to the default qemu flags (QEMU_FLAGS variable or
1023 C<-cpu coreduo -smp 2>).
1025 =item -q, --qemu-flags=I<flags>
1027 Replace the default qemu flags (QEMU_FLAGS variable or C<-cpu coreduo
1028 -smp 2>) with I<flags> specified here.
1032 =head2 Interaction with the bootloader on the target
1034 See B<--serial>. There will be new options soon.
1036 =head2 Target's output reception phase
1040 =item -s, --serial[=device]
1042 Use serial line to control GRUB bootloader and to see the output
1043 serial output of the machine. The default value is F</dev/ttyUSB0>.
1047 See also B<--iprelay>.
1049 =head2 Termination phase
1051 Daemons that were spwned (F<dhcpd> and F<tftpd>) are killed here.
1053 =head1 NOVABOOT SCRIPT SYNTAX
1055 The syntax tries to mimic POSIX shell syntax. The syntax is defined with the following rules.
1057 Lines starting with "#" are ignored.
1059 Lines that end with "\" are concatenated with the following line after
1060 removal of the final "\" and leading whitespace of the following line.
1062 Lines in the form I<VARIABLE=...> (i.e. matching '^[A-Z_]+=' regular
1063 expression) assign values to internal variables. See VARIABLES
1066 Otherwise, the first word on the line represents the filename
1067 (relative to the build directory (see B<--build-dir>) of the module to
1068 load and the remaining words are passed as the command line
1071 When the line ends with "<<WORD" then the subsequent lines until the
1072 line containing only WORD are copied literally to the file named on
1075 When the line ends with "< CMD" the command CMD is executed with
1076 C</bin/sh> and its standard output is stored in the file named on that
1077 line. The SRCDIR variable in CMD's environment is set to the absolute
1078 path of the directory containing the interpreted novaboot script.
1081 #!/usr/bin/env novaboot
1082 WVDESC=Example program
1083 bin/apps/sigma0.nul S0_DEFAULT script_start:1,1 \
1084 verbose hostkeyb:0,0x60,1,12,2
1086 hello.nulconfig <<EOF
1087 sigma0::mem:16 name::/s0/log name::/s0/timer name::/s0/fs/rom ||
1088 rom://bin/apps/hello.nul
1091 This example will load three modules: sigma0.nul, hello.nul and
1092 hello.nulconfig. sigma0 gets some command line parameters and
1093 hello.nulconfig file is generated on the fly from the lines between
1098 The following variables are interpreted in the novaboot script:
1104 Novaboot chdir()s to this directory before file generation phase. The
1105 directory name specified here is relative to the build directory
1106 specified by other means (see L</--build-dir>).
1110 Description of the wvtest-compliant program.
1112 =item WVTEST_TIMEOUT
1114 The timeout in seconds for WvTest harness. If no complete line appears
1115 in the test output within the time specified here, the test fails. It
1116 is necessary to specify this for long running tests that produce no
1117 intermediate output.
1121 Use a specific qemu binary (can be overriden with B<-Q>) and flags
1122 when booting this script under qemu. If QEMU_FLAGS variable is also
1123 specified flags specified in QEMU variable are replaced by those in
1128 Use specific qemu flags (can be overriden with B<-q>).
1130 =item HYPERVISOR_PARAMS
1132 Parameters passed to hypervisor. The default value is "serial", unless
1133 overriden in configuration file.
1137 The kernel to use instead of NOVA hypervisor specified in the
1138 configuration file. The value should contain the name of the kernel
1139 image as well as its command line parameters. If this variable is
1140 defined and non-empty, the variable HYPERVISOR_PARAMS is not used.
1144 =head1 CONFIGURATION FILE
1146 Novaboot can read its configuration from a file. Configuration file
1147 was necessary in early days of novaboot. Nowadays, an attempt is made
1148 to not use the configuration file because it makes certain novaboot
1149 scripts unusable on systems without (or with different) configuration
1150 file. The only recommended use of the configuration file is to specify
1151 custom_options (see bellow).
1153 If you decide to use the configuration file, it is looked up, by
1154 default, in files named F<.novaboot> as described in L</Configuration
1155 reading phase>. Alternatively, its location can be specified with the
1156 B<-c> switch or with the NOVABOOT_CONFIG environment variable. The
1157 configuration file has perl syntax and should set values of certain
1158 Perl variables. The current configuration can be dumped with the
1159 B<--dump-config> switch. Some configuration variables can be overriden
1160 by environment variables (see below) or by command line switches.
1162 Documentation of some configuration variables follows:
1168 Build directory location relative to the location of the configuration
1173 Hash of shortcuts to be used with the B<--target> option. If the hash
1174 contains, for instance, the following pair of values
1176 'mybox' => '--server=boot:/tftproot --serial=/dev/ttyUSB0 --grub',
1178 then the following two commands are equivalent:
1180 ./script --server=boot:/tftproot --serial=/dev/ttyUSB0 --grub
1185 =head1 ENVIRONMENT VARIABLES
1187 Some options can be specified not only via config file or command line
1188 but also through environment variables. Environment variables override
1189 the values from configuration file and command line parameters
1190 override the environment variables.
1194 =item NOVABOOT_CONFIG
1196 Name of the novaboot configuration file to use instead of the default
1199 =item NOVABOOT_BENDER
1201 Defining this variable has the same meaning as B<--bender> option.
1207 Michal Sojka <sojka@os.inf.tu-dresden.de>