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);
35 my $invocation_dir = getcwd();
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';
45 "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',
46 "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',
47 "localhost" => '--scriptmod=s/console=tty[A-Z0-9,]+// --server=/boot/novaboot/$NAME --grub2 --grub-prefix=/boot/novaboot/$NAME --grub2-prolog=" set root=\'(hd0,msdos1)\'"',
49 $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 $builddir = File::Spec->rel2abs($CFG::builddir, dirname($cfg)) if defined $CFG::builddir;
69 print STDERR "novaboot: Read $cfg\n";
74 # We don't use $0 here, because it points to the novaboot itself and
75 # not to the novaboot script. The problem with this approach is that
76 # when a script is run as "novaboot <options> <script>" then $ARGV[0]
77 # contains the first option. Hence the -f check.
78 my $dir = File::Spec->rel2abs($ARGV[0] && -f $ARGV[0] ? dirname($ARGV[0]) : '', $invocation_dir);
79 while (-d $dir && $dir ne "/") {
80 push @cfgs, "$dir/.novaboot" if -r "$dir/.novaboot";
81 $dir = abs_path($dir."/..");
84 my $cfg = $ENV{'NOVABOOT_CONFIG'};
85 Getopt::Long::Configure(qw/no_ignore_case pass_through/);
86 GetOptions ("config|c=s" => \$cfg);
87 read_config($_) foreach $cfg or reverse @cfgs;
89 ## Command line handling
91 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, $stty);
94 $rom_prefix = 'rom://';
95 $stty = 'raw -crtscts -onlcr 115200';
97 Getopt::Long::Configure(qw/no_ignore_case no_pass_through/);
100 "append|a=s" => \$append,
101 "bender|b" => \$bender,
102 "build-dir=s" => sub { my ($n, $v) = @_; $builddir = File::Spec->rel2abs($v); },
103 "concat" => \$concat,
104 "chainloader=s" => \@chainloaders,
105 "dhcp-tftp|d" => \$dhcp_tftp,
106 "dump" => \$dump_opt,
107 "dump-config" => \$dump_config,
108 "gen-only" => \$gen_only,
109 "grub|g:s" => \$grub_config,
110 "grub-preamble=s"=> \$grub_preamble,
111 "grub-prefix=s" => \$grub_prefix,
112 "grub2:s" => \$grub2_config,
113 "grub2-prolog=s" => \$grub2_prolog,
114 "iprelay=s" => \$iprelay,
115 "iso|i:s" => \$iso_image,
116 "name=s" => \$config_name_opt,
117 "no-file-gen" => \$no_file_gen,
120 "pulsar|p:s" => \$pulsar,
121 "pulsar-root=s" => \$pulsar_root,
122 "qemu|Q=s" => \$qemu,
123 "qemu-append=s" => \$qemu_append,
124 "qemu-flags|q=s" => \$qemu_flags_cmd,
125 "rsync-flags=s" => \$rsync_flags,
126 "scons:s" => \$scons,
127 "scriptmod=s" => \@scriptmod,
128 "serial|s:s" => \$serial,
129 "server:s" => \$server,
130 "strip-rom" => sub { $rom_prefix = ''; },
132 "target|t=s" => sub { my ($opt_name, $opt_value) = @_;
133 exists $CFG::targets{$opt_value} or die("Unknown target '$opt_value' (valid targets are: ".join(", ", sort keys(%CFG::targets)).")");
134 GetOptionsFromString($CFG::targets{$opt_value}, %opt_spec); },
138 GetOptions %opt_spec or pod2usage(2);
139 pod2usage(1) if $help;
140 pod2usage(-exitstatus => 0, -verbose => 2) if $man;
142 ### Dump sanitized configuration (if requested)
146 $Data::Dumper::Indent=1;
147 print "# This file is in perl syntax.\n";
148 foreach my $key(sort(keys(%CFG::))) { # See "Symbol Tables" in perlmod(1)
149 if (defined ${$CFG::{$key}}) { print Data::Dumper->Dump([${$CFG::{$key}}], ["*$key"]); }
150 if ( @{$CFG::{$key}}) { print Data::Dumper->Dump([\@{$CFG::{$key}}], ["*$key"]); }
151 if ( %{$CFG::{$key}}) { print Data::Dumper->Dump([\%{$CFG::{$key}}], ["*$key"]); }
157 ### Sanitize configuration
159 if (defined $config_name_opt && scalar(@ARGV) > 1) { die "You cannot use --name with multiple scripts"; }
162 if (defined $serial) { $serial ||= "/dev/ttyUSB0"; }
163 if (defined $grub_config) { $grub_config ||= "menu.lst"; }
164 if (defined $grub2_config) { $grub2_config ||= "grub.cfg"; }
166 ## Parse the novaboot script(s)
172 my ($modules, $variables, $generated, $continuation);
174 if ($ARGV ne $last_fn) { # New script
175 die "Missing EOF in $last_fn" if $file;
176 die "Unfinished line in $last_fn" if $line;
178 push @scripts, { 'filename' => $ARGV,
179 'modules' => $modules = [],
180 'variables' => $variables = {},
181 'generated' => $generated = []};
185 next if /^#/ || /^\s*$/; # Skip comments and empty lines
187 foreach my $mod(@scriptmod) { eval $mod; }
189 print "$_\n" if $dump_opt;
191 if (/^([A-Z_]+)=(.*)$/) { # Internal variable
192 $$variables{$1} = $2;
195 if (/^([^ ]*)(.*?)[[:space:]]*<<([^ ]*)$/) { # Heredoc start
196 push @$modules, "$1$2";
198 push @$generated, {filename => $1, content => $file};
202 if ($file && $_ eq $EOF) { # Heredoc end
206 if ($file) { # Heredoc content
207 push @{$file}, "$_\n";
210 $_ =~ s/^[[:space:]]*// if ($continuation);
211 if (/\\$/) { # Line continuation
212 $line .= substr($_, 0, length($_)-1);
218 $line .= " $append" if ($append && scalar(@$modules) == 0);
220 if ($line =~ /^([^ ]*)(.*?)[[:space:]]*< ?(.*)$/) { # Command substitution
221 push @$modules, "$1$2";
222 push @$generated, {filename => $1, command => $3};
226 push @$modules, $line;
230 #print Dumper(\@scripts);
236 sub generate_configs($$$) {
237 my ($base, $generated, $filename) = @_;
238 if ($base) { $base = "$base/"; };
239 foreach my $g(@$generated) {
240 if (exists $$g{content}) {
241 my $config = $$g{content};
242 my $fn = $$g{filename};
243 open(my $f, '>', $fn) || die("$fn: $!");
244 map { s|\brom://([^ ]*)|$rom_prefix$base$1|g; print $f "$_"; } @{$config};
246 print "novaboot: Created $fn\n";
247 } elsif (exists $$g{command} && ! $no_file_gen) {
248 $ENV{SRCDIR} = dirname(File::Spec->rel2abs( $filename, $invocation_dir ));
249 system_verbose("( $$g{command} ) > $$g{filename}");
254 sub generate_grub_config($$$$;$)
256 my ($filename, $title, $base, $modules_ref, $preamble) = @_;
257 if ($base) { $base = "$base/"; };
258 open(my $fg, '>', $filename) or die "$filename: $!";
259 print $fg "$preamble\n" if $preamble;
260 print $fg "title $title\n" if $title;
261 #print $fg "root $base\n"; # root doesn't really work for (nd)
263 foreach (@$modules_ref) {
266 my ($kbin, $kcmd) = split(' ', $_, 2);
267 $kcmd = '' if !defined $kcmd;
268 print $fg "kernel ${base}$kbin $kcmd\n";
270 s|\brom://([^ ]*)|$rom_prefix$base$1|g; # Translate rom:// files - needed for vdisk parameter of sigma0
271 print $fg "module $base$_\n";
275 print("novaboot: Created $builddir/$filename\n");
279 sub generate_grub2_config($$$$;$$)
281 my ($filename, $title, $base, $modules_ref, $preamble, $prolog) = @_;
282 if ($base && substr($base,-1,1) ne '/') { $base = "$base/"; };
283 open(my $fg, '>', $filename) or die "$filename: $!";
284 print $fg "$preamble\n" if $preamble;
285 $title ||= 'novaboot';
286 print $fg "menuentry $title {\n";
287 print $fg "$prolog\n" if $prolog;
289 foreach (@$modules_ref) {
292 my ($kbin, $kcmd) = split(' ', $_, 2);
293 $kcmd = '' if !defined $kcmd;
294 print $fg " multiboot ${base}$kbin $kcmd\n";
297 # GRUB2 doesn't pass filename in multiboot info so we have to duplicate it here
298 $_ = join(' ', ($args[0], @args));
299 s|\brom://|$rom_prefix|g; # We do not need to translate path for GRUB2
300 print $fg " module $base$_\n";
305 print("novaboot: Created $builddir/$filename\n");
309 sub generate_pulsar_config($$)
311 my ($filename, $modules_ref) = @_;
312 open(my $fg, '>', $filename) or die "$filename: $!";
313 print $fg "root $pulsar_root\n" if defined $pulsar_root;
316 foreach (@$modules_ref) {
319 ($kbin, $kcmd) = split(' ', $_, 2);
320 $kcmd = '' if !defined $kcmd;
323 s|\brom://|$rom_prefix|g;
324 print $fg "load $_\n";
327 # Put kernel as last - this is needed for booting Linux and has no influence on non-Linux OSes
328 print $fg "exec $kbin $kcmd\n";
330 print("novaboot: Created $builddir/$filename\n");
334 sub shell_cmd_string(@)
336 return join(' ', map((/^[-_=a-zA-Z0-9\/\.\+]+$/ ? "$_" : "'$_'"), @_));
341 print "novaboot: Running: ".shell_cmd_string(@_)."\n";
345 sub system_verbose($)
348 print "novaboot: Running: $cmd\n";
349 my $ret = system($cmd);
350 if ($ret & 0x007f) { die("Command terminated by a signal"); }
351 if ($ret & 0xff00) {die("Command exit with non-zero exit code"); }
352 if ($ret) { die("Command failure $ret"); }
357 if (exists $variables->{WVDESC}) {
358 print "Testing \"$variables->{WVDESC}\" in $last_fn:\n";
359 } elsif ($last_fn =~ /\.wv$/) {
360 print "Testing \"all\" in $last_fn:\n";
363 ## Connect to the target and check whether is not occupied
365 # We have to do this before file generation phase, because file
366 # generation is intermixed with file deployment phase and we want to
367 # check whether the target is not used by somebody else before
368 # deploying files. Otherwise, we may rewrite other user's files on a
371 my $exp; # Expect object to communicate with the target over serial line
373 my ($target_reset, $target_power_on, $target_power_off);
375 if (defined $iprelay) {
377 $iprelay =~ /([.0-9]+)(:([0-9]+))?/;
380 my $paddr = sockaddr_in($port, inet_aton($addr));
381 my $proto = getprotobyname('tcp');
382 socket($IPRELAY, PF_INET, SOCK_STREAM, $proto) || die "socket: $!";
383 print "novaboot: Connecting to IP relay... ";
384 connect($IPRELAY, $paddr) || die "connect: $!";
386 $exp = Expect->init(\*$IPRELAY);
390 print $exp "\xFF\xF6"; # AYT
391 my $connected = $exp->expect(20, # Timeout in seconds
392 '<iprelayd: connected>',
393 '-re', '<WEB51 HW[^>]*>');
398 my ($relay, $onoff) = @_;
399 die unless ($relay == 1 || $relay == 2);
401 my $cmd = ($relay == 1 ? 0x5 : 0x6) | ($onoff ? 0x20 : 0x10);
402 return "\xFF\xFA\x2C\x32".chr($cmd)."\xFF\xF0";
406 my ($relay, $onoff) = @_;
407 die unless ($relay == 1 || $relay == 2);
408 my $cmd = ($relay == 1 ? 0xdf : 0xbf) | ($onoff ? 0x00 : 0xff);
409 return "\xFF\xFA\x2C\x97".chr($cmd)."\xFF\xF0";
413 my ($relay, $onoff, $can_giveup) = @_;
414 my $confirmation = '';
416 print $exp relaycmd($relay, $onoff);
417 my $confirmed = $exp->expect(20, # Timeout in seconds
418 relayconf($relay, $onoff));
421 print("Relay confirmation timeout - ignoring\n");
423 die "Relay confirmation timeout";
429 $target_reset = sub {
430 relay(2, 1, 1); # Reset the machine
435 $target_power_off = sub {
436 relay(1, 1); # Press power button
437 usleep(6000000); # Long press to switch off
441 $target_power_on = sub {
442 relay(1, 1); # Press power button
443 usleep(100000); # Short press
449 system_verbose("stty -F $serial $stty");
450 open($CONN, "+<", $serial) || die "open $serial: $!";
451 $exp = Expect->init(\*$CONN);
454 $exp = new Expect(); # Make $exp ready for calling $exp->spawn() later
457 if (defined $on_opt && defined $target_power_on) {
461 if (defined $off_opt && defined $target_power_off) {
462 print "novaboot: Switching the target off...\n";
463 &$target_power_off();
467 $builddir ||= dirname(File::Spec->rel2abs( ${$scripts[0]}{filename})) if scalar @scripts;
468 if (defined $builddir) {
469 chdir($builddir) or die "Can't change directory to $builddir: $!";
470 print "novaboot: Entering directory `$builddir'\n";
473 ## File generation phase
474 my (%files_iso, $menu_iso, $filename);
475 my $config_name = '';
477 foreach my $script (@scripts) {
478 $filename = $$script{filename};
479 $modules = $$script{modules};
480 $generated = $$script{generated};
481 $variables = $$script{variables};
483 ($config_name = $filename) =~ s#.*/##;
484 $config_name = $config_name_opt if (defined $config_name_opt);
486 if (exists $variables->{BUILDDIR}) {
487 $builddir = File::Spec->rel2abs($variables->{BUILDDIR});
488 chdir($builddir) or die "Can't change directory to $builddir: $!";
489 print "novaboot: Entering directory `$builddir'\n";
493 if (exists $variables->{KERNEL}) {
494 $kernel = $variables->{KERNEL};
496 if ($CFG::hypervisor) {
497 $kernel = $CFG::hypervisor . " ";
498 if (exists $variables->{HYPERVISOR_PARAMS}) {
499 $kernel .= $variables->{HYPERVISOR_PARAMS};
501 $kernel .= $CFG::hypervisor_params;
505 @$modules = ($kernel, @$modules) if $kernel;
506 @$modules = (@chainloaders, @$modules);
507 @$modules = ("bin/boot/bender", @$modules) if ($bender || defined $ENV{'NOVABOOT_BENDER'});
510 ($prefix = $grub_prefix) =~ s/\$NAME/$config_name/ if defined $grub_prefix;
511 $prefix ||= $builddir;
512 # TODO: use $grub_prefix as first parameter if some switch is given
513 generate_configs('', $generated, $filename);
515 ### Generate bootloader configuration files
516 my @bootloader_configs;
517 push @bootloader_configs, generate_grub_config($grub_config, $config_name, $prefix, $modules, $grub_preamble) if (defined $grub_config);
518 push @bootloader_configs, generate_grub2_config($grub2_config, $config_name, $prefix, $modules, $grub_preamble, $grub2_prolog) if (defined $grub2_config);
519 push @bootloader_configs, generate_pulsar_config('config-'.($pulsar||'novaboot'), $modules) if (defined $pulsar);
522 if (defined $scons) {
523 my @files = map({ ($file) = m/([^ ]*)/; $file; } @$modules);
524 # Filter-out generated files
525 my @to_build = grep({ my $file = $_; !scalar(grep($file eq $$_{filename}, @$generated)) } @files);
526 system_verbose($scons || $CFG::scons." ".join(" ", @to_build));
529 ### Copy files (using rsync)
530 if (defined $server && !defined($gen_only)) {
531 (my $real_server = $server) =~ s/\$NAME/$config_name/;
533 my ($hostname, $path) = split(":", $real_server, 2);
534 if (! defined $path) {
538 my $files = join(" ", map({ ($file) = m/([^ ]*)/; $file; } ( @$modules, @bootloader_configs)));
539 map({ my $file = (split)[0]; die "$file: $!" if ! -f $file; } @$modules);
540 my $istty = -t STDOUT && ($ENV{'TERM'} || 'dumb') ne 'dumb';
541 my $progress = $istty ? "--progress" : "";
542 system_verbose("rsync $progress -RLp $rsync_flags $files $real_server");
543 if ($server =~ m|/\$NAME$| && $concat) {
544 my $cmd = join("; ", map { "( cd $path/.. && cat */$_ > $_ )" } @bootloader_configs);
545 system_verbose($hostname ? "ssh $hostname '$cmd'" : $cmd);
549 ### Prepare ISO image generation
550 if (defined $iso_image) {
551 generate_configs("(cd)", $generated, $filename);
553 generate_grub_config(\$menu, $config_name, "(cd)", $modules);
554 $menu_iso .= "$menu\n";
555 map { ($file,undef) = split; $files_iso{$file} = 1; } @$modules;
559 ## Generate ISO image
560 if (defined $iso_image) {
561 open(my $fh, ">menu-iso.lst");
562 print $fh "timeout 5\n\n$menu_iso";
564 my $files = "boot/grub/menu.lst=menu-iso.lst " . join(" ", map("$_=$_", keys(%files_iso)));
565 $iso_image ||= "$config_name.iso";
566 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");
567 print("ISO image created: $builddir/$iso_image\n");
570 exit(0) if defined $gen_only;
572 ## Boot the system using various methods and send serial output to stdout
574 if (scalar(@scripts) > 1 && ( defined $dhcp_tftp || defined $serial || defined $iprelay)) {
575 die "You cannot do this with multiple scripts simultaneously";
578 if ($variables->{WVTEST_TIMEOUT}) {
579 print "wvtest: timeout ", $variables->{WVTEST_TIMEOUT}, "\n";
584 $str =~ s/^\s+|\s+$//g;
590 if (!(defined $dhcp_tftp || defined $serial || defined $iprelay || defined $server || defined $iso_image)) {
592 $qemu ||= $variables->{QEMU} || $CFG::qemu;
593 my @qemu_flags = split(" ", $qemu);
594 $qemu = shift(@qemu_flags);
596 @qemu_flags = split(/ +/, trim($variables->{QEMU_FLAGS})) if exists $variables->{QEMU_FLAGS};
597 @qemu_flags = split(/ +/, trim($qemu_flags_cmd)) if $qemu_flags_cmd;
598 push(@qemu_flags, split(/ +/, trim($qemu_append || '')));
600 if (defined $iso_image) {
601 # Boot NOVA with grub (and test the iso image)
602 push(@qemu_flags, ('-cdrom', "$config_name.iso"));
604 # Boot NOVA without GRUB
606 # Non-patched qemu doesn't like commas, but NUL can live with pluses instead of commans
607 foreach (@$modules) {s/,/+/g;}
608 generate_configs("", $generated, $filename);
610 if (scalar @$modules) {
611 my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);
612 $kcmd = '' if !defined $kcmd;
614 @$modules = map { if (/\.dtb$/) { $dtb=$_; (); } else { $_ } } @$modules;
615 my $initrd = join ",", @$modules;
617 push(@qemu_flags, ('-kernel', $kbin, '-append', $kcmd));
618 push(@qemu_flags, ('-initrd', $initrd)) if $initrd;
619 push(@qemu_flags, ('-dtb', $dtb)) if $dtb;
622 push(@qemu_flags, qw(-serial stdio)); # Redirect serial output (for collecting test restuls)
623 unshift(@qemu_flags, ('-name', $config_name));
624 print "novaboot: Running: ".shell_cmd_string($qemu, @qemu_flags)."\n";
625 $exp->spawn(($qemu, @qemu_flags)) || die("exec() failed: $!");
628 ### Local DHCPD and TFTPD
630 my ($dhcpd_pid, $tftpd_pid);
632 if (defined $dhcp_tftp)
634 generate_configs("(nd)", $generated, $filename);
635 system_verbose('mkdir -p tftpboot');
636 generate_grub_config("tftpboot/os-menu.lst", $config_name, "(nd)", \@$modules, "timeout 0");
637 open(my $fh, '>', 'dhcpd.conf');
638 my $mac = `cat /sys/class/net/eth0/address`;
640 print $fh "subnet 10.23.23.0 netmask 255.255.255.0 {
641 range 10.23.23.10 10.23.23.100;
642 filename \"bin/boot/grub/pxegrub.pxe\";
643 next-server 10.23.23.1;
646 hardware ethernet $mac;
647 fixed-address 10.23.23.1;
650 system_verbose("sudo ip a add 10.23.23.1/24 dev eth0;
651 sudo ip l set dev eth0 up;
652 sudo touch dhcpd.leases");
655 if ($dhcpd_pid == 0) {
656 # This way, the spawned server are killed when this script is killed.
657 exec_verbose("sudo dhcpd -d -cf dhcpd.conf -lf dhcpd.leases -pf dhcpd.pid");
660 if ($tftpd_pid == 0) {
661 exec_verbose("sudo in.tftpd --foreground --secure -v -v -v $builddir");
663 $SIG{TERM} = sub { print "CHILDS KILLED\n"; kill 15, $dhcpd_pid, $tftpd_pid; };
666 ### Serial line or IP relay
668 if (defined $target_reset) {
669 print "novaboot: Reseting the test box... ";
675 # Serial line of the target is available
676 print "novaboot: Serial line interaction (press Ctrl-C to interrupt)...\n";
678 $exp->interact(undef, "\cC"); # Interact until Ctrl-C is pressed
681 ### Wait for dhcpc or tftpd
682 if (defined $dhcp_tftp) {
683 kill 15, $dhcpd_pid, $tftpd_pid;
685 if ($pid == $dhcpd_pid) { print "dhcpd exited!\n"; }
686 elsif ($pid == $tftpd_pid) { print "tftpd exited!\n"; }
687 else { print "wait returned: $pid\n"; }
688 kill(15, 0); # Kill current process group i.e. all remaining children
695 novaboot - A tool for booting various operating systems on various hardware or in qemu
699 B<novaboot> [ options ] [--] script...
701 B<./script> [ options ]
707 This program makes it easier to boot NOVA or other operating system
708 (OS) in different environments. It reads a so called novaboot script
709 and uses it either to boot the OS in an emulator (e.g. in qemu) or to
710 generate the configuration for a specific bootloader and optionally to
711 copy the necessary binaries and other needed files to proper
712 locations, perhaps on a remote server. In case the system is actually
713 booted, its serial output is redirected to standard output if that is
716 A typical way of using novaboot is to make the novaboot script
717 executable and set its first line to I<#!/usr/bin/env novaboot>. Then,
718 booting a particular OS configuration becomes the same as executing a
719 local program - the novaboot script.
721 With C<novaboot> you can:
727 Run an OS in Qemu. This is the default action when no other action is
728 specified by command line switches. Thus running C<novaboot ./script>
729 (or C<./script> as described above) will run Qemu and make it boot the
730 configuration specified in the I<script>.
734 Create a bootloader configuration file (currently supported
735 bootloaders are GRUB, GRUB2 and Pulsar) and copy it with all other
736 files needed for booting to another, perhaps remote, location.
738 ./script --server --iprelay=192.168.1.2
740 This command copies files to a TFTP server specified in the
741 configuration file and uses TCP/IP-controlled relay to reset the test
742 box and receive its serial output.
746 Run DHCP and TFTP server on developer's machine to PXE-boot NOVA from
751 When a PXE-bootable machine is connected via Ethernet to developer's
752 machine, it will boot the configuration described in I<script>.
756 Create bootable ISO images. E.g.
758 novaboot --iso -- script1 script2
760 The created ISO image will have GRUB bootloader installed on it and
761 the boot menu will allow selecting between I<script1> and I<script2>
766 =head1 PHASES AND OPTIONS
768 Novaboot performs its work in several phases. Each phase can be
769 influenced by several options, certain phases can be skipped. The list
770 of phases (in the execution order) and the corresponding options
773 =head2 Configuration reading phase
775 After starting, novaboot reads configuration files. By default, it
776 searches for files named F<.novaboot> starting from the directory of
777 the novaboot script (or working directory, see bellow) and continuing
778 upwards up to the root directory. The configuration files are read in
779 order from the root directory downwards with latter files overriding
780 settings from the former ones.
782 In certain cases, the location of the novaboot script cannot be
783 determined in this early phase. This happens either when the script is
784 read from the standard input or when novaboot is invoked explicitly
785 and options precede the script name, as in the example L</"4."> above.
786 In this case the current working directory is used as a starting point
787 for configuration file search.
791 =item -c, --config=<filename>
793 Use the specified configuration file instead of the default one(s).
797 =head2 Command line processing phase
803 Dump the current configuration to stdout end exits. Useful as an
804 initial template for a configuration file.
808 Print short (B<-h>) or long (B<--help>) help.
810 =item -t, --target=<target>
812 This option serves as a user configurable shortcut for other novaboot
813 options. The effect of this option is the same as the options stored
814 in the C<%targets> configuration variable under key I<target>. See
815 also L</"CONFIGURATION FILE">.
819 =head2 Script preprocessing phase
821 This phases allows to modify the parsed novaboot script before it is
822 used in the later phases.
826 =item -a, --append=<parameters>
828 Appends a string to the first "filename" line in the novaboot script.
829 This can be used to append parameters to the kernel's or root task's
834 Use F<bender> chainloader. Bender scans the PCI bus for PCI serial
835 ports and stores the information about them in the BIOS data area for
838 =item --chainloader=<chainloader>
840 Chainloader that is loaded before the kernel and other files specified
841 in the novaboot script. E.g. 'bin/boot/bender promisc'.
845 Prints the content of the novaboot script after removing comments and
846 evaluating all I<--scriptmod> expressions. Exit after reading (and
849 =item --scriptmod=I<perl expression>
851 When novaboot script is read, I<perl expression> is executed for every
852 line (in $_ variable). For example, C<novaboot
853 --scriptmod=s/sigma0/omega6/g> replaces every occurrence of I<sigma0>
854 in the script with I<omega6>.
856 When this option is present, it overrides I<$script_modifier> variable
857 from the configuration file, which has the same effect. If this option
858 is given multiple times all expressions are evaluated in the command
863 Strip I<rom://> prefix from command lines and generated config files.
864 The I<rom://> prefix is used by NUL. For NRE, it has to be stripped.
868 =head2 File generation phase
870 In this phase, files needed for booting are generated in a so called
871 I<build directory> (see TODO). In most cases configuration for a
872 bootloader is generated automatically by novaboot. It is also possible
873 to generate other files using I<heredoc> or I<"<"> syntax in novaboot
874 scripts. Finally, binaries can be generated in this phases by running
879 =item --build-dir=<directory>
881 Overrides the default build directory location.
883 The default build directory location is determined as follows: If the
884 configuration file defines the C<$builddir> variable, its value is
885 used. Otherwise, it is the directory that contains the first processed
888 =item -g, --grub[=I<filename>]
890 Generates grub bootloader menu file. If the I<filename> is not
891 specified, F<menu.lst> is used. The I<filename> is relative to the
892 build directory (see B<--build-dir>).
894 =item --grub-preamble=I<prefix>
896 Specifies the I<preable> that is at the beginning of the generated
897 GRUB or GRUB2 config files. This is useful for specifying GRUB's
900 =item --grub-prefix=I<prefix>
902 Specifies I<prefix> that is put in front of every file name in GRUB's
903 F<menu.lst>. The default value is the absolute path to the build directory.
905 If the I<prefix> contains string $NAME, it will be replaced with the
906 name of the novaboot script (see also B<--name>).
908 =item --grub2[=I<filename>]
910 Generate GRUB2 menuentry in I<filename>. If I<filename> is not
911 specified F<grub.cfg> is used. The content of the menuentry can be
912 customized with B<--grub-preable>, B<--grub2-prolog> or
913 B<--grub_prefix> options.
915 In order to use the the generated menuentry on your development
916 machine that uses GRUB2, append the following snippet to
917 F</etc/grub.d/40_custom> file and regenerate your grub configuration,
918 i.e. run update-grub on Debian/Ubuntu.
920 if [ -f /path/to/nul/build/grub.cfg ]; then
921 source /path/to/nul/build/grub.cfg
924 =item --grub2-prolog=I<prolog>
926 Specifies text I<preable> that is put at the begiging of the entry
929 =item --name=I<string>
931 Use the name I<string> instead of the name of the novaboot script.
932 This name is used for things like a title of grub menu or for the
933 server directory where the boot files are copied to.
937 Do not generate files on the fly (i.e. "<" syntax) except for the
938 files generated via "<<WORD" syntax.
940 =item -p, --pulsar[=mac]
942 Generates pulsar bootloader configuration file named F<config-I<mac>>
943 The I<mac> string is typically a MAC address and defaults to
946 =item --scons[=scons command]
948 Runs I<scons> to build files that are not generated by novaboot
953 Exit novaboot after file generation phase.
957 =head2 Target connection check
959 If supported by the target, the connection to it is made and it is
960 checked whether the target is not occupied by another novaboot
963 =head2 File deployment phase
965 In some setups, it is necessary to copy the files needed for booting
966 to a particular location, e.g. to a TFTP boot server or to the
971 =item -d, --dhcp-tftp
973 Turns your workstation into a DHCP and TFTP server so that NOVA
974 can be booted via PXE BIOS on a test machine directly connected by
975 a plain Ethernet cable to your workstation.
977 The DHCP and TFTP servers require root privileges and C<novaboot>
978 uses C<sudo> command to obtain those. You can put the following to
979 I</etc/sudoers> to allow running the necessary commands without
982 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
983 your_login ALL=NOPASSWD: NOVABOOT
985 =item -i, --iso[=filename]
987 Generates the ISO image that boots NOVA system via GRUB. If no filename
988 is given, the image is stored under I<NAME>.iso, where I<NAME> is the name
989 of the novaboot script (see also B<--name>).
991 =item --server[=[[user@]server:]path]
993 Copy all files needed for booting to another location (implies B<-g>
994 unless B<--grub2> is given). The files will be copied (by B<rsync>
995 tool) to the directory I<path>. If the I<path> contains string $NAME,
996 it will be replaced with the name of the novaboot script (see also
1001 If B<--server> is used and its value ends with $NAME, then after
1002 copying the files, a new bootloader configuration file (e.g. menu.lst)
1003 is created at I<path-wo-name>, i.e. the path specified by B<--server>
1004 with $NAME part removed. The content of the file is created by
1005 concatenating all files of the same name from all subdirectories of
1006 I<path-wo-name> found on the "server".
1008 =item --rsync-flags=I<flags>
1010 Specifies which I<flags> are appended to F<rsync> command line when
1011 copying files as a result of I<--server> option.
1015 =head2 Target power-on and reset phase
1019 =item --iprelay=I<addr[:port]>
1021 Use IP relay to reset the machine and to get the serial output. The IP
1022 address of the relay is given by I<addr> parameter.
1024 Note: This option is expected to work with HWG-ER02a IP relays.
1028 Switch on/off the target machine. Currently works only with
1031 =item -Q, --qemu=I<qemu-binary>
1033 The name of qemu binary to use. The default is 'qemu'.
1035 =item --qemu-append=I<flags>
1037 Append I<flags> to the default qemu flags (QEMU_FLAGS variable or
1038 C<-cpu coreduo -smp 2>).
1040 =item -q, --qemu-flags=I<flags>
1042 Replace the default qemu flags (QEMU_FLAGS variable or C<-cpu coreduo
1043 -smp 2>) with I<flags> specified here.
1047 =head2 Interaction with the bootloader on the target
1049 See B<--serial>. There will be new options soon.
1051 =head2 Target's output reception phase
1055 =item -s, --serial[=device]
1057 Use serial line to control GRUB bootloader and to see the output
1058 serial output of the machine. The default value is F</dev/ttyUSB0>.
1060 =item --stty=<settings>
1062 Specifies settings passed to C<stty> invoked on the serial line
1063 specified with B<--serial>. If this option is not given, C<stty> is
1064 called with C<raw -crtscts -onlcr 115200> settings.
1068 See also B<--iprelay>.
1070 =head2 Termination phase
1072 Daemons that were spwned (F<dhcpd> and F<tftpd>) are killed here.
1074 =head1 NOVABOOT SCRIPT SYNTAX
1076 The syntax tries to mimic POSIX shell syntax. The syntax is defined with the following rules.
1078 Lines starting with "#" are ignored.
1080 Lines that end with "\" are concatenated with the following line after
1081 removal of the final "\" and leading whitespace of the following line.
1083 Lines in the form I<VARIABLE=...> (i.e. matching '^[A-Z_]+=' regular
1084 expression) assign values to internal variables. See VARIABLES
1087 Otherwise, the first word on the line represents the filename
1088 (relative to the build directory (see B<--build-dir>) of the module to
1089 load and the remaining words are passed as the command line
1092 When the line ends with "<<WORD" then the subsequent lines until the
1093 line containing only WORD are copied literally to the file named on
1096 When the line ends with "< CMD" the command CMD is executed with
1097 C</bin/sh> and its standard output is stored in the file named on that
1098 line. The SRCDIR variable in CMD's environment is set to the absolute
1099 path of the directory containing the interpreted novaboot script.
1102 #!/usr/bin/env novaboot
1103 WVDESC=Example program
1104 bin/apps/sigma0.nul S0_DEFAULT script_start:1,1 \
1105 verbose hostkeyb:0,0x60,1,12,2
1107 hello.nulconfig <<EOF
1108 sigma0::mem:16 name::/s0/log name::/s0/timer name::/s0/fs/rom ||
1109 rom://bin/apps/hello.nul
1112 This example will load three modules: sigma0.nul, hello.nul and
1113 hello.nulconfig. sigma0 gets some command line parameters and
1114 hello.nulconfig file is generated on the fly from the lines between
1119 The following variables are interpreted in the novaboot script:
1125 Novaboot chdir()s to this directory before file generation phase. The
1126 directory name specified here is relative to the build directory
1127 specified by other means (see L</--build-dir>).
1131 Description of the wvtest-compliant program.
1133 =item WVTEST_TIMEOUT
1135 The timeout in seconds for WvTest harness. If no complete line appears
1136 in the test output within the time specified here, the test fails. It
1137 is necessary to specify this for long running tests that produce no
1138 intermediate output.
1142 Use a specific qemu binary (can be overriden with B<-Q>) and flags
1143 when booting this script under qemu. If QEMU_FLAGS variable is also
1144 specified flags specified in QEMU variable are replaced by those in
1149 Use specific qemu flags (can be overriden with B<-q>).
1151 =item HYPERVISOR_PARAMS
1153 Parameters passed to hypervisor. The default value is "serial", unless
1154 overriden in configuration file.
1158 The kernel to use instead of NOVA hypervisor specified in the
1159 configuration file. The value should contain the name of the kernel
1160 image as well as its command line parameters. If this variable is
1161 defined and non-empty, the variable HYPERVISOR_PARAMS is not used.
1165 =head1 CONFIGURATION FILE
1167 Novaboot can read its configuration from a file. Configuration file
1168 was necessary in early days of novaboot. Nowadays, an attempt is made
1169 to not use the configuration file because it makes certain novaboot
1170 scripts unusable on systems without (or with different) configuration
1171 file. The only recommended use of the configuration file is to specify
1172 custom_options (see bellow).
1174 If you decide to use the configuration file, it is looked up, by
1175 default, in files named F<.novaboot> as described in L</Configuration
1176 reading phase>. Alternatively, its location can be specified with the
1177 B<-c> switch or with the NOVABOOT_CONFIG environment variable. The
1178 configuration file has perl syntax and should set values of certain
1179 Perl variables. The current configuration can be dumped with the
1180 B<--dump-config> switch. Some configuration variables can be overriden
1181 by environment variables (see below) or by command line switches.
1183 Documentation of some configuration variables follows:
1189 Build directory location relative to the location of the configuration
1194 Hash of shortcuts to be used with the B<--target> option. If the hash
1195 contains, for instance, the following pair of values
1197 'mybox' => '--server=boot:/tftproot --serial=/dev/ttyUSB0 --grub',
1199 then the following two commands are equivalent:
1201 ./script --server=boot:/tftproot --serial=/dev/ttyUSB0 --grub
1206 =head1 ENVIRONMENT VARIABLES
1208 Some options can be specified not only via config file or command line
1209 but also through environment variables. Environment variables override
1210 the values from configuration file and command line parameters
1211 override the environment variables.
1215 =item NOVABOOT_CONFIG
1217 Name of the novaboot configuration file to use instead of the default
1220 =item NOVABOOT_BENDER
1222 Defining this variable has the same meaning as B<--bender> option.
1228 Michal Sojka <sojka@os.inf.tu-dresden.de>