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 ## Configuration file handling
37 # Default configuration
38 $CFG::hypervisor = "";
39 $CFG::hypervisor_params = "serial";
40 $CFG::genisoimage = "genisoimage";
41 $CFG::qemu = 'qemu -cpu coreduo -smp 2';
43 "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',
44 "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',
45 "localhost" => '--scriptmod=s/console=tty[A-Z0-9,]+// --server=/boot/novaboot/$NAME --grub2 --grub-prefix=/boot/novaboot/$NAME --grub2-prolog=" set root=\'(hd0,msdos1)\'"',
47 $CFG::scons = "scons -j2";
54 package CFG; # Put config data into a separate namespace
59 die("ERROR: Failure compiling '$cfg' - $@");
60 } elsif (! defined($rc)) {
61 die("ERROR: Failure reading '$cfg' - $!");
63 die("ERROR: Failure processing '$cfg'");
66 $builddir = File::Spec->rel2abs($CFG::builddir, dirname($cfg)) if defined $CFG::builddir;
67 print STDERR "novaboot: Read $cfg\n";
72 # We don't use $0 here, because it points to the novaboot itself and
73 # not to the novaboot script. The problem with this approach is that
74 # when a script is run as "novaboot <options> <script>" then $ARGV[0]
75 # contains the first option. Hence the -f check.
76 my $dir = abs_path($invocation_dir . (($ARGV[0] && -f $ARGV[0]) ? '/'.dirname($ARGV[0]) : ''));
77 while (-d $dir && $dir ne "/") {
78 push @cfgs, "$dir/.novaboot" if -r "$dir/.novaboot";
79 $dir = abs_path($dir."/..");
82 my $cfg = $ENV{'NOVABOOT_CONFIG'};
83 Getopt::Long::Configure(qw/no_ignore_case pass_through/);
84 GetOptions ("config|c=s" => \$cfg);
85 read_config($_) foreach $cfg or reverse @cfgs;
87 ## Command line handling
89 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);
92 $rom_prefix = 'rom://';
94 Getopt::Long::Configure(qw/no_ignore_case no_pass_through/);
97 "append|a=s" => \$append,
98 "bender|b" => \$bender,
99 "build-dir=s" => sub { my ($n, $v) = @_; $builddir = File::Spec->rel2abs($v); },
100 "concat" => \$concat,
101 "chainloader=s" => \@chainloaders,
102 "dhcp-tftp|d" => \$dhcp_tftp,
103 "dump" => \$dump_opt,
104 "dump-config" => \$dump_config,
105 "gen-only" => \$gen_only,
106 "grub|g:s" => \$grub_config,
107 "grub-preamble=s"=> \$grub_preamble,
108 "grub-prefix=s" => \$grub_prefix,
109 "grub2:s" => \$grub2_config,
110 "grub2-prolog=s" => \$grub2_prolog,
111 "iprelay=s" => \$iprelay,
112 "iso|i:s" => \$iso_image,
113 "name=s" => \$config_name_opt,
114 "no-file-gen" => \$no_file_gen,
117 "pulsar|p:s" => \$pulsar,
118 "pulsar-root=s" => \$pulsar_root,
119 "qemu|Q=s" => \$qemu,
120 "qemu-append=s" => \$qemu_append,
121 "qemu-flags|q=s" => \$qemu_flags_cmd,
122 "rsync-flags=s" => \$rsync_flags,
123 "scons:s" => \$scons,
124 "scriptmod=s" => \@scriptmod,
125 "serial|s:s" => \$serial,
126 "server:s" => \$server,
127 "strip-rom" => sub { $rom_prefix = ''; },
128 "target|t=s" => sub { my ($opt_name, $opt_value) = @_;
129 exists $CFG::targets{$opt_value} or die("Unknown target '$opt_value' (valid targets are: ".join(", ", sort keys(%CFG::targets)).")");
130 GetOptionsFromString($CFG::targets{$opt_value}, %opt_spec); },
134 GetOptions %opt_spec or pod2usage(2);
135 pod2usage(1) if $help;
136 pod2usage(-exitstatus => 0, -verbose => 2) if $man;
138 ### Dump sanitized configuration (if requested)
142 $Data::Dumper::Indent=1;
143 print "# This file is in perl syntax.\n";
144 foreach my $key(sort(keys(%CFG::))) { # See "Symbol Tables" in perlmod(1)
145 if (defined ${$CFG::{$key}}) { print Data::Dumper->Dump([${$CFG::{$key}}], ["*$key"]); }
146 if ( @{$CFG::{$key}}) { print Data::Dumper->Dump([\@{$CFG::{$key}}], ["*$key"]); }
147 if ( %{$CFG::{$key}}) { print Data::Dumper->Dump([\%{$CFG::{$key}}], ["*$key"]); }
153 ### Sanitize configuration
155 if (defined $config_name_opt && scalar(@ARGV) > 1) { die "You cannot use --name with multiple scripts"; }
158 if (defined $serial) { $serial ||= "/dev/ttyUSB0"; }
159 if (defined $grub_config) { $grub_config ||= "menu.lst"; }
160 if (defined $grub2_config) { $grub2_config ||= "grub.cfg"; }
162 ## Parse the novaboot script(s)
168 my ($modules, $variables, $generated, $continuation);
170 if ($ARGV ne $last_fn) { # New script
171 die "Missing EOF in $last_fn" if $file;
172 die "Unfinished line in $last_fn" if $line;
174 push @scripts, { 'filename' => $ARGV,
175 'modules' => $modules = [],
176 'variables' => $variables = {},
177 'generated' => $generated = []};
181 next if /^#/ || /^\s*$/; # Skip comments and empty lines
183 foreach my $mod(@scriptmod) { eval $mod; }
185 print "$_\n" if $dump_opt;
187 if (/^([A-Z_]+)=(.*)$/) { # Internal variable
188 $$variables{$1} = $2;
191 if (/^([^ ]*)(.*?)[[:space:]]*<<([^ ]*)$/) { # Heredoc start
192 push @$modules, "$1$2";
194 push @$generated, {filename => $1, content => $file};
198 if ($file && $_ eq $EOF) { # Heredoc end
202 if ($file) { # Heredoc content
203 push @{$file}, "$_\n";
206 $_ =~ s/^[[:space:]]*// if ($continuation);
207 if (/\\$/) { # Line continuation
208 $line .= substr($_, 0, length($_)-1);
214 $line .= " $append" if ($append && scalar(@$modules) == 0);
216 if ($line =~ /^([^ ]*)(.*?)[[:space:]]*< ?(.*)$/) { # Command substitution
217 push @$modules, "$1$2";
218 push @$generated, {filename => $1, command => $3};
222 push @$modules, $line;
226 #print Dumper(\@scripts);
232 sub generate_configs($$$) {
233 my ($base, $generated, $filename) = @_;
234 if ($base) { $base = "$base/"; };
235 foreach my $g(@$generated) {
236 if (exists $$g{content}) {
237 my $config = $$g{content};
238 my $fn = $$g{filename};
239 open(my $f, '>', $fn) || die("$fn: $!");
240 map { s|\brom://([^ ]*)|$rom_prefix$base$1|g; print $f "$_"; } @{$config};
242 print "novaboot: Created $fn\n";
243 } elsif (exists $$g{command} && ! $no_file_gen) {
244 $ENV{SRCDIR} = dirname(File::Spec->rel2abs( $filename, $invocation_dir ));
245 system_verbose("( $$g{command} ) > $$g{filename}");
250 sub generate_grub_config($$$$;$)
252 my ($filename, $title, $base, $modules_ref, $preamble) = @_;
253 if ($base) { $base = "$base/"; };
254 open(my $fg, '>', $filename) or die "$filename: $!";
255 print $fg "$preamble\n" if $preamble;
256 print $fg "title $title\n" if $title;
257 #print $fg "root $base\n"; # root doesn't really work for (nd)
259 foreach (@$modules_ref) {
262 my ($kbin, $kcmd) = split(' ', $_, 2);
263 $kcmd = '' if !defined $kcmd;
264 print $fg "kernel ${base}$kbin $kcmd\n";
266 s|\brom://([^ ]*)|$rom_prefix$base$1|g; # Translate rom:// files - needed for vdisk parameter of sigma0
267 print $fg "module $base$_\n";
271 print("novaboot: Created $builddir/$filename\n");
275 sub generate_grub2_config($$$$;$$)
277 my ($filename, $title, $base, $modules_ref, $preamble, $prolog) = @_;
278 if ($base && substr($base,-1,1) ne '/') { $base = "$base/"; };
279 open(my $fg, '>', $filename) or die "$filename: $!";
280 print $fg "$preamble\n" if $preamble;
281 $title ||= 'novaboot';
282 print $fg "menuentry $title {\n";
283 print $fg "$prolog\n" if $prolog;
285 foreach (@$modules_ref) {
288 my ($kbin, $kcmd) = split(' ', $_, 2);
289 $kcmd = '' if !defined $kcmd;
290 print $fg " multiboot ${base}$kbin $kcmd\n";
293 # GRUB2 doesn't pass filename in multiboot info so we have to duplicate it here
294 $_ = join(' ', ($args[0], @args));
295 s|\brom://|$rom_prefix|g; # We do not need to translate path for GRUB2
296 print $fg " module $base$_\n";
301 print("novaboot: Created $builddir/$filename\n");
305 sub generate_pulsar_config($$)
307 my ($filename, $modules_ref) = @_;
308 open(my $fg, '>', $filename) or die "$filename: $!";
309 print $fg "root $pulsar_root\n" if defined $pulsar_root;
312 foreach (@$modules_ref) {
315 ($kbin, $kcmd) = split(' ', $_, 2);
316 $kcmd = '' if !defined $kcmd;
319 s|\brom://|$rom_prefix|g;
320 print $fg "load $_\n";
323 # Put kernel as last - this is needed for booting Linux and has no influence on non-Linux OSes
324 print $fg "exec $kbin $kcmd\n";
326 print("novaboot: Created $builddir/$filename\n");
332 print "novaboot: Running: ".join(' ', map("'$_'", @_))."\n";
336 sub system_verbose($)
339 print "novaboot: Running: $cmd\n";
340 my $ret = system($cmd);
341 if ($ret & 0x007f) { die("Command terminated by a signal"); }
342 if ($ret & 0xff00) {die("Command exit with non-zero exit code"); }
343 if ($ret) { die("Command failure $ret"); }
348 if (exists $variables->{WVDESC}) {
349 print "Testing \"$variables->{WVDESC}\" in $last_fn:\n";
350 } elsif ($last_fn =~ /\.wv$/) {
351 print "Testing \"all\" in $last_fn:\n";
354 ## Handle reset and power on/off - TODO: move after the file generation phase
357 if (defined $iprelay) {
358 $iprelay =~ /([.0-9]+)(:([0-9]+))?/;
361 my $paddr = sockaddr_in($port, inet_aton($addr));
362 my $proto = getprotobyname('tcp');
363 socket($IPRELAY, PF_INET, SOCK_STREAM, $proto) || die "socket: $!";
364 print "novaboot: Connecting to IP relay... ";
365 connect($IPRELAY, $paddr) || die "connect: $!";
367 $IPRELAY->autoflush(1);
370 print $IPRELAY "\xFF\xF6";
372 local $SIG{ALRM} = sub { die "Relay AYT timeout"; };
373 my $ayt_reponse = "";
374 my $read = sysread($IPRELAY, $ayt_reponse, 100);
378 print "$ayt_reponse\n";
379 if ($ayt_reponse =~ /<iprelayd: not connected/) {
387 my ($relay, $onoff) = @_;
388 die unless ($relay == 1 || $relay == 2);
390 my $cmd = ($relay == 1 ? 0x5 : 0x6) | ($onoff ? 0x20 : 0x10);
391 return "\xFF\xFA\x2C\x32".chr($cmd)."\xFF\xF0";
395 my ($relay, $onoff) = @_;
396 die unless ($relay == 1 || $relay == 2);
397 my $cmd = ($relay == 1 ? 0xdf : 0xbf) | ($onoff ? 0x00 : 0xff);
398 return "\xFF\xFA\x2C\x97".chr($cmd)."\xFF\xF0";
402 my ($relay, $onoff, $can_giveup) = @_;
403 my $confirmation = '';
404 print $IPRELAY relaycmd($relay, $onoff);
406 # We use non-blocking I/O and polling here because for some
407 # reason read() on blocking FD returns only after all
408 # requested data is available. If we get during the first
409 # read() only a part of confirmation, we may get the rest
410 # after the system boots and print someting, which may be too
412 $IPRELAY->blocking(0);
414 alarm(20); # Timeout in seconds
416 local $SIG{ALRM} = sub {
417 if ($can_giveup) { print("Relay confirmation timeout - ignoring\n"); $giveup = 1;}
418 else {die "Relay confirmation timeout";}
421 while (($index=index($confirmation, relayconf($relay, $onoff))) < 0 && !$giveup) {
422 my $read = read($IPRELAY, $confirmation, 70, length($confirmation));
423 if (!defined($read)) {
424 die("IP relay: $!") unless $! == EAGAIN;
428 #use MIME::QuotedPrint;
429 #print "confirmation = ".encode_qp($confirmation)."\n";
432 $IPRELAY->blocking(1);
436 if ($iprelay && (defined $on_opt || defined $off_opt)) {
437 relay(1, 1); # Press power button
438 if (defined $on_opt) {
439 usleep(100000); # Short press
441 print "novaboot: Switching the target off...\n";
442 usleep(6000000); # Long press to switch off
444 print $IPRELAY relay(1, 0);
448 $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);
466 if (exists $variables->{KERNEL}) {
467 $kernel = $variables->{KERNEL};
469 if ($CFG::hypervisor) {
470 $kernel = $CFG::hypervisor . " ";
471 if (exists $variables->{HYPERVISOR_PARAMS}) {
472 $kernel .= $variables->{HYPERVISOR_PARAMS};
474 $kernel .= $CFG::hypervisor_params;
478 @$modules = ($kernel, @$modules) if $kernel;
479 @$modules = (@chainloaders, @$modules);
480 @$modules = ("bin/boot/bender", @$modules) if ($bender || defined $ENV{'NOVABOOT_BENDER'});
483 ($prefix = $grub_prefix) =~ s/\$NAME/$config_name/ if defined $grub_prefix;
484 $prefix ||= $builddir;
485 # TODO: use $grub_prefix as first parameter if some switch is given
486 generate_configs('', $generated, $filename);
488 ### Generate bootloader configuration files
489 my @bootloader_configs;
490 push @bootloader_configs, generate_grub_config($grub_config, $config_name, $prefix, $modules, $grub_preamble) if (defined $grub_config);
491 push @bootloader_configs, generate_grub2_config($grub2_config, $config_name, $prefix, $modules, $grub_preamble, $grub2_prolog) if (defined $grub2_config);
492 push @bootloader_configs, generate_pulsar_config('config-'.($pulsar||'novaboot'), $modules) if (defined $pulsar);
495 if (defined $scons) {
496 my @files = map({ ($file) = m/([^ ]*)/; $file; } @$modules);
497 # Filter-out generated files
498 my @to_build = grep({ my $file = $_; !scalar(grep($file eq $$_{filename}, @$generated)) } @files);
499 system_verbose($scons || $CFG::scons." ".join(" ", @to_build));
502 ### Copy files (using rsync)
503 if (defined $server && !defined($gen_only)) {
504 (my $real_server = $server) =~ s/\$NAME/$config_name/;
506 my ($hostname, $path) = split(":", $real_server, 2);
507 if (! defined $path) {
511 my $files = join(" ", map({ ($file) = m/([^ ]*)/; $file; } ( @$modules, @bootloader_configs)));
512 map({ my $file = (split)[0]; die "$file: $!" if ! -f $file; } @$modules);
513 my $istty = -t STDOUT && ($ENV{'TERM'} || 'dumb') ne 'dumb';
514 my $progress = $istty ? "--progress" : "";
515 system_verbose("rsync $progress -RLp $rsync_flags $files $real_server");
516 if ($server =~ m|/\$NAME$| && $concat) {
517 my $cmd = join("; ", map { "( cd $path/.. && cat */$_ > $_ )" } @bootloader_configs);
518 system_verbose($hostname ? "ssh $hostname '$cmd'" : $cmd);
522 ### Prepare ISO image generation
523 if (defined $iso_image) {
524 generate_configs("(cd)", $generated, $filename);
526 generate_grub_config(\$menu, $config_name, "(cd)", $modules);
527 $menu_iso .= "$menu\n";
528 map { ($file,undef) = split; $files_iso{$file} = 1; } @$modules;
532 ## Generate ISO image
533 if (defined $iso_image) {
534 open(my $fh, ">menu-iso.lst");
535 print $fh "timeout 5\n\n$menu_iso";
537 my $files = "boot/grub/menu.lst=menu-iso.lst " . join(" ", map("$_=$_", keys(%files_iso)));
538 $iso_image ||= "$config_name.iso";
539 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");
540 print("ISO image created: $builddir/$iso_image\n");
543 exit(0) if defined $gen_only;
545 ## Boot the system using various methods and send serial output to stdout
547 if (scalar(@scripts) > 1 && ( defined $dhcp_tftp || defined $serial || defined $iprelay)) {
548 die "You cannot do this with multiple scripts simultaneously";
551 if ($variables->{WVTEST_TIMEOUT}) {
552 print "wvtest: timeout ", $variables->{WVTEST_TIMEOUT}, "\n";
557 $str =~ s/^\s+|\s+$//g;
563 if (!(defined $dhcp_tftp || defined $serial || defined $iprelay || defined $server || defined $iso_image)) {
565 $qemu ||= $variables->{QEMU} || $CFG::qemu;
566 my @qemu_flags = split(" ", $qemu);
567 $qemu = shift(@qemu_flags);
569 @qemu_flags = split(/ +/, trim($variables->{QEMU_FLAGS})) if exists $variables->{QEMU_FLAGS};
570 @qemu_flags = split(/ +/, trim($qemu_flags_cmd)) if $qemu_flags_cmd;
571 push(@qemu_flags, split(/ +/, trim($qemu_append || '')));
573 if (defined $iso_image) {
574 # Boot NOVA with grub (and test the iso image)
575 push(@qemu_flags, ('-cdrom', "$config_name.iso"));
577 # Boot NOVA without GRUB
579 # Non-patched qemu doesn't like commas, but NUL can live with pluses instead of commans
580 foreach (@$modules) {s/,/+/g;}
581 generate_configs("", $generated, $filename);
583 my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);
584 $kcmd = '' if !defined $kcmd;
586 @$modules = map { if (/\.dtb$/) { $dtb=$_; (); } else { $_ } } @$modules;
587 my $initrd = join ",", @$modules;
589 push(@qemu_flags, ('-kernel', $kbin, '-append', $kcmd));
590 push(@qemu_flags, ('-initrd', $initrd)) if $initrd;
591 push(@qemu_flags, ('-dtb', $dtb)) if $dtb;
593 push(@qemu_flags, qw(-serial stdio)); # Redirect serial output (for collecting test restuls)
594 exec_verbose(($qemu, '-name', $config_name, @qemu_flags));
597 ### Local DHCPD and TFTPD
599 my ($dhcpd_pid, $tftpd_pid);
601 if (defined $dhcp_tftp)
603 generate_configs("(nd)", $generated, $filename);
604 system_verbose('mkdir -p tftpboot');
605 generate_grub_config("tftpboot/os-menu.lst", $config_name, "(nd)", \@$modules, "timeout 0");
606 open(my $fh, '>', 'dhcpd.conf');
607 my $mac = `cat /sys/class/net/eth0/address`;
609 print $fh "subnet 10.23.23.0 netmask 255.255.255.0 {
610 range 10.23.23.10 10.23.23.100;
611 filename \"bin/boot/grub/pxegrub.pxe\";
612 next-server 10.23.23.1;
615 hardware ethernet $mac;
616 fixed-address 10.23.23.1;
619 system_verbose("sudo ip a add 10.23.23.1/24 dev eth0;
620 sudo ip l set dev eth0 up;
621 sudo touch dhcpd.leases");
624 if ($dhcpd_pid == 0) {
625 # This way, the spawned server are killed when this script is killed.
626 exec_verbose("sudo dhcpd -d -cf dhcpd.conf -lf dhcpd.leases -pf dhcpd.pid");
629 if ($tftpd_pid == 0) {
630 exec_verbose("sudo in.tftpd --foreground --secure -v -v -v $builddir");
632 $SIG{TERM} = sub { print "CHILDS KILLED\n"; kill 15, $dhcpd_pid, $tftpd_pid; };
635 ### Serial line or IP relay
637 if ($serial || defined $iprelay) {
639 if (defined $iprelay) {
640 print "novaboot: Reseting the test box... ";
641 relay(2, 1, 1); # Reset the machine
648 system("stty -F $serial raw -crtscts -onlcr 115200");
649 open($CONN, "+<", $serial) || die "open $serial: $!";
653 # Pass the NOVA output to stdout.
657 kill 15, $dhcpd_pid, $tftpd_pid if ($dhcp_tftp);
661 ### Wait for dhcpc or tftpd
662 if (defined $dhcp_tftp) {
664 if ($pid == $dhcpd_pid) { print "dhcpd exited!\n"; }
665 elsif ($pid == $tftpd_pid) { print "tftpd exited!\n"; }
666 else { print "wait returned: $pid\n"; }
667 kill(15, 0); # Kill current process group i.e. all remaining children
674 novaboot - A tool for booting various operating systems on various hardware or in qemu
678 B<novaboot> [ options ] [--] script...
680 B<./script> [ options ]
686 This program makes it easier to boot NOVA or other operating system
687 (OS) in different environments. It reads a so called novaboot script
688 and uses it either to boot the OS in an emulator (e.g. in qemu) or to
689 generate the configuration for a specific bootloader and optionally to
690 copy the necessary binaries and other needed files to proper
691 locations, perhaps on a remote server. In case the system is actually
692 booted, its serial output is redirected to standard output if that is
695 A typical way of using novaboot is to make the novaboot script
696 executable and set its first line to I<#!/usr/bin/env novaboot>. Then,
697 booting a particular OS configuration becomes the same as executing a
698 local program - the novaboot script.
700 With C<novaboot> you can:
706 Run an OS in Qemu. This is the default action when no other action is
707 specified by command line switches. Thus running C<novaboot ./script>
708 (or C<./script> as described above) will run Qemu and make it boot the
709 configuration specified in the I<script>.
713 Create a bootloader configuration file (currently supported
714 bootloaders are GRUB, GRUB2 and Pulsar) and copy it with all other
715 files needed for booting to another, perhaps remote, location.
717 ./script --server --iprelay=192.168.1.2
719 This command copies files to a TFTP server specified in the
720 configuration file and uses TCP/IP-controlled relay to reset the test
721 box and receive its serial output.
725 Run DHCP and TFTP server on developer's machine to PXE-boot NOVA from
730 When a PXE-bootable machine is connected via Ethernet to developer's
731 machine, it will boot the configuration described in I<script>.
735 Create bootable ISO images. E.g.
737 novaboot --iso -- script1 script2
739 The created ISO image will have GRUB bootloader installed on it and
740 the boot menu will allow selecting between I<script1> and I<script2>
745 =head1 PHASES AND OPTIONS
747 Novaboot perform its work in several phases. Each phase can be
748 influenced by several options, certain phases can be skipped. The list
749 of phases with the corresponding options follows.
751 =head2 Configuration reading phase
753 After starting, novaboot reads configuration files. By default, it
754 searches for files named F<.novaboot> starting from the directory of
755 the novaboot script (or working directory, see bellow) and continuing
756 upwards up to the root directory. The configuration files are read in
757 order from the root directory downwards with latter files overriding
758 settings from the former ones.
760 In certain cases, the location of the novaboot script cannot be
761 determined in this early phase. This happens either when the script is
762 read from the standard input or when novaboot is invoked explicitly
763 and options precede the script name, as in the example L</"4."> above.
764 In this case the current working directory is used as a starting point
765 for configuration file search.
769 =item -c, --config=<filename>
771 Use the specified configuration file instead of the default one(s).
775 =head2 Command line processing phase
781 Dump the current configuration to stdout end exits. Useful as an
782 initial template for a configuration file.
786 Print short (B<-h>) or long (B<--help>) help.
788 =item -t, --target=<target>
790 This option serves as a user configurable shortcut for other novaboot
791 options. The effect of this option is the same as the options stored
792 in the C<%targets> configuration variable under key I<target>. See
793 also L</"CONFIGURATION FILE">.
797 =head2 Script preprocessing phase
799 This phases allows to modify the parsed novaboot script before it is
800 used in the later phases.
804 =item -a, --append=<parameters>
806 Appends a string to the first "filename" line in the novaboot script.
807 This can be used to append parameters to the kernel's or root task's
812 Use F<bender> chainloader. Bender scans the PCI bus for PCI serial
813 ports and stores the information about them in the BIOS data area for
816 =item --chainloader=<chainloader>
818 Chainloader that is loaded before the kernel and other files specified
819 in the novaboot script. E.g. 'bin/boot/bender promisc'.
823 Prints the content of the novaboot script after removing comments and
824 evaluating all I<--scriptmod> expressions. Exit after reading (and
827 =item --scriptmod=I<perl expression>
829 When novaboot script is read, I<perl expression> is executed for every
830 line (in $_ variable). For example, C<novaboot
831 --scriptmod=s/sigma0/omega6/g> replaces every occurrence of I<sigma0>
832 in the script with I<omega6>.
834 When this option is present, it overrides I<$script_modifier> variable
835 from the configuration file, which has the same effect. If this option
836 is given multiple times all expressions are evaluated in the command
841 Strip I<rom://> prefix from command lines and generated config files.
842 The I<rom://> prefix is used by NUL. For NRE, it has to be stripped.
846 =head2 File generation phase
848 In this phase, files needed for booting are generated in a so called
849 I<build directory> (see TODO). In most cases configuration for a
850 bootloader is generated automatically by novaboot. It is also possible
851 to generate other files using I<heredoc> or I<"<"> syntax in novaboot
852 scripts. Finally, binaries can be generated in this phases by running
857 =item --build-dir=<directory>
859 Overrides the default build directory location.
861 The default build directory location is determined as follows: If the
862 configuration file defines the C<$builddir> variable, its value is
863 used. Otherwise, it is the directory that contains the first processed
866 =item -g, --grub[=I<filename>]
868 Generates grub bootloader menu file. If the I<filename> is not
869 specified, F<menu.lst> is used. The I<filename> is relative to the
870 build directory (see B<--build-dir>).
872 =item --grub-preamble=I<prefix>
874 Specifies the I<preable> that is at the beginning of the generated
875 GRUB or GRUB2 config files. This is useful for specifying GRUB's
878 =item --grub-prefix=I<prefix>
880 Specifies I<prefix> that is put in front of every file name in GRUB's
881 F<menu.lst>. The default value is the absolute path to the build directory.
883 If the I<prefix> contains string $NAME, it will be replaced with the
884 name of the novaboot script (see also B<--name>).
886 =item --grub2[=I<filename>]
888 Generate GRUB2 menuentry in I<filename>. If I<filename> is not
889 specified F<grub.cfg> is used. The content of the menuentry can be
890 customized with B<--grub-preable>, B<--grub2-prolog> or
891 B<--grub_prefix> options.
893 In order to use the the generated menuentry on your development
894 machine that uses GRUB2, append the following snippet to
895 F</etc/grub.d/40_custom> file and regenerate your grub configuration,
896 i.e. run update-grub on Debian/Ubuntu.
898 if [ -f /path/to/nul/build/grub.cfg ]; then
899 source /path/to/nul/build/grub.cfg
902 =item --grub2-prolog=I<prolog>
904 Specifies text I<preable> that is put at the begiging of the entry
907 =item --name=I<string>
909 Use the name I<string> instead of the name of the novaboot script.
910 This name is used for things like a title of grub menu or for the
911 server directory where the boot files are copied to.
915 Do not generate files on the fly (i.e. "<" syntax) except for the
916 files generated via "<<WORD" syntax.
918 =item -p, --pulsar[=mac]
920 Generates pulsar bootloader configuration file named F<config-I<mac>>
921 The I<mac> string is typically a MAC address and defaults to
924 =item --scons[=scons command]
926 Runs I<scons> to build files that are not generated by novaboot
931 Exit novaboot after file generation phase.
935 =head2 Target connection check
937 If supported by the target, the connection to it is made and it is
938 checked whether the target is not occupied by another novaboot
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.
993 =head2 Target power-on and reset phase
997 =item --iprelay=I<addr[:port]>
999 Use IP relay to reset the machine and to get the serial output. The IP
1000 address of the relay is given by I<addr> parameter.
1002 Note: This option is expected to work with HWG-ER02a IP relays.
1006 Switch on/off the target machine. Currently works only with
1009 =item -Q, --qemu=I<qemu-binary>
1011 The name of qemu binary to use. The default is 'qemu'.
1013 =item --qemu-append=I<flags>
1015 Append I<flags> to the default qemu flags (QEMU_FLAGS variable or
1016 C<-cpu coreduo -smp 2>).
1018 =item -q, --qemu-flags=I<flags>
1020 Replace the default qemu flags (QEMU_FLAGS variable or C<-cpu coreduo
1021 -smp 2>) with I<flags> specified here.
1025 =head2 Interaction with the bootloader on the target
1027 See B<--serial>. There will be new options soon.
1029 =head2 Target's output reception phase
1033 =item -s, --serial[=device]
1035 Use serial line to control GRUB bootloader and to see the output
1036 serial output of the machine. The default value is F</dev/ttyUSB0>.
1040 See also B<--iprelay>.
1042 =head2 Termination phase
1044 Daemons that were spwned (F<dhcpd> and F<tftpd>) are killed here.
1046 =head1 NOVABOOT SCRIPT SYNTAX
1048 The syntax tries to mimic POSIX shell syntax. The syntax is defined with the following rules.
1050 Lines starting with "#" are ignored.
1052 Lines that end with "\" are concatenated with the following line after
1053 removal of the final "\" and leading whitespace of the following line.
1055 Lines in the form I<VARIABLE=...> (i.e. matching '^[A-Z_]+=' regular
1056 expression) assign values to internal variables. See VARIABLES
1059 Otherwise, the first word on the line represents the filename
1060 (relative to the build directory (see B<--build-dir>) of the module to
1061 load and the remaining words are passed as the command line
1064 When the line ends with "<<WORD" then the subsequent lines until the
1065 line containing only WORD are copied literally to the file named on
1068 When the line ends with "< CMD" the command CMD is executed with
1069 C</bin/sh> and its standard output is stored in the file named on that
1070 line. The SRCDIR variable in CMD's environment is set to the absolute
1071 path of the directory containing the interpreted novaboot script.
1074 #!/usr/bin/env novaboot
1075 WVDESC=Example program
1076 bin/apps/sigma0.nul S0_DEFAULT script_start:1,1 \
1077 verbose hostkeyb:0,0x60,1,12,2
1079 hello.nulconfig <<EOF
1080 sigma0::mem:16 name::/s0/log name::/s0/timer name::/s0/fs/rom ||
1081 rom://bin/apps/hello.nul
1084 This example will load three modules: sigma0.nul, hello.nul and
1085 hello.nulconfig. sigma0 gets some command line parameters and
1086 hello.nulconfig file is generated on the fly from the lines between
1091 The following variables are interpreted in the novaboot script:
1097 Description of the wvtest-compliant program.
1099 =item WVTEST_TIMEOUT
1101 The timeout in seconds for WvTest harness. If no complete line appears
1102 in the test output within the time specified here, the test fails. It
1103 is necessary to specify this for long running tests that produce no
1104 intermediate output.
1108 Use a specific qemu binary (can be overriden with B<-Q>) and flags
1109 when booting this script under qemu. If QEMU_FLAGS variable is also
1110 specified flags specified in QEMU variable are replaced by those in
1115 Use specific qemu flags (can be overriden with B<-q>).
1117 =item HYPERVISOR_PARAMS
1119 Parameters passed to hypervisor. The default value is "serial", unless
1120 overriden in configuration file.
1124 The kernel to use instead of NOVA hypervisor specified in the
1125 configuration file. The value should contain the name of the kernel
1126 image as well as its command line parameters. If this variable is
1127 defined and non-empty, the variable HYPERVISOR_PARAMS is not used.
1131 =head1 CONFIGURATION FILE
1133 Novaboot can read its configuration from a file. Configuration file
1134 was necessary in early days of novaboot. Nowadays, an attempt is made
1135 to not use the configuration file because it makes certain novaboot
1136 scripts unusable on systems without (or with different) configuration
1137 file. The only recommended use of the configuration file is to specify
1138 custom_options (see bellow).
1140 If you decide to use the configuration file, it is looked up, by
1141 default, in files named F<.novaboot> as described in L</Configuration
1142 reading phase>. Alternatively, its location can be specified with the
1143 B<-c> switch or with the NOVABOOT_CONFIG environment variable. The
1144 configuration file has perl syntax and should set values of certain
1145 Perl variables. The current configuration can be dumped with the
1146 B<--dump-config> switch. Some configuration variables can be overriden
1147 by environment variables (see below) or by command line switches.
1149 Documentation of some configuration variables follows:
1155 Build directory location relative to the location of the configuration
1160 Hash of shortcuts to be used with the B<--target> option. If the hash
1161 contains, for instance, the following pair of values
1163 'mybox' => '--server=boot:/tftproot --serial=/dev/ttyUSB0 --grub',
1165 then the following two commands are equivalent:
1167 ./script --server=boot:/tftproot --serial=/dev/ttyUSB0 --grub
1172 =head1 ENVIRONMENT VARIABLES
1174 Some options can be specified not only via config file or command line
1175 but also through environment variables. Environment variables override
1176 the values from configuration file and command line parameters
1177 override the environment variables.
1181 =item NOVABOOT_CONFIG
1183 Name of the novaboot configuration file to use instead of the default
1186 =item NOVABOOT_BENDER
1188 Defining this variable has the same meaning as B<--bender> option.
1194 Michal Sojka <sojka@os.inf.tu-dresden.de>