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';
44 $CFG::default_target = 'qemu';
47 "tud" => '--server=erwin.inf.tu-dresden.de:~sojka/boot/novaboot --rsync-flags="--chmod=Dg+s,ug+w,o-w,+rX --rsync-path=\"umask 002 && rsync\"" --grub --grub-prefix=(nd)/tftpboot/sojka/novaboot --grub-preamble="timeout 0" --concat --iprelay=141.76.48.80:2324 --scriptmod=s/\\\\bhostserial\\\\b/hostserialpci/g',
48 "novabox" => '--server=rtime.felk.cvut.cz:/srv/tftp/novaboot --rsync-flags="--chmod=Dg+s,ug+w,o-w,+rX --rsync-path=\"umask 002 && rsync\"" --pulsar --iprelay=147.32.86.92:2324',
49 "localhost" => '--scriptmod=s/console=tty[A-Z0-9,]+// --server=/boot/novaboot/$NAME --grub2 --grub-prefix=/boot/novaboot/$NAME --grub2-prolog=" set root=\'(hd0,msdos1)\'"',
50 "ryuglab" => '--server=pc-sojkam.felk.cvut.cz:/srv/tftp --uboot --uboot-init="mw f0000b00 \${psc_cfg}" --remote-cmd="ssh -t pc-sojkam.felk.cvut.cz \"cu -l /dev/ttyUSB0 -s 115200\"" --remote-expect="Connected." --reset-cmd="ssh -t pc-sojkam.felk.cvut.cz \"dtrrts /dev/ttyUSB0 1 1\""',
51 "ryulocal" => '--dhcp-tftp --serial --uboot --uboot-init="dhcp; mw f0000b00 \${psc_cfg}" --reset-cmd="if which dtrrts; then dtrrts $NB_SERIAL 0 1; sleep 0.1; dtrrts $NB_SERIAL 1 1; fi"',
54 chomp(my $nproc = `nproc`);
55 $CFG::scons = "scons -j$nproc";
56 $CFG::make = "make -j$nproc";
63 package CFG; # Put config data into a separate namespace
68 die("ERROR: Failure compiling '$cfg' - $@");
69 } elsif (! defined($rc)) {
70 die("ERROR: Failure reading '$cfg' - $!");
72 die("ERROR: Failure processing '$cfg'");
75 $builddir = File::Spec->rel2abs($CFG::builddir, dirname($cfg)) if defined $CFG::builddir;
76 print STDERR "novaboot: Read $cfg\n";
81 # We don't use $0 here, because it points to the novaboot itself and
82 # not to the novaboot script. The problem with this approach is that
83 # when a script is run as "novaboot <options> <script>" then $ARGV[0]
84 # contains the first option. Hence the -f check.
85 my $dir = File::Spec->rel2abs($ARGV[0] && -f $ARGV[0] ? dirname($ARGV[0]) : '', $invocation_dir);
86 while (-d $dir && $dir ne "/") {
87 push @cfgs, "$dir/.novaboot" if -r "$dir/.novaboot";
88 $dir = abs_path($dir."/..");
91 my $cfg = $ENV{'NOVABOOT_CONFIG'};
92 Getopt::Long::Configure(qw/no_ignore_case pass_through/);
93 GetOptions ("config|c=s" => \$cfg);
94 read_config($_) foreach $cfg or reverse @cfgs;
96 ## Command line handling
99 GetOptions ("target|t=s" => \$explicit_target);
101 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, $make, $man, $no_file_gen, $off_opt, $on_opt, $pulsar, $pulsar_root, $qemu, $qemu_append, $qemu_flags_cmd, $remote_cmd, $remote_expect, $reset_cmd, $rom_prefix, $rsync_flags, @scriptmod, $scons, $serial, $server, $stty, $uboot, $uboot_init);
104 $rom_prefix = 'rom://';
105 $stty = 'raw -crtscts -onlcr 115200';
107 Getopt::Long::Configure(qw/no_ignore_case no_pass_through/);
110 "append|a=s" => \$append,
111 "bender|b" => \$bender,
112 "build-dir=s" => sub { my ($n, $v) = @_; $builddir = File::Spec->rel2abs($v); },
113 "concat" => \$concat,
114 "chainloader=s" => \@chainloaders,
115 "dhcp-tftp|d" => \$dhcp_tftp,
116 "dump" => \$dump_opt,
117 "dump-config" => \$dump_config,
118 "gen-only" => \$gen_only,
119 "grub|g:s" => \$grub_config,
120 "grub-preamble=s"=> \$grub_preamble,
121 "grub-prefix=s" => \$grub_prefix,
122 "grub2:s" => \$grub2_config,
123 "grub2-prolog=s" => \$grub2_prolog,
124 "iprelay=s" => \$iprelay,
125 "iso|i:s" => \$iso_image,
126 "name=s" => \$config_name_opt,
127 "make|m:s" => \$make,
128 "no-file-gen" => \$no_file_gen,
131 "pulsar|p:s" => \$pulsar,
132 "pulsar-root=s" => \$pulsar_root,
133 "qemu|Q:s" => \$qemu,
134 "qemu-append=s" => \$qemu_append,
135 "qemu-flags|q=s" => \$qemu_flags_cmd,
136 "remote-cmd=s" => \$remote_cmd,
137 "remote-expect=s"=> \$remote_expect,
138 "reset-cmd=s" => \$reset_cmd,
139 "rsync-flags=s" => \$rsync_flags,
140 "scons:s" => \$scons,
141 "scriptmod=s" => \@scriptmod,
142 "serial|s:s" => \$serial,
143 "server:s" => \$server,
144 "strip-rom" => sub { $rom_prefix = ''; },
147 "uboot-init=s" => \$uboot_init,
152 # First process target options
153 GetOptionsFromString($CFG::targets{$explicit_target || $CFG::default_target}, %opt_spec);
155 # Then process other command line options - some of them may override
156 # what was specified by the target
157 GetOptions %opt_spec;
158 pod2usage(1) if $help;
159 pod2usage(-exitstatus => 0, -verbose => 2) if $man;
161 ### Dump sanitized configuration (if requested)
165 $Data::Dumper::Indent=1;
166 print "# This file is in perl syntax.\n";
167 foreach my $key(sort(keys(%CFG::))) { # See "Symbol Tables" in perlmod(1)
168 if (defined ${$CFG::{$key}}) { print Data::Dumper->Dump([${$CFG::{$key}}], ["*$key"]); }
169 if ( @{$CFG::{$key}}) { print Data::Dumper->Dump([\@{$CFG::{$key}}], ["*$key"]); }
170 if ( %{$CFG::{$key}}) { print Data::Dumper->Dump([\%{$CFG::{$key}}], ["*$key"]); }
176 ### Sanitize configuration
178 if (defined $config_name_opt && scalar(@ARGV) > 1) { die "You cannot use --name with multiple scripts"; }
181 if (defined $serial) {
182 $serial ||= "/dev/ttyUSB0";
183 $ENV{NB_SERIAL} = $serial;
185 if (defined $grub_config) { $grub_config ||= "menu.lst"; }
186 if (defined $grub2_config) { $grub2_config ||= "grub.cfg"; }
188 ## Parse the novaboot script(s)
194 my ($modules, $variables, $generated, $continuation);
196 if ($ARGV ne $last_fn) { # New script
197 die "Missing EOF in $last_fn" if $file;
198 die "Unfinished line in $last_fn" if $line;
200 push @scripts, { 'filename' => $ARGV,
201 'modules' => $modules = [],
202 'variables' => $variables = {},
203 'generated' => $generated = []};
207 next if /^#/ || /^\s*$/; # Skip comments and empty lines
209 foreach my $mod(@scriptmod) { eval $mod; }
211 print "$_\n" if $dump_opt;
213 if (/^([A-Z_]+)=(.*)$/) { # Internal variable
214 $$variables{$1} = $2;
217 if (/^([^ ]*)(.*?)[[:space:]]*<<([^ ]*)$/) { # Heredoc start
218 push @$modules, "$1$2";
220 push @$generated, {filename => $1, content => $file};
224 if ($file && $_ eq $EOF) { # Heredoc end
228 if ($file) { # Heredoc content
229 push @{$file}, "$_\n";
232 $_ =~ s/^[[:space:]]*// if ($continuation);
233 if (/\\$/) { # Line continuation
234 $line .= substr($_, 0, length($_)-1);
240 $line .= " $append" if ($append && scalar(@$modules) == 0);
242 if ($line =~ /^([^ ]*)(.*?)[[:space:]]*< ?(.*)$/) { # Command substitution
243 push @$modules, "$1$2";
244 push @$generated, {filename => $1, command => $3};
248 push @$modules, $line;
252 #print Dumper(\@scripts);
258 sub generate_configs($$$) {
259 my ($base, $generated, $filename) = @_;
260 if ($base) { $base = "$base/"; };
261 foreach my $g(@$generated) {
262 if (exists $$g{content}) {
263 my $config = $$g{content};
264 my $fn = $$g{filename};
265 open(my $f, '>', $fn) || die("$fn: $!");
266 map { s|\brom://([^ ]*)|$rom_prefix$base$1|g; print $f "$_"; } @{$config};
268 print "novaboot: Created $fn\n";
269 } elsif (exists $$g{command} && ! $no_file_gen) {
270 $ENV{SRCDIR} = dirname(File::Spec->rel2abs( $filename, $invocation_dir ));
271 system_verbose("( $$g{command} ) > $$g{filename}");
276 sub generate_grub_config($$$$;$)
278 my ($filename, $title, $base, $modules_ref, $preamble) = @_;
279 if ($base) { $base = "$base/"; };
280 open(my $fg, '>', $filename) or die "$filename: $!";
281 print $fg "$preamble\n" if $preamble;
282 print $fg "title $title\n" if $title;
283 #print $fg "root $base\n"; # root doesn't really work for (nd)
285 foreach (@$modules_ref) {
288 my ($kbin, $kcmd) = split(' ', $_, 2);
289 $kcmd = '' if !defined $kcmd;
290 print $fg "kernel ${base}$kbin $kcmd\n";
292 s|\brom://([^ ]*)|$rom_prefix$base$1|g; # Translate rom:// files - needed for vdisk parameter of sigma0
293 print $fg "module $base$_\n";
297 print("novaboot: Created $builddir/$filename\n");
301 sub generate_grub2_config($$$$;$$)
303 my ($filename, $title, $base, $modules_ref, $preamble, $prolog) = @_;
304 if ($base && substr($base,-1,1) ne '/') { $base = "$base/"; };
305 open(my $fg, '>', $filename) or die "$filename: $!";
306 print $fg "$preamble\n" if $preamble;
307 $title ||= 'novaboot';
308 print $fg "menuentry $title {\n";
309 print $fg "$prolog\n" if $prolog;
311 foreach (@$modules_ref) {
314 my ($kbin, $kcmd) = split(' ', $_, 2);
315 $kcmd = '' if !defined $kcmd;
316 print $fg " multiboot ${base}$kbin $kcmd\n";
319 # GRUB2 doesn't pass filename in multiboot info so we have to duplicate it here
320 $_ = join(' ', ($args[0], @args));
321 s|\brom://|$rom_prefix|g; # We do not need to translate path for GRUB2
322 print $fg " module $base$_\n";
327 print("novaboot: Created $builddir/$filename\n");
331 sub generate_pulsar_config($$)
333 my ($filename, $modules_ref) = @_;
334 open(my $fg, '>', $filename) or die "$filename: $!";
335 print $fg "root $pulsar_root\n" if defined $pulsar_root;
338 foreach (@$modules_ref) {
341 ($kbin, $kcmd) = split(' ', $_, 2);
342 $kcmd = '' if !defined $kcmd;
345 s|\brom://|$rom_prefix|g;
346 print $fg "load $_\n";
349 # Put kernel as last - this is needed for booting Linux and has no influence on non-Linux OSes
350 print $fg "exec $kbin $kcmd\n";
352 print("novaboot: Created $builddir/$filename\n");
356 sub shell_cmd_string(@)
358 return join(' ', map((/^[-_=a-zA-Z0-9\/\.\+]+$/ ? "$_" : "'$_'"), @_));
363 print "novaboot: Running: ".shell_cmd_string(@_)."\n";
367 sub system_verbose($)
370 print "novaboot: Running: $cmd\n";
371 my $ret = system($cmd);
372 if ($ret & 0x007f) { die("Command terminated by a signal"); }
373 if ($ret & 0xff00) {die("Command exit with non-zero exit code"); }
374 if ($ret) { die("Command failure $ret"); }
379 if (exists $variables->{WVDESC}) {
380 print "Testing \"$variables->{WVDESC}\" in $last_fn:\n";
381 } elsif ($last_fn =~ /\.wv$/) {
382 print "Testing \"all\" in $last_fn:\n";
385 ## Connect to the target and check whether is not occupied
387 # We have to do this before file generation phase, because file
388 # generation is intermixed with file deployment phase and we want to
389 # check whether the target is not used by somebody else before
390 # deploying files. Otherwise, we may rewrite other user's files on a
393 my $exp; # Expect object to communicate with the target over serial line
395 my ($target_reset, $target_power_on, $target_power_off);
397 if (defined $iprelay) {
399 $iprelay =~ /([.0-9]+)(:([0-9]+))?/;
402 my $paddr = sockaddr_in($port, inet_aton($addr));
403 my $proto = getprotobyname('tcp');
404 socket($IPRELAY, PF_INET, SOCK_STREAM, $proto) || die "socket: $!";
405 print "novaboot: Connecting to IP relay... ";
406 connect($IPRELAY, $paddr) || die "connect: $!";
408 $exp = Expect->init(\*$IPRELAY);
412 print $exp "\xFF\xF6"; # AYT
413 my $connected = $exp->expect(20, # Timeout in seconds
414 '<iprelayd: connected>',
415 '-re', '<WEB51 HW[^>]*>');
420 my ($relay, $onoff) = @_;
421 die unless ($relay == 1 || $relay == 2);
423 my $cmd = ($relay == 1 ? 0x5 : 0x6) | ($onoff ? 0x20 : 0x10);
424 return "\xFF\xFA\x2C\x32".chr($cmd)."\xFF\xF0";
428 my ($relay, $onoff) = @_;
429 die unless ($relay == 1 || $relay == 2);
430 my $cmd = ($relay == 1 ? 0xdf : 0xbf) | ($onoff ? 0x00 : 0xff);
431 return "\xFF\xFA\x2C\x97".chr($cmd)."\xFF\xF0";
435 my ($relay, $onoff, $can_giveup) = @_;
436 my $confirmation = '';
438 print $exp relaycmd($relay, $onoff);
439 my $confirmed = $exp->expect(20, # Timeout in seconds
440 relayconf($relay, $onoff));
443 print("Relay confirmation timeout - ignoring\n");
445 die "Relay confirmation timeout";
451 $target_reset = sub {
452 relay(2, 1, 1); # Reset the machine
457 $target_power_off = sub {
458 relay(1, 1); # Press power button
459 usleep(6000000); # Long press to switch off
463 $target_power_on = sub {
464 relay(1, 1); # Press power button
465 usleep(100000); # Short press
471 system_verbose("stty -F $serial $stty");
472 open($CONN, "+<", $serial) || die "open $serial: $!";
473 $exp = Expect->init(\*$CONN);
474 } elsif ($remote_cmd) {
475 print "novaboot: Running: $remote_cmd\n";
476 $exp = Expect->spawn($remote_cmd);
479 if ($remote_expect) {
480 $exp->expect(10, $remote_expect) || die "Expect for '$remote_expect' timed out";
483 if (defined $reset_cmd) {
484 $target_reset = sub {
485 system_verbose($reset_cmd);
489 if (defined $on_opt && defined $target_power_on) {
493 if (defined $off_opt && defined $target_power_off) {
494 print "novaboot: Switching the target off...\n";
495 &$target_power_off();
499 $builddir ||= dirname(File::Spec->rel2abs( ${$scripts[0]}{filename})) if scalar @scripts;
500 if (defined $builddir) {
501 chdir($builddir) or die "Can't change directory to $builddir: $!";
502 print "novaboot: Entering directory `$builddir'\n";
505 ## File generation phase
506 my (%files_iso, $menu_iso, $filename);
507 my $config_name = '';
509 foreach my $script (@scripts) {
510 $filename = $$script{filename};
511 $modules = $$script{modules};
512 $generated = $$script{generated};
513 $variables = $$script{variables};
515 ($config_name = $filename) =~ s#.*/##;
516 $config_name = $config_name_opt if (defined $config_name_opt);
518 if (exists $variables->{BUILDDIR}) {
519 $builddir = File::Spec->rel2abs($variables->{BUILDDIR});
520 chdir($builddir) or die "Can't change directory to $builddir: $!";
521 print "novaboot: Entering directory `$builddir'\n";
525 if (exists $variables->{KERNEL}) {
526 $kernel = $variables->{KERNEL};
528 if ($CFG::hypervisor) {
529 $kernel = $CFG::hypervisor . " ";
530 if (exists $variables->{HYPERVISOR_PARAMS}) {
531 $kernel .= $variables->{HYPERVISOR_PARAMS};
533 $kernel .= $CFG::hypervisor_params;
537 @$modules = ($kernel, @$modules) if $kernel;
538 @$modules = (@chainloaders, @$modules);
539 @$modules = ("bin/boot/bender", @$modules) if ($bender || defined $ENV{'NOVABOOT_BENDER'});
542 ($prefix = $grub_prefix) =~ s/\$NAME/$config_name/ if defined $grub_prefix;
543 $prefix ||= $builddir;
544 # TODO: use $grub_prefix as first parameter if some switch is given
545 generate_configs('', $generated, $filename);
547 ### Generate bootloader configuration files
548 my @bootloader_configs;
549 push @bootloader_configs, generate_grub_config($grub_config, $config_name, $prefix, $modules, $grub_preamble) if (defined $grub_config);
550 push @bootloader_configs, generate_grub2_config($grub2_config, $config_name, $prefix, $modules, $grub_preamble, $grub2_prolog) if (defined $grub2_config);
551 push @bootloader_configs, generate_pulsar_config('config-'.($pulsar||'novaboot'), $modules) if (defined $pulsar);
553 ### Run scons or make
555 my @files = map({ ($file) = m/([^ ]*)/; $file; } @$modules);
556 # Filter-out generated files
557 my @to_build = grep({ my $file = $_; !scalar(grep($file eq $$_{filename}, @$generated)) } @files);
559 system_verbose($scons || $CFG::scons." ".join(" ", @to_build)) if (defined $scons);
560 system_verbose($make || $CFG::make ." ".join(" ", @to_build)) if (defined $make);
563 ### Copy files (using rsync)
564 if (defined $server && !defined($gen_only)) {
565 (my $real_server = $server) =~ s/\$NAME/$config_name/;
567 my ($hostname, $path) = split(":", $real_server, 2);
568 if (! defined $path) {
572 my $files = join(" ", map({ ($file) = m/([^ ]*)/; $file; } ( @$modules, @bootloader_configs)));
573 map({ my $file = (split)[0]; die "$file: $!" if ! -f $file; } @$modules);
574 my $istty = -t STDOUT && ($ENV{'TERM'} || 'dumb') ne 'dumb';
575 my $progress = $istty ? "--progress" : "";
576 system_verbose("rsync $progress -RLp $rsync_flags $files $real_server");
577 if ($server =~ m|/\$NAME$| && $concat) {
578 my $cmd = join("; ", map { "( cd $path/.. && cat */$_ > $_ )" } @bootloader_configs);
579 system_verbose($hostname ? "ssh $hostname '$cmd'" : $cmd);
583 ### Prepare ISO image generation
584 if (defined $iso_image) {
585 generate_configs("(cd)", $generated, $filename);
587 generate_grub_config(\$menu, $config_name, "(cd)", $modules);
588 $menu_iso .= "$menu\n";
589 map { ($file,undef) = split; $files_iso{$file} = 1; } @$modules;
593 ## Generate ISO image
594 if (defined $iso_image) {
595 open(my $fh, ">menu-iso.lst");
596 print $fh "timeout 5\n\n$menu_iso";
598 my $files = "boot/grub/menu.lst=menu-iso.lst " . join(" ", map("$_=$_", keys(%files_iso)));
599 $iso_image ||= "$config_name.iso";
600 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");
601 print("ISO image created: $builddir/$iso_image\n");
604 exit(0) if defined $gen_only;
606 ## Boot the system using various methods and send serial output to stdout
608 if (scalar(@scripts) > 1 && ( defined $dhcp_tftp || defined $serial || defined $iprelay)) {
609 die "You cannot do this with multiple scripts simultaneously";
612 if ($variables->{WVTEST_TIMEOUT}) {
613 print "wvtest: timeout ", $variables->{WVTEST_TIMEOUT}, "\n";
618 $str =~ s/^\s+|\s+$//g;
626 $qemu ||= $variables->{QEMU} || $CFG::qemu;
627 my @qemu_flags = split(" ", $qemu);
628 $qemu = shift(@qemu_flags);
630 @qemu_flags = split(/ +/, trim($variables->{QEMU_FLAGS})) if exists $variables->{QEMU_FLAGS};
631 @qemu_flags = split(/ +/, trim($qemu_flags_cmd)) if $qemu_flags_cmd;
632 push(@qemu_flags, split(/ +/, trim($qemu_append || '')));
634 if (defined $iso_image) {
635 # Boot NOVA with grub (and test the iso image)
636 push(@qemu_flags, ('-cdrom', "$config_name.iso"));
638 # Boot NOVA without GRUB
640 # Non-patched qemu doesn't like commas, but NUL can live with pluses instead of commans
641 foreach (@$modules) {s/,/+/g;}
642 generate_configs("", $generated, $filename);
644 if (scalar @$modules) {
645 my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);
646 $kcmd = '' if !defined $kcmd;
648 @$modules = map { if (/\.dtb$/) { $dtb=$_; (); } else { $_ } } @$modules;
649 my $initrd = join ",", @$modules;
651 push(@qemu_flags, ('-kernel', $kbin, '-append', $kcmd));
652 push(@qemu_flags, ('-initrd', $initrd)) if $initrd;
653 push(@qemu_flags, ('-dtb', $dtb)) if $dtb;
656 push(@qemu_flags, qw(-serial stdio)); # Redirect serial output (for collecting test restuls)
657 unshift(@qemu_flags, ('-name', $config_name));
658 print "novaboot: Running: ".shell_cmd_string($qemu, @qemu_flags)."\n";
659 $exp = Expect->spawn(($qemu, @qemu_flags)) || die("exec() failed: $!");
662 ### Local DHCPD and TFTPD
664 my ($dhcpd_pid, $tftpd_pid);
666 if (defined $dhcp_tftp)
668 generate_configs("(nd)", $generated, $filename);
669 system_verbose('mkdir -p tftpboot');
670 generate_grub_config("tftpboot/os-menu.lst", $config_name, "(nd)", \@$modules, "timeout 0");
671 open(my $fh, '>', 'dhcpd.conf');
672 my $mac = `cat /sys/class/net/eth0/address`;
674 print $fh "subnet 10.23.23.0 netmask 255.255.255.0 {
675 range 10.23.23.10 10.23.23.100;
676 filename \"bin/boot/grub/pxegrub.pxe\";
677 next-server 10.23.23.1;
680 hardware ethernet $mac;
681 fixed-address 10.23.23.1;
684 system_verbose("sudo ip a add 10.23.23.1/24 dev eth0;
685 sudo ip l set dev eth0 up;
686 sudo touch dhcpd.leases");
688 # We run servers by forking ourselves, because the servers end up
689 # in our process group and get killed by signals sent to the
690 # process group (e.g. Ctrl-C on terminal).
692 exec_verbose("sudo dhcpd -d -cf dhcpd.conf -lf dhcpd.leases -pf dhcpd.pid") if ($dhcpd_pid == 0);
694 exec_verbose("sudo in.tftpd --foreground --secure -v -v -v --pidfile tftpd.pid $builddir") if ($tftpd_pid == 0);
696 # Kill server when we die
697 $SIG{__DIE__} = sub { system_verbose('sudo pkill --pidfile=dhcpd.pid');
698 system_verbose('sudo pkill --pidfile=tftpd.pid'); };
701 ### Serial line or IP relay
703 if (defined $target_reset) {
704 print "novaboot: Reseting the test box... ";
709 if (defined $uboot) {
710 print "novaboot: Waiting for uBoot prompt...\n";
713 [qr/Hit any key to stop autoboot:/, sub { $exp->send("\n"); exp_continue; }],
714 '=> ') || die "No uBoot prompt deteceted";
715 $exp->send("$uboot_init\n") if $uboot_init;
716 $exp->expect(10, '=> ') || die "uBoot prompt timeout";
718 my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);
720 @$modules = map { if (/\.dtb$/) { $dtb=$_; (); } else { $_ } } @$modules;
721 my $initrd = shift @$modules;
723 my $kern_addr = '800000';
724 my $initrd_addr = '-';
727 $exp->send("tftp $kern_addr $kbin\n");
729 [qr/#/, sub { exp_continue; }],
730 '=> ') || die "Kernel load failed";
732 $dtb_addr = '7f0000';
733 $exp->send("tftp $dtb_addr $dtb\n");
735 [qr/#/, sub { exp_continue; }],
736 '=> ') || die "Device tree load failed";
738 if (defined $initrd) {
739 $initrd_addr = 'b00000';
740 $exp->send("tftp $initrd_addr $initrd\n");
742 [qr/#/, sub { exp_continue; }],
743 '=> ') || die "Initrd load failed";
745 $exp->send("set bootargs $kcmd\n");
746 $exp->expect(1, '=> ') || die "uBoot prompt timeout";
747 $exp->send("bootm $kern_addr $initrd_addr $dtb_addr\n");
748 $exp->expect(1, "\n") || die "uBoot command timeout";
752 # Serial line of the target is available
753 print "novaboot: Serial line interaction (press Ctrl-C to interrupt)...\n";
756 if (-t STDIN) { # Set up bi-directional communication if we run on terminal
757 my $infile = new IO::File;
758 $infile->IO::File::fdopen(*STDIN,'r');
759 my $in_object = Expect->exp_init($infile);
760 $in_object->set_group($exp);
761 #$in_object->set_seq("\cC",undef);
763 # I'm not sure when to use raw mode and when not. With
764 # --dhcp-tftp, I want the output of daemons to be normally
765 # formated (no raw mode). On the other hand, I don't want
766 # input for qemu to be echoed. Need to think more about this.
767 $in_object->manual_stty(1);
768 push(@inputs, $in_object);
770 Expect::interconnect(@inputs);
773 ## Kill dhcpc or tftpd
774 if (defined $dhcp_tftp) {
775 die("novaboot: This should kill servers on background\n");
782 novaboot - A tool for booting various operating systems on various hardware or in qemu
786 B<novaboot> [ options ] [--] script...
788 B<./script> [ options ]
792 This program makes it easier to boot NOVA or other operating system
793 (OS) on different targets (machines or emulators). It reads a so
794 called novaboot script, that specifies the boot configuration, and
795 setups the target to boot that configuration. Setting up the target
796 means to generate the bootloader configuration files, deploy the
797 binaries and other needed files to proper locations, perhaps on a
798 remote boot server and reset the target. Then, target's serial output
799 is redirected to standard output if that is possible.
801 A typical way of using novaboot is to make the novaboot script
802 executable and set its first line to I<#!/usr/bin/env novaboot>. Then,
803 booting a particular OS configuration becomes the same as executing a
804 local program - the novaboot script.
806 For example, with C<novaboot> you can:
812 Run an OS in Qemu. This is the default action when no other action is
813 specified by command line switches. Thus running C<novaboot ./script>
814 (or C<./script> as described above) will run Qemu and make it boot the
815 configuration specified in the I<script>.
819 Create a bootloader configuration file (currently supported
820 bootloaders are GRUB, GRUB2, Pulsar and uBoot) and copy it with all
821 other files needed for booting to another, perhaps remote, location.
823 ./script --server --iprelay=192.168.1.2
825 This command copies files to a TFTP server specified in the
826 configuration file and uses TCP/IP-controlled relay to reset the test
827 box and receive its serial output.
831 Run DHCP and TFTP server on developer's machine to PXE-boot the OS
836 When a PXE-bootable machine is connected via Ethernet to developer's
837 machine, it will boot the configuration described in I<script>.
841 Create bootable ISO images. E.g.
843 novaboot --iso -- script1 script2
845 The created ISO image will have GRUB bootloader installed on it and
846 the boot menu will allow selecting between I<script1> and I<script2>
851 =head1 PHASES AND OPTIONS
853 Novaboot performs its work in several phases. Each phase can be
854 influenced by several options, certain phases can be skipped. The list
855 of phases (in the execution order) and the corresponding options
858 =head2 Configuration reading phase
860 After starting, novaboot reads configuration files. By default, it
861 searches for files named F<.novaboot> starting from the directory of
862 the novaboot script (or working directory, see bellow) and continuing
863 upwards up to the root directory. The configuration files are read in
864 order from the root directory downwards with latter files overriding
865 settings from the former ones.
867 In certain cases, the location of the novaboot script cannot be
868 determined in this early phase. This happens either when the script is
869 read from the standard input or when novaboot is invoked explicitly
870 and options precede the script name, as in the example L</"4."> above.
871 In this case the current working directory is used as a starting point
872 for configuration file search.
876 =item -c, --config=I<filename>
878 Use the specified configuration file instead of the default one(s).
882 =head2 Command line processing phase
888 Dump the current configuration to stdout end exits. Useful as an
889 initial template for a configuration file.
893 Print short (B<-h>) or long (B<--help>) help.
895 =item -t, --target=I<target>
897 This option serves as a user configurable shortcut for other novaboot
898 options. The effect of this option is the same as the options stored
899 in the C<%targets> configuration variable under key I<target>. See
900 also L</"CONFIGURATION FILE">.
904 =head2 Script preprocessing phase
906 This phases allows to modify the parsed novaboot script before it is
907 used in the later phases.
911 =item -a, --append=I<parameters>
913 Appends a string to the first "filename" line in the novaboot script.
914 This can be used to append parameters to the kernel's or root task's
919 Use F<bender> chainloader. Bender scans the PCI bus for PCI serial
920 ports and stores the information about them in the BIOS data area for
923 =item --chainloader=I<chainloader>
925 Chainloader that is loaded before the kernel and other files specified
926 in the novaboot script. E.g. 'bin/boot/bender promisc'.
930 Prints the content of the novaboot script after removing comments and
931 evaluating all I<--scriptmod> expressions. Exit after reading (and
934 =item --scriptmod=I<perl expression>
936 When novaboot script is read, I<perl expression> is executed for every
937 line (in $_ variable). For example, C<novaboot
938 --scriptmod=s/sigma0/omega6/g> replaces every occurrence of I<sigma0>
939 in the script with I<omega6>.
941 When this option is present, it overrides I<$script_modifier> variable
942 from the configuration file, which has the same effect. If this option
943 is given multiple times all expressions are evaluated in the command
948 Strip I<rom://> prefix from command lines and generated config files.
949 The I<rom://> prefix is used by NUL. For NRE, it has to be stripped.
953 =head2 File generation phase
955 In this phase, files needed for booting are generated in a so called
956 I<build directory> (see TODO). In most cases configuration for a
957 bootloader is generated automatically by novaboot. It is also possible
958 to generate other files using I<heredoc> or I<"<"> syntax in novaboot
959 scripts. Finally, binaries can be generated in this phases by running
964 =item --build-dir=I<directory>
966 Overrides the default build directory location.
968 The default build directory location is determined as follows: If the
969 configuration file defines the C<$builddir> variable, its value is
970 used. Otherwise, it is the directory that contains the first processed
973 =item -g, --grub[=I<filename>]
975 Generates grub bootloader menu file. If the I<filename> is not
976 specified, F<menu.lst> is used. The I<filename> is relative to the
977 build directory (see B<--build-dir>).
979 =item --grub-preamble=I<prefix>
981 Specifies the I<preable> that is at the beginning of the generated
982 GRUB or GRUB2 config files. This is useful for specifying GRUB's
985 =item --grub-prefix=I<prefix>
987 Specifies I<prefix> that is put in front of every file name in GRUB's
988 F<menu.lst>. The default value is the absolute path to the build directory.
990 If the I<prefix> contains string $NAME, it will be replaced with the
991 name of the novaboot script (see also B<--name>).
993 =item --grub2[=I<filename>]
995 Generate GRUB2 menuentry in I<filename>. If I<filename> is not
996 specified F<grub.cfg> is used. The content of the menuentry can be
997 customized with B<--grub-preable>, B<--grub2-prolog> or
998 B<--grub_prefix> options.
1000 In order to use the the generated menuentry on your development
1001 machine that uses GRUB2, append the following snippet to
1002 F</etc/grub.d/40_custom> file and regenerate your grub configuration,
1003 i.e. run update-grub on Debian/Ubuntu.
1005 if [ -f /path/to/nul/build/grub.cfg ]; then
1006 source /path/to/nul/build/grub.cfg
1009 =item --grub2-prolog=I<prolog>
1011 Specifies text I<preable> that is put at the begiging of the entry
1014 =item -m, --make[=make command]
1016 Runs C<make> to build files that are not generated by novaboot itself.
1018 =item --name=I<string>
1020 Use the name I<string> instead of the name of the novaboot script.
1021 This name is used for things like a title of grub menu or for the
1022 server directory where the boot files are copied to.
1026 Do not generate files on the fly (i.e. "<" syntax) except for the
1027 files generated via "<<WORD" syntax.
1029 =item -p, --pulsar[=mac]
1031 Generates pulsar bootloader configuration file named F<config-I<mac>>
1032 The I<mac> string is typically a MAC address and defaults to
1035 =item --scons[=scons command]
1037 Runs C<scons> to build files that are not generated by novaboot
1042 Exit novaboot after file generation phase.
1046 =head2 Target connection check
1048 If supported by the target, the connection to it is made and it is
1049 checked whether the target is not occupied by another novaboot
1054 =item --iprelay=I<addr[:port]>
1056 Use TCP/IP relay and serial port to access the target's serial port
1057 and powercycle it. The IP address of the relay is given by I<addr>
1058 parameter. If I<port> is not specified, it default to 23.
1060 Note: This option is supposed to work with HWG-ER02a IP relays.
1062 =item -s, --serial[=device]
1064 Target's serial line is connected to host's serial line (device). The
1065 default value for device is F</dev/ttyUSB0>.
1067 The value of this option is exported in NB_NOVABOOT environment
1068 variable to all subprocesses run by C<novaboot>.
1070 =item --stty=I<settings>
1072 Specifies settings passed to C<stty> invoked on the serial line
1073 specified with B<--serial> option. If this option is not given,
1074 C<stty> is called with C<raw -crtscts -onlcr 115200> settings.
1076 =item --remote-cmd=I<cmd>
1078 Command that mediates connection to the target's serial line. For
1079 example C<ssh server 'cu -l /dev/ttyS0'>.
1081 =item --remote-expect=I<string>
1083 Wait for reception of I<string> on the remote connection before
1088 =head2 File deployment phase
1090 In some setups, it is necessary to copy the files needed for booting
1091 to a particular location, e.g. to a TFTP boot server or to the
1096 =item -d, --dhcp-tftp
1098 Turns your workstation into a DHCP and TFTP server so that the OS can
1099 be booted via PXE BIOS (or similar mechanism) on the test machine
1100 directly connected by a plain Ethernet cable to your workstation.
1102 The DHCP and TFTP servers require root privileges and C<novaboot>
1103 uses C<sudo> command to obtain those. You can put the following to
1104 I</etc/sudoers> to allow running the necessary commands without
1105 asking for password.
1107 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
1108 your_login ALL=NOPASSWD: NOVABOOT
1110 =item -i, --iso[=filename]
1112 Generates the ISO image that boots NOVA system via GRUB. If no filename
1113 is given, the image is stored under I<NAME>.iso, where I<NAME> is the name
1114 of the novaboot script (see also B<--name>).
1116 =item --server[=[[user@]server:]path]
1118 Copy all files needed for booting to another location (implies B<-g>
1119 unless B<--grub2> is given). The files will be copied (by B<rsync>
1120 tool) to the directory I<path>. If the I<path> contains string $NAME,
1121 it will be replaced with the name of the novaboot script (see also
1126 If B<--server> is used and its value ends with $NAME, then after
1127 copying the files, a new bootloader configuration file (e.g. menu.lst)
1128 is created at I<path-wo-name>, i.e. the path specified by B<--server>
1129 with $NAME part removed. The content of the file is created by
1130 concatenating all files of the same name from all subdirectories of
1131 I<path-wo-name> found on the "server".
1133 =item --rsync-flags=I<flags>
1135 Specifies which I<flags> are appended to F<rsync> command line when
1136 copying files as a result of I<--server> option.
1140 =head2 Target power-on and reset phase
1146 Switch on/off the target machine. Currently works only with
1149 =item -Q, --qemu[=I<qemu-binary>]
1151 Boot the configuration in qemu. Optionally, the name of qemu binary
1152 can be specified as a parameter.
1154 =item --qemu-append=I<flags>
1156 Append I<flags> to the default qemu flags (QEMU_FLAGS variable or
1157 C<-cpu coreduo -smp 2>).
1159 =item -q, --qemu-flags=I<flags>
1161 Replace the default qemu flags (QEMU_FLAGS variable or C<-cpu coreduo
1162 -smp 2>) with I<flags> specified here.
1164 =item --reset-cmd=I<cmd>
1166 Command that resets the target.
1170 =head2 Interaction with the bootloader on the target
1176 Interact with uBoot bootloader to boot the thing described in the
1177 novaboot script. Implementation of this option is currently tied to a
1178 particular board that we use. It may be subject to changes in the
1183 Command(s) to send the uBoot bootloader before loading the images and
1188 =head2 Target interaction phase
1190 In this phase, target's serial output is passed to C<novaboot> stdout.
1191 If C<novaboot>'s stdin is on TTY, the stdin is passed to the target
1192 allowing interactive work with the target.
1194 This phase end when the target hangs up or when Ctrl-C is pressed.
1196 =head1 NOVABOOT SCRIPT SYNTAX
1198 The syntax tries to mimic POSIX shell syntax. The syntax is defined with the following rules.
1200 Lines starting with "#" are ignored.
1202 Lines that end with "\" are concatenated with the following line after
1203 removal of the final "\" and leading whitespace of the following line.
1205 Lines in the form I<VARIABLE=...> (i.e. matching '^[A-Z_]+=' regular
1206 expression) assign values to internal variables. See VARIABLES
1209 Otherwise, the first word on the line represents the filename
1210 (relative to the build directory (see B<--build-dir>) of the module to
1211 load and the remaining words are passed as the command line
1214 When the line ends with "<<WORD" then the subsequent lines until the
1215 line containing only WORD are copied literally to the file named on
1218 When the line ends with "< CMD" the command CMD is executed with
1219 C</bin/sh> and its standard output is stored in the file named on that
1220 line. The SRCDIR variable in CMD's environment is set to the absolute
1221 path of the directory containing the interpreted novaboot script.
1224 #!/usr/bin/env novaboot
1225 WVDESC=Example program
1226 bin/apps/sigma0.nul S0_DEFAULT script_start:1,1 \
1227 verbose hostkeyb:0,0x60,1,12,2
1229 hello.nulconfig <<EOF
1230 sigma0::mem:16 name::/s0/log name::/s0/timer name::/s0/fs/rom ||
1231 rom://bin/apps/hello.nul
1234 This example will load three modules: sigma0.nul, hello.nul and
1235 hello.nulconfig. sigma0 gets some command line parameters and
1236 hello.nulconfig file is generated on the fly from the lines between
1241 The following variables are interpreted in the novaboot script:
1247 Novaboot chdir()s to this directory before file generation phase. The
1248 directory name specified here is relative to the build directory
1249 specified by other means (see L</--build-dir>).
1253 Description of the wvtest-compliant program.
1255 =item WVTEST_TIMEOUT
1257 The timeout in seconds for WvTest harness. If no complete line appears
1258 in the test output within the time specified here, the test fails. It
1259 is necessary to specify this for long running tests that produce no
1260 intermediate output.
1264 Use a specific qemu binary (can be overriden with B<-Q>) and flags
1265 when booting this script under qemu. If QEMU_FLAGS variable is also
1266 specified flags specified in QEMU variable are replaced by those in
1271 Use specific qemu flags (can be overriden with B<-q>).
1273 =item HYPERVISOR_PARAMS
1275 Parameters passed to hypervisor. The default value is "serial", unless
1276 overriden in configuration file.
1280 The kernel to use instead of NOVA hypervisor specified in the
1281 configuration file. The value should contain the name of the kernel
1282 image as well as its command line parameters. If this variable is
1283 defined and non-empty, the variable HYPERVISOR_PARAMS is not used.
1287 =head1 CONFIGURATION FILE
1289 Novaboot can read its configuration from one or more files. By
1290 default, novaboot looks for files named F<.novaboot> as described in
1291 L</Configuration reading phase>. Alternatively, its location can be
1292 specified with the B<-c> switch or with the NOVABOOT_CONFIG
1293 environment variable. The configuration file has perl syntax and
1294 should set values of certain Perl variables. The current configuration
1295 can be dumped with the B<--dump-config> switch. Some configuration
1296 variables can be overriden by environment variables (see below) or by
1297 command line switches.
1299 Supporte configuration variables include:
1305 Build directory location relative to the location of the configuration
1308 =item $default_target
1310 Default target (see below) to use when no target is explicitely
1311 specified on command line with the B<--target> option.
1315 Hash of shortcuts to be used with the B<--target> option. If the hash
1316 contains, for instance, the following pair of values
1318 'mybox' => '--server=boot:/tftproot --serial=/dev/ttyUSB0 --grub',
1320 then the following two commands are equivalent:
1322 ./script --server=boot:/tftproot --serial=/dev/ttyUSB0 --grub
1327 =head1 ENVIRONMENT VARIABLES
1329 Some options can be specified not only via config file or command line
1330 but also through environment variables. Environment variables override
1331 the values from configuration file and command line parameters
1332 override the environment variables.
1336 =item NOVABOOT_CONFIG
1338 Name of the novaboot configuration file to use instead of the default
1341 =item NOVABOOT_BENDER
1343 Defining this variable has the same meaning as B<--bender> option.
1349 Michal Sojka <sojka@os.inf.tu-dresden.de>