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, $remote_cmd, $reset_cmd, $rom_prefix, $rsync_flags, @scriptmod, $scons, $serial, $server, $stty, $uboot, $uboot_init);
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 "remote-cmd=s" => \$remote_cmd,
126 "reset-cmd=s" => \$reset_cmd,
127 "rsync-flags=s" => \$rsync_flags,
128 "scons:s" => \$scons,
129 "scriptmod=s" => \@scriptmod,
130 "serial|s:s" => \$serial,
131 "server:s" => \$server,
132 "strip-rom" => sub { $rom_prefix = ''; },
134 "target|t=s" => sub { my ($opt_name, $opt_value) = @_;
135 exists $CFG::targets{$opt_value} or die("Unknown target '$opt_value' (valid targets are: ".join(", ", sort keys(%CFG::targets)).")");
136 GetOptionsFromString($CFG::targets{$opt_value}, %opt_spec); },
141 GetOptions %opt_spec or pod2usage(2);
142 pod2usage(1) if $help;
143 pod2usage(-exitstatus => 0, -verbose => 2) if $man;
145 ### Dump sanitized configuration (if requested)
149 $Data::Dumper::Indent=1;
150 print "# This file is in perl syntax.\n";
151 foreach my $key(sort(keys(%CFG::))) { # See "Symbol Tables" in perlmod(1)
152 if (defined ${$CFG::{$key}}) { print Data::Dumper->Dump([${$CFG::{$key}}], ["*$key"]); }
153 if ( @{$CFG::{$key}}) { print Data::Dumper->Dump([\@{$CFG::{$key}}], ["*$key"]); }
154 if ( %{$CFG::{$key}}) { print Data::Dumper->Dump([\%{$CFG::{$key}}], ["*$key"]); }
160 ### Sanitize configuration
162 if (defined $config_name_opt && scalar(@ARGV) > 1) { die "You cannot use --name with multiple scripts"; }
165 if (defined $serial) { $serial ||= "/dev/ttyUSB0"; }
166 if (defined $grub_config) { $grub_config ||= "menu.lst"; }
167 if (defined $grub2_config) { $grub2_config ||= "grub.cfg"; }
169 ## Parse the novaboot script(s)
175 my ($modules, $variables, $generated, $continuation);
177 if ($ARGV ne $last_fn) { # New script
178 die "Missing EOF in $last_fn" if $file;
179 die "Unfinished line in $last_fn" if $line;
181 push @scripts, { 'filename' => $ARGV,
182 'modules' => $modules = [],
183 'variables' => $variables = {},
184 'generated' => $generated = []};
188 next if /^#/ || /^\s*$/; # Skip comments and empty lines
190 foreach my $mod(@scriptmod) { eval $mod; }
192 print "$_\n" if $dump_opt;
194 if (/^([A-Z_]+)=(.*)$/) { # Internal variable
195 $$variables{$1} = $2;
198 if (/^([^ ]*)(.*?)[[:space:]]*<<([^ ]*)$/) { # Heredoc start
199 push @$modules, "$1$2";
201 push @$generated, {filename => $1, content => $file};
205 if ($file && $_ eq $EOF) { # Heredoc end
209 if ($file) { # Heredoc content
210 push @{$file}, "$_\n";
213 $_ =~ s/^[[:space:]]*// if ($continuation);
214 if (/\\$/) { # Line continuation
215 $line .= substr($_, 0, length($_)-1);
221 $line .= " $append" if ($append && scalar(@$modules) == 0);
223 if ($line =~ /^([^ ]*)(.*?)[[:space:]]*< ?(.*)$/) { # Command substitution
224 push @$modules, "$1$2";
225 push @$generated, {filename => $1, command => $3};
229 push @$modules, $line;
233 #print Dumper(\@scripts);
239 sub generate_configs($$$) {
240 my ($base, $generated, $filename) = @_;
241 if ($base) { $base = "$base/"; };
242 foreach my $g(@$generated) {
243 if (exists $$g{content}) {
244 my $config = $$g{content};
245 my $fn = $$g{filename};
246 open(my $f, '>', $fn) || die("$fn: $!");
247 map { s|\brom://([^ ]*)|$rom_prefix$base$1|g; print $f "$_"; } @{$config};
249 print "novaboot: Created $fn\n";
250 } elsif (exists $$g{command} && ! $no_file_gen) {
251 $ENV{SRCDIR} = dirname(File::Spec->rel2abs( $filename, $invocation_dir ));
252 system_verbose("( $$g{command} ) > $$g{filename}");
257 sub generate_grub_config($$$$;$)
259 my ($filename, $title, $base, $modules_ref, $preamble) = @_;
260 if ($base) { $base = "$base/"; };
261 open(my $fg, '>', $filename) or die "$filename: $!";
262 print $fg "$preamble\n" if $preamble;
263 print $fg "title $title\n" if $title;
264 #print $fg "root $base\n"; # root doesn't really work for (nd)
266 foreach (@$modules_ref) {
269 my ($kbin, $kcmd) = split(' ', $_, 2);
270 $kcmd = '' if !defined $kcmd;
271 print $fg "kernel ${base}$kbin $kcmd\n";
273 s|\brom://([^ ]*)|$rom_prefix$base$1|g; # Translate rom:// files - needed for vdisk parameter of sigma0
274 print $fg "module $base$_\n";
278 print("novaboot: Created $builddir/$filename\n");
282 sub generate_grub2_config($$$$;$$)
284 my ($filename, $title, $base, $modules_ref, $preamble, $prolog) = @_;
285 if ($base && substr($base,-1,1) ne '/') { $base = "$base/"; };
286 open(my $fg, '>', $filename) or die "$filename: $!";
287 print $fg "$preamble\n" if $preamble;
288 $title ||= 'novaboot';
289 print $fg "menuentry $title {\n";
290 print $fg "$prolog\n" if $prolog;
292 foreach (@$modules_ref) {
295 my ($kbin, $kcmd) = split(' ', $_, 2);
296 $kcmd = '' if !defined $kcmd;
297 print $fg " multiboot ${base}$kbin $kcmd\n";
300 # GRUB2 doesn't pass filename in multiboot info so we have to duplicate it here
301 $_ = join(' ', ($args[0], @args));
302 s|\brom://|$rom_prefix|g; # We do not need to translate path for GRUB2
303 print $fg " module $base$_\n";
308 print("novaboot: Created $builddir/$filename\n");
312 sub generate_pulsar_config($$)
314 my ($filename, $modules_ref) = @_;
315 open(my $fg, '>', $filename) or die "$filename: $!";
316 print $fg "root $pulsar_root\n" if defined $pulsar_root;
319 foreach (@$modules_ref) {
322 ($kbin, $kcmd) = split(' ', $_, 2);
323 $kcmd = '' if !defined $kcmd;
326 s|\brom://|$rom_prefix|g;
327 print $fg "load $_\n";
330 # Put kernel as last - this is needed for booting Linux and has no influence on non-Linux OSes
331 print $fg "exec $kbin $kcmd\n";
333 print("novaboot: Created $builddir/$filename\n");
337 sub shell_cmd_string(@)
339 return join(' ', map((/^[-_=a-zA-Z0-9\/\.\+]+$/ ? "$_" : "'$_'"), @_));
344 print "novaboot: Running: ".shell_cmd_string(@_)."\n";
348 sub system_verbose($)
351 print "novaboot: Running: $cmd\n";
352 my $ret = system($cmd);
353 if ($ret & 0x007f) { die("Command terminated by a signal"); }
354 if ($ret & 0xff00) {die("Command exit with non-zero exit code"); }
355 if ($ret) { die("Command failure $ret"); }
360 if (exists $variables->{WVDESC}) {
361 print "Testing \"$variables->{WVDESC}\" in $last_fn:\n";
362 } elsif ($last_fn =~ /\.wv$/) {
363 print "Testing \"all\" in $last_fn:\n";
366 ## Connect to the target and check whether is not occupied
368 # We have to do this before file generation phase, because file
369 # generation is intermixed with file deployment phase and we want to
370 # check whether the target is not used by somebody else before
371 # deploying files. Otherwise, we may rewrite other user's files on a
374 my $exp; # Expect object to communicate with the target over serial line
376 my ($target_reset, $target_power_on, $target_power_off);
378 if (defined $iprelay) {
380 $iprelay =~ /([.0-9]+)(:([0-9]+))?/;
383 my $paddr = sockaddr_in($port, inet_aton($addr));
384 my $proto = getprotobyname('tcp');
385 socket($IPRELAY, PF_INET, SOCK_STREAM, $proto) || die "socket: $!";
386 print "novaboot: Connecting to IP relay... ";
387 connect($IPRELAY, $paddr) || die "connect: $!";
389 $exp = Expect->init(\*$IPRELAY);
393 print $exp "\xFF\xF6"; # AYT
394 my $connected = $exp->expect(20, # Timeout in seconds
395 '<iprelayd: connected>',
396 '-re', '<WEB51 HW[^>]*>');
401 my ($relay, $onoff) = @_;
402 die unless ($relay == 1 || $relay == 2);
404 my $cmd = ($relay == 1 ? 0x5 : 0x6) | ($onoff ? 0x20 : 0x10);
405 return "\xFF\xFA\x2C\x32".chr($cmd)."\xFF\xF0";
409 my ($relay, $onoff) = @_;
410 die unless ($relay == 1 || $relay == 2);
411 my $cmd = ($relay == 1 ? 0xdf : 0xbf) | ($onoff ? 0x00 : 0xff);
412 return "\xFF\xFA\x2C\x97".chr($cmd)."\xFF\xF0";
416 my ($relay, $onoff, $can_giveup) = @_;
417 my $confirmation = '';
419 print $exp relaycmd($relay, $onoff);
420 my $confirmed = $exp->expect(20, # Timeout in seconds
421 relayconf($relay, $onoff));
424 print("Relay confirmation timeout - ignoring\n");
426 die "Relay confirmation timeout";
432 $target_reset = sub {
433 relay(2, 1, 1); # Reset the machine
438 $target_power_off = sub {
439 relay(1, 1); # Press power button
440 usleep(6000000); # Long press to switch off
444 $target_power_on = sub {
445 relay(1, 1); # Press power button
446 usleep(100000); # Short press
452 system_verbose("stty -F $serial $stty");
453 open($CONN, "+<", $serial) || die "open $serial: $!";
454 $exp = Expect->init(\*$CONN);
456 $exp = new Expect(); # Make $exp ready for calling $exp->spawn() later
459 print "novaboot: Running: $remote_cmd\n";
460 $exp->spawn($remote_cmd);
464 if (defined $reset_cmd) {
465 $target_reset = sub {
466 system_verbose($reset_cmd);
470 if (defined $on_opt && defined $target_power_on) {
474 if (defined $off_opt && defined $target_power_off) {
475 print "novaboot: Switching the target off...\n";
476 &$target_power_off();
480 $builddir ||= dirname(File::Spec->rel2abs( ${$scripts[0]}{filename})) if scalar @scripts;
481 if (defined $builddir) {
482 chdir($builddir) or die "Can't change directory to $builddir: $!";
483 print "novaboot: Entering directory `$builddir'\n";
486 ## File generation phase
487 my (%files_iso, $menu_iso, $filename);
488 my $config_name = '';
490 foreach my $script (@scripts) {
491 $filename = $$script{filename};
492 $modules = $$script{modules};
493 $generated = $$script{generated};
494 $variables = $$script{variables};
496 ($config_name = $filename) =~ s#.*/##;
497 $config_name = $config_name_opt if (defined $config_name_opt);
499 if (exists $variables->{BUILDDIR}) {
500 $builddir = File::Spec->rel2abs($variables->{BUILDDIR});
501 chdir($builddir) or die "Can't change directory to $builddir: $!";
502 print "novaboot: Entering directory `$builddir'\n";
506 if (exists $variables->{KERNEL}) {
507 $kernel = $variables->{KERNEL};
509 if ($CFG::hypervisor) {
510 $kernel = $CFG::hypervisor . " ";
511 if (exists $variables->{HYPERVISOR_PARAMS}) {
512 $kernel .= $variables->{HYPERVISOR_PARAMS};
514 $kernel .= $CFG::hypervisor_params;
518 @$modules = ($kernel, @$modules) if $kernel;
519 @$modules = (@chainloaders, @$modules);
520 @$modules = ("bin/boot/bender", @$modules) if ($bender || defined $ENV{'NOVABOOT_BENDER'});
523 ($prefix = $grub_prefix) =~ s/\$NAME/$config_name/ if defined $grub_prefix;
524 $prefix ||= $builddir;
525 # TODO: use $grub_prefix as first parameter if some switch is given
526 generate_configs('', $generated, $filename);
528 ### Generate bootloader configuration files
529 my @bootloader_configs;
530 push @bootloader_configs, generate_grub_config($grub_config, $config_name, $prefix, $modules, $grub_preamble) if (defined $grub_config);
531 push @bootloader_configs, generate_grub2_config($grub2_config, $config_name, $prefix, $modules, $grub_preamble, $grub2_prolog) if (defined $grub2_config);
532 push @bootloader_configs, generate_pulsar_config('config-'.($pulsar||'novaboot'), $modules) if (defined $pulsar);
535 if (defined $scons) {
536 my @files = map({ ($file) = m/([^ ]*)/; $file; } @$modules);
537 # Filter-out generated files
538 my @to_build = grep({ my $file = $_; !scalar(grep($file eq $$_{filename}, @$generated)) } @files);
539 system_verbose($scons || $CFG::scons." ".join(" ", @to_build));
542 ### Copy files (using rsync)
543 if (defined $server && !defined($gen_only)) {
544 (my $real_server = $server) =~ s/\$NAME/$config_name/;
546 my ($hostname, $path) = split(":", $real_server, 2);
547 if (! defined $path) {
551 my $files = join(" ", map({ ($file) = m/([^ ]*)/; $file; } ( @$modules, @bootloader_configs)));
552 map({ my $file = (split)[0]; die "$file: $!" if ! -f $file; } @$modules);
553 my $istty = -t STDOUT && ($ENV{'TERM'} || 'dumb') ne 'dumb';
554 my $progress = $istty ? "--progress" : "";
555 system_verbose("rsync $progress -RLp $rsync_flags $files $real_server");
556 if ($server =~ m|/\$NAME$| && $concat) {
557 my $cmd = join("; ", map { "( cd $path/.. && cat */$_ > $_ )" } @bootloader_configs);
558 system_verbose($hostname ? "ssh $hostname '$cmd'" : $cmd);
562 ### Prepare ISO image generation
563 if (defined $iso_image) {
564 generate_configs("(cd)", $generated, $filename);
566 generate_grub_config(\$menu, $config_name, "(cd)", $modules);
567 $menu_iso .= "$menu\n";
568 map { ($file,undef) = split; $files_iso{$file} = 1; } @$modules;
572 ## Generate ISO image
573 if (defined $iso_image) {
574 open(my $fh, ">menu-iso.lst");
575 print $fh "timeout 5\n\n$menu_iso";
577 my $files = "boot/grub/menu.lst=menu-iso.lst " . join(" ", map("$_=$_", keys(%files_iso)));
578 $iso_image ||= "$config_name.iso";
579 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");
580 print("ISO image created: $builddir/$iso_image\n");
583 exit(0) if defined $gen_only;
585 ## Boot the system using various methods and send serial output to stdout
587 if (scalar(@scripts) > 1 && ( defined $dhcp_tftp || defined $serial || defined $iprelay)) {
588 die "You cannot do this with multiple scripts simultaneously";
591 if ($variables->{WVTEST_TIMEOUT}) {
592 print "wvtest: timeout ", $variables->{WVTEST_TIMEOUT}, "\n";
597 $str =~ s/^\s+|\s+$//g;
603 if (!(defined $dhcp_tftp || defined $serial || defined $iprelay || defined $server || defined $iso_image)) {
605 $qemu ||= $variables->{QEMU} || $CFG::qemu;
606 my @qemu_flags = split(" ", $qemu);
607 $qemu = shift(@qemu_flags);
609 @qemu_flags = split(/ +/, trim($variables->{QEMU_FLAGS})) if exists $variables->{QEMU_FLAGS};
610 @qemu_flags = split(/ +/, trim($qemu_flags_cmd)) if $qemu_flags_cmd;
611 push(@qemu_flags, split(/ +/, trim($qemu_append || '')));
613 if (defined $iso_image) {
614 # Boot NOVA with grub (and test the iso image)
615 push(@qemu_flags, ('-cdrom', "$config_name.iso"));
617 # Boot NOVA without GRUB
619 # Non-patched qemu doesn't like commas, but NUL can live with pluses instead of commans
620 foreach (@$modules) {s/,/+/g;}
621 generate_configs("", $generated, $filename);
623 if (scalar @$modules) {
624 my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);
625 $kcmd = '' if !defined $kcmd;
627 @$modules = map { if (/\.dtb$/) { $dtb=$_; (); } else { $_ } } @$modules;
628 my $initrd = join ",", @$modules;
630 push(@qemu_flags, ('-kernel', $kbin, '-append', $kcmd));
631 push(@qemu_flags, ('-initrd', $initrd)) if $initrd;
632 push(@qemu_flags, ('-dtb', $dtb)) if $dtb;
635 push(@qemu_flags, qw(-serial stdio)); # Redirect serial output (for collecting test restuls)
636 unshift(@qemu_flags, ('-name', $config_name));
637 print "novaboot: Running: ".shell_cmd_string($qemu, @qemu_flags)."\n";
638 $exp->spawn(($qemu, @qemu_flags)) || die("exec() failed: $!");
641 ### Local DHCPD and TFTPD
643 my ($dhcpd_pid, $tftpd_pid);
645 if (defined $dhcp_tftp)
647 generate_configs("(nd)", $generated, $filename);
648 system_verbose('mkdir -p tftpboot');
649 generate_grub_config("tftpboot/os-menu.lst", $config_name, "(nd)", \@$modules, "timeout 0");
650 open(my $fh, '>', 'dhcpd.conf');
651 my $mac = `cat /sys/class/net/eth0/address`;
653 print $fh "subnet 10.23.23.0 netmask 255.255.255.0 {
654 range 10.23.23.10 10.23.23.100;
655 filename \"bin/boot/grub/pxegrub.pxe\";
656 next-server 10.23.23.1;
659 hardware ethernet $mac;
660 fixed-address 10.23.23.1;
663 system_verbose("sudo ip a add 10.23.23.1/24 dev eth0;
664 sudo ip l set dev eth0 up;
665 sudo touch dhcpd.leases");
667 # We run servers by forking ourselves, because the servers end up
668 # in our process group and get killed by signals sent to the
669 # process group (e.g. Ctrl-C on terminal).
671 exec_verbose("sudo dhcpd -d -cf dhcpd.conf -lf dhcpd.leases -pf dhcpd.pid") if ($dhcpd_pid == 0);
673 exec_verbose("sudo in.tftpd --foreground --secure -v -v -v --pidfile tftpd.pid $builddir") if ($tftpd_pid == 0);
675 # Kill server when we die
676 $SIG{__DIE__} = sub { system_verbose('sudo pkill --pidfile=dhcpd.pid');
677 system_verbose('sudo pkill --pidfile=tftpd.pid'); };
680 ### Serial line or IP relay
682 if (defined $target_reset) {
683 print "novaboot: Reseting the test box... ";
688 if (defined $uboot) {
689 print "novaboot: Waiting for uBoot prompt...\n";
692 [qr/Hit any key to stop autoboot:/, sub { $exp->send('.'); exp_continue; }],
693 '=> ') || die "No uBoot prompt deteceted";
695 $exp->send("dhcp\n");
696 $exp->expect(1, '=> ');
698 my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);
700 @$modules = map { if (/\.dtb$/) { $dtb=$_; (); } else { $_ } } @$modules;
701 my $initrd = shift @$modules;
703 my $kern_addr = '800000';
704 my $initrd_addr = '-';
708 $exp->send("tftp $kern_addr $kbin\n");
710 [qr/#/, sub { exp_continue; }],
711 '=> ') || die "Kernel load failed";
713 $dtb_addr = '7f0000';
714 $exp->send("tftp $dtb_addr $dtb\n");
716 [qr/#/, sub { exp_continue; }],
717 '=> ') || die "Device tree load failed";
719 if (defined $initrd) {
720 $initrd_addr = 'b00000';
721 $exp->send("tftp $initrd_addr $initrd\n");
723 [qr/#/, sub { exp_continue; }],
724 '=> ') || die "Initrd load failed";
726 $exp->send("mw f0000b00 \${psc_cfg}\n");
727 $exp->send("set bootargs $kcmd\n");
728 $exp->expect(1, '=> ');
729 $exp->send("bootm $kern_addr $initrd_addr $dtb_addr\n");
733 # Serial line of the target is available
734 print "novaboot: Serial line interaction (press Ctrl-C to interrupt)...\n";
737 if (-t STDIN) { # Set up bi-directional communication if we run on terminal
738 my $infile = new IO::File;
739 $infile->IO::File::fdopen(*STDIN,'r');
740 my $in_object = Expect->exp_init($infile);
741 $in_object->set_group($exp);
742 #$in_object->set_seq("\cC",undef);
744 # I'm not sure when to use raw mode and when not. With
745 # --dhcp-tftp, I want the output of daemons to be normally
746 # formated (no raw mode). On the other hand, I don't want
747 # input for qemu to be echoed. Need to think more about this.
748 $in_object->manual_stty(1);
749 push(@inputs, $in_object);
751 Expect::interconnect(@inputs);
754 ## Kill dhcpc or tftpd
755 if (defined $dhcp_tftp) {
756 die("novaboot: This should kill servers on background\n");
763 novaboot - A tool for booting various operating systems on various hardware or in qemu
767 B<novaboot> [ options ] [--] script...
769 B<./script> [ options ]
775 This program makes it easier to boot NOVA or other operating system
776 (OS) in different environments. It reads a so called novaboot script
777 and uses it either to boot the OS in an emulator (e.g. in qemu) or to
778 generate the configuration for a specific bootloader and optionally to
779 copy the necessary binaries and other needed files to proper
780 locations, perhaps on a remote server. In case the system is actually
781 booted, its serial output is redirected to standard output if that is
784 A typical way of using novaboot is to make the novaboot script
785 executable and set its first line to I<#!/usr/bin/env novaboot>. Then,
786 booting a particular OS configuration becomes the same as executing a
787 local program - the novaboot script.
789 With C<novaboot> you can:
795 Run an OS in Qemu. This is the default action when no other action is
796 specified by command line switches. Thus running C<novaboot ./script>
797 (or C<./script> as described above) will run Qemu and make it boot the
798 configuration specified in the I<script>.
802 Create a bootloader configuration file (currently supported
803 bootloaders are GRUB, GRUB2, Pulsar and uBoot) and copy it with all
804 other files needed for booting to another, perhaps remote, location.
806 ./script --server --iprelay=192.168.1.2
808 This command copies files to a TFTP server specified in the
809 configuration file and uses TCP/IP-controlled relay to reset the test
810 box and receive its serial output.
814 Run DHCP and TFTP server on developer's machine to PXE-boot NOVA from
819 When a PXE-bootable machine is connected via Ethernet to developer's
820 machine, it will boot the configuration described in I<script>.
824 Create bootable ISO images. E.g.
826 novaboot --iso -- script1 script2
828 The created ISO image will have GRUB bootloader installed on it and
829 the boot menu will allow selecting between I<script1> and I<script2>
834 =head1 PHASES AND OPTIONS
836 Novaboot performs its work in several phases. Each phase can be
837 influenced by several options, certain phases can be skipped. The list
838 of phases (in the execution order) and the corresponding options
841 =head2 Configuration reading phase
843 After starting, novaboot reads configuration files. By default, it
844 searches for files named F<.novaboot> starting from the directory of
845 the novaboot script (or working directory, see bellow) and continuing
846 upwards up to the root directory. The configuration files are read in
847 order from the root directory downwards with latter files overriding
848 settings from the former ones.
850 In certain cases, the location of the novaboot script cannot be
851 determined in this early phase. This happens either when the script is
852 read from the standard input or when novaboot is invoked explicitly
853 and options precede the script name, as in the example L</"4."> above.
854 In this case the current working directory is used as a starting point
855 for configuration file search.
859 =item -c, --config=<filename>
861 Use the specified configuration file instead of the default one(s).
865 =head2 Command line processing phase
871 Dump the current configuration to stdout end exits. Useful as an
872 initial template for a configuration file.
876 Print short (B<-h>) or long (B<--help>) help.
878 =item -t, --target=<target>
880 This option serves as a user configurable shortcut for other novaboot
881 options. The effect of this option is the same as the options stored
882 in the C<%targets> configuration variable under key I<target>. See
883 also L</"CONFIGURATION FILE">.
887 =head2 Script preprocessing phase
889 This phases allows to modify the parsed novaboot script before it is
890 used in the later phases.
894 =item -a, --append=<parameters>
896 Appends a string to the first "filename" line in the novaboot script.
897 This can be used to append parameters to the kernel's or root task's
902 Use F<bender> chainloader. Bender scans the PCI bus for PCI serial
903 ports and stores the information about them in the BIOS data area for
906 =item --chainloader=<chainloader>
908 Chainloader that is loaded before the kernel and other files specified
909 in the novaboot script. E.g. 'bin/boot/bender promisc'.
913 Prints the content of the novaboot script after removing comments and
914 evaluating all I<--scriptmod> expressions. Exit after reading (and
917 =item --scriptmod=I<perl expression>
919 When novaboot script is read, I<perl expression> is executed for every
920 line (in $_ variable). For example, C<novaboot
921 --scriptmod=s/sigma0/omega6/g> replaces every occurrence of I<sigma0>
922 in the script with I<omega6>.
924 When this option is present, it overrides I<$script_modifier> variable
925 from the configuration file, which has the same effect. If this option
926 is given multiple times all expressions are evaluated in the command
931 Strip I<rom://> prefix from command lines and generated config files.
932 The I<rom://> prefix is used by NUL. For NRE, it has to be stripped.
936 =head2 File generation phase
938 In this phase, files needed for booting are generated in a so called
939 I<build directory> (see TODO). In most cases configuration for a
940 bootloader is generated automatically by novaboot. It is also possible
941 to generate other files using I<heredoc> or I<"<"> syntax in novaboot
942 scripts. Finally, binaries can be generated in this phases by running
947 =item --build-dir=<directory>
949 Overrides the default build directory location.
951 The default build directory location is determined as follows: If the
952 configuration file defines the C<$builddir> variable, its value is
953 used. Otherwise, it is the directory that contains the first processed
956 =item -g, --grub[=I<filename>]
958 Generates grub bootloader menu file. If the I<filename> is not
959 specified, F<menu.lst> is used. The I<filename> is relative to the
960 build directory (see B<--build-dir>).
962 =item --grub-preamble=I<prefix>
964 Specifies the I<preable> that is at the beginning of the generated
965 GRUB or GRUB2 config files. This is useful for specifying GRUB's
968 =item --grub-prefix=I<prefix>
970 Specifies I<prefix> that is put in front of every file name in GRUB's
971 F<menu.lst>. The default value is the absolute path to the build directory.
973 If the I<prefix> contains string $NAME, it will be replaced with the
974 name of the novaboot script (see also B<--name>).
976 =item --grub2[=I<filename>]
978 Generate GRUB2 menuentry in I<filename>. If I<filename> is not
979 specified F<grub.cfg> is used. The content of the menuentry can be
980 customized with B<--grub-preable>, B<--grub2-prolog> or
981 B<--grub_prefix> options.
983 In order to use the the generated menuentry on your development
984 machine that uses GRUB2, append the following snippet to
985 F</etc/grub.d/40_custom> file and regenerate your grub configuration,
986 i.e. run update-grub on Debian/Ubuntu.
988 if [ -f /path/to/nul/build/grub.cfg ]; then
989 source /path/to/nul/build/grub.cfg
992 =item --grub2-prolog=I<prolog>
994 Specifies text I<preable> that is put at the begiging of the entry
997 =item --name=I<string>
999 Use the name I<string> instead of the name of the novaboot script.
1000 This name is used for things like a title of grub menu or for the
1001 server directory where the boot files are copied to.
1005 Do not generate files on the fly (i.e. "<" syntax) except for the
1006 files generated via "<<WORD" syntax.
1008 =item -p, --pulsar[=mac]
1010 Generates pulsar bootloader configuration file named F<config-I<mac>>
1011 The I<mac> string is typically a MAC address and defaults to
1014 =item --scons[=scons command]
1016 Runs I<scons> to build files that are not generated by novaboot
1021 Exit novaboot after file generation phase.
1025 =head2 Target connection check
1027 If supported by the target, the connection to it is made and it is
1028 checked whether the target is not occupied by another novaboot
1031 =head2 File deployment phase
1033 In some setups, it is necessary to copy the files needed for booting
1034 to a particular location, e.g. to a TFTP boot server or to the
1039 =item -d, --dhcp-tftp
1041 Turns your workstation into a DHCP and TFTP server so that NOVA
1042 can be booted via PXE BIOS on a test machine directly connected by
1043 a plain Ethernet cable to your workstation.
1045 The DHCP and TFTP servers require root privileges and C<novaboot>
1046 uses C<sudo> command to obtain those. You can put the following to
1047 I</etc/sudoers> to allow running the necessary commands without
1048 asking for password.
1050 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 --pidfile tftpd.pid *, /usr/bin/touch dhcpd.leases, /usr/bin/pkill --pidfile=dhcpd.pid, /usr/bin/pkill --pidfile=tftpd.pid
1051 your_login ALL=NOPASSWD: NOVABOOT
1053 =item -i, --iso[=filename]
1055 Generates the ISO image that boots NOVA system via GRUB. If no filename
1056 is given, the image is stored under I<NAME>.iso, where I<NAME> is the name
1057 of the novaboot script (see also B<--name>).
1059 =item --server[=[[user@]server:]path]
1061 Copy all files needed for booting to another location (implies B<-g>
1062 unless B<--grub2> is given). The files will be copied (by B<rsync>
1063 tool) to the directory I<path>. If the I<path> contains string $NAME,
1064 it will be replaced with the name of the novaboot script (see also
1069 If B<--server> is used and its value ends with $NAME, then after
1070 copying the files, a new bootloader configuration file (e.g. menu.lst)
1071 is created at I<path-wo-name>, i.e. the path specified by B<--server>
1072 with $NAME part removed. The content of the file is created by
1073 concatenating all files of the same name from all subdirectories of
1074 I<path-wo-name> found on the "server".
1076 =item --rsync-flags=I<flags>
1078 Specifies which I<flags> are appended to F<rsync> command line when
1079 copying files as a result of I<--server> option.
1083 =head2 Target power-on and reset phase
1087 =item --iprelay=I<addr[:port]>
1089 Use IP relay to reset the machine and to get the serial output. The IP
1090 address of the relay is given by I<addr> parameter.
1092 Note: This option is expected to work with HWG-ER02a IP relays.
1096 Switch on/off the target machine. Currently works only with
1099 =item -Q, --qemu=I<qemu-binary>
1101 The name of qemu binary to use. The default is 'qemu'.
1103 =item --qemu-append=I<flags>
1105 Append I<flags> to the default qemu flags (QEMU_FLAGS variable or
1106 C<-cpu coreduo -smp 2>).
1108 =item -q, --qemu-flags=I<flags>
1110 Replace the default qemu flags (QEMU_FLAGS variable or C<-cpu coreduo
1111 -smp 2>) with I<flags> specified here.
1115 =head2 Interaction with the bootloader on the target
1117 See B<--serial>. There will be new options soon.
1123 Interact with uBoot bootloader to boot the thing described in the
1124 novaboot script. Implementation of this option is currently tied to a
1125 particular board that we use. It may be subject to changes in the
1130 =head2 Target's output reception phase
1134 =item -s, --serial[=device]
1136 Use serial line to control GRUB bootloader and to see the output
1137 serial output of the machine. The default value is F</dev/ttyUSB0>.
1139 =item --stty=<settings>
1141 Specifies settings passed to C<stty> invoked on the serial line
1142 specified with B<--serial>. If this option is not given, C<stty> is
1143 called with C<raw -crtscts -onlcr 115200> settings.
1145 =item --remote-cmd=<cmd>
1147 Command that mediates connection to the target's serial line. For
1148 example C<ssh server 'cu -l /dev/ttyS0'>.
1150 =item --reset-cmd=<cmd>
1152 Command that resets the target.
1156 See also B<--iprelay>.
1158 =head2 Termination phase
1160 Daemons that were spwned (F<dhcpd> and F<tftpd>) are killed here.
1162 =head1 NOVABOOT SCRIPT SYNTAX
1164 The syntax tries to mimic POSIX shell syntax. The syntax is defined with the following rules.
1166 Lines starting with "#" are ignored.
1168 Lines that end with "\" are concatenated with the following line after
1169 removal of the final "\" and leading whitespace of the following line.
1171 Lines in the form I<VARIABLE=...> (i.e. matching '^[A-Z_]+=' regular
1172 expression) assign values to internal variables. See VARIABLES
1175 Otherwise, the first word on the line represents the filename
1176 (relative to the build directory (see B<--build-dir>) of the module to
1177 load and the remaining words are passed as the command line
1180 When the line ends with "<<WORD" then the subsequent lines until the
1181 line containing only WORD are copied literally to the file named on
1184 When the line ends with "< CMD" the command CMD is executed with
1185 C</bin/sh> and its standard output is stored in the file named on that
1186 line. The SRCDIR variable in CMD's environment is set to the absolute
1187 path of the directory containing the interpreted novaboot script.
1190 #!/usr/bin/env novaboot
1191 WVDESC=Example program
1192 bin/apps/sigma0.nul S0_DEFAULT script_start:1,1 \
1193 verbose hostkeyb:0,0x60,1,12,2
1195 hello.nulconfig <<EOF
1196 sigma0::mem:16 name::/s0/log name::/s0/timer name::/s0/fs/rom ||
1197 rom://bin/apps/hello.nul
1200 This example will load three modules: sigma0.nul, hello.nul and
1201 hello.nulconfig. sigma0 gets some command line parameters and
1202 hello.nulconfig file is generated on the fly from the lines between
1207 The following variables are interpreted in the novaboot script:
1213 Novaboot chdir()s to this directory before file generation phase. The
1214 directory name specified here is relative to the build directory
1215 specified by other means (see L</--build-dir>).
1219 Description of the wvtest-compliant program.
1221 =item WVTEST_TIMEOUT
1223 The timeout in seconds for WvTest harness. If no complete line appears
1224 in the test output within the time specified here, the test fails. It
1225 is necessary to specify this for long running tests that produce no
1226 intermediate output.
1230 Use a specific qemu binary (can be overriden with B<-Q>) and flags
1231 when booting this script under qemu. If QEMU_FLAGS variable is also
1232 specified flags specified in QEMU variable are replaced by those in
1237 Use specific qemu flags (can be overriden with B<-q>).
1239 =item HYPERVISOR_PARAMS
1241 Parameters passed to hypervisor. The default value is "serial", unless
1242 overriden in configuration file.
1246 The kernel to use instead of NOVA hypervisor specified in the
1247 configuration file. The value should contain the name of the kernel
1248 image as well as its command line parameters. If this variable is
1249 defined and non-empty, the variable HYPERVISOR_PARAMS is not used.
1253 =head1 CONFIGURATION FILE
1255 Novaboot can read its configuration from a file. Configuration file
1256 was necessary in early days of novaboot. Nowadays, an attempt is made
1257 to not use the configuration file because it makes certain novaboot
1258 scripts unusable on systems without (or with different) configuration
1259 file. The only recommended use of the configuration file is to specify
1260 custom_options (see bellow).
1262 If you decide to use the configuration file, it is looked up, by
1263 default, in files named F<.novaboot> as described in L</Configuration
1264 reading phase>. Alternatively, its location can be specified with the
1265 B<-c> switch or with the NOVABOOT_CONFIG environment variable. The
1266 configuration file has perl syntax and should set values of certain
1267 Perl variables. The current configuration can be dumped with the
1268 B<--dump-config> switch. Some configuration variables can be overriden
1269 by environment variables (see below) or by command line switches.
1271 Documentation of some configuration variables follows:
1277 Build directory location relative to the location of the configuration
1282 Hash of shortcuts to be used with the B<--target> option. If the hash
1283 contains, for instance, the following pair of values
1285 'mybox' => '--server=boot:/tftproot --serial=/dev/ttyUSB0 --grub',
1287 then the following two commands are equivalent:
1289 ./script --server=boot:/tftproot --serial=/dev/ttyUSB0 --grub
1294 =head1 ENVIRONMENT VARIABLES
1296 Some options can be specified not only via config file or command line
1297 but also through environment variables. Environment variables override
1298 the values from configuration file and command line parameters
1299 override the environment variables.
1303 =item NOVABOOT_CONFIG
1305 Name of the novaboot configuration file to use instead of the default
1308 =item NOVABOOT_BENDER
1310 Defining this variable has the same meaning as B<--bender> option.
1316 Michal Sojka <sojka@os.inf.tu-dresden.de>