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}; sleep 1" --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}; sleep 1" --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=192.168.1.1:/tftp --iprelay=192.168.1.2
825 This command copies files to the TFTP server and uses
826 TCP/IP-controlled relay to reset the test box and receive its serial
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 Note that the options needed for a specific target can be stored in a
852 L</"CONFIGURATION FILE"> and then it is sufficient to use only the
853 B<-t> option to specify the name of the target.
855 =head1 PHASES AND OPTIONS
857 Novaboot performs its work in several phases. Each phase can be
858 influenced by several options, certain phases can be skipped. The list
859 of phases (in the execution order) and the corresponding options
862 =head2 Configuration reading phase
864 After starting, novaboot reads configuration files. By default, it
865 searches for files named F<.novaboot> starting from the directory of
866 the novaboot script (or working directory, see bellow) and continuing
867 upwards up to the root directory. The configuration files are read in
868 order from the root directory downwards with latter files overriding
869 settings from the former ones.
871 In certain cases, the location of the novaboot script cannot be
872 determined in this early phase. This happens either when the script is
873 read from the standard input or when novaboot is invoked explicitly
874 and options precede the script name, as in the example L</"4."> above.
875 In this case the current working directory is used as a starting point
876 for configuration file search.
880 =item -c, --config=I<filename>
882 Use the specified configuration file instead of the default one(s).
886 =head2 Command line processing phase
892 Dump the current configuration to stdout end exits. Useful as an
893 initial template for a configuration file.
897 Print short (B<-h>) or long (B<--help>) help.
899 =item -t, --target=I<target>
901 This option serves as a user configurable shortcut for other novaboot
902 options. The effect of this option is the same as the options stored
903 in the C<%targets> configuration variable under key I<target>. See
904 also L</"CONFIGURATION FILE">.
908 =head2 Script preprocessing phase
910 This phases allows to modify the parsed novaboot script before it is
911 used in the later phases.
915 =item -a, --append=I<parameters>
917 Appends a string to the first "filename" line in the novaboot script.
918 This can be used to append parameters to the kernel's or root task's
923 Use F<bender> chainloader. Bender scans the PCI bus for PCI serial
924 ports and stores the information about them in the BIOS data area for
927 =item --chainloader=I<chainloader>
929 Chainloader that is loaded before the kernel and other files specified
930 in the novaboot script. E.g. 'bin/boot/bender promisc'.
934 Prints the content of the novaboot script after removing comments and
935 evaluating all I<--scriptmod> expressions. Exit after reading (and
938 =item --scriptmod=I<perl expression>
940 When novaboot script is read, I<perl expression> is executed for every
941 line (in $_ variable). For example, C<novaboot
942 --scriptmod=s/sigma0/omega6/g> replaces every occurrence of I<sigma0>
943 in the script with I<omega6>.
945 When this option is present, it overrides I<$script_modifier> variable
946 from the configuration file, which has the same effect. If this option
947 is given multiple times all expressions are evaluated in the command
952 Strip I<rom://> prefix from command lines and generated config files.
953 The I<rom://> prefix is used by NUL. For NRE, it has to be stripped.
957 =head2 File generation phase
959 In this phase, files needed for booting are generated in a so called
960 I<build directory> (see TODO). In most cases configuration for a
961 bootloader is generated automatically by novaboot. It is also possible
962 to generate other files using I<heredoc> or I<"<"> syntax in novaboot
963 scripts. Finally, binaries can be generated in this phases by running
968 =item --build-dir=I<directory>
970 Overrides the default build directory location.
972 The default build directory location is determined as follows: If the
973 configuration file defines the C<$builddir> variable, its value is
974 used. Otherwise, it is the directory that contains the first processed
977 =item -g, --grub[=I<filename>]
979 Generates grub bootloader menu file. If the I<filename> is not
980 specified, F<menu.lst> is used. The I<filename> is relative to the
981 build directory (see B<--build-dir>).
983 =item --grub-preamble=I<prefix>
985 Specifies the I<preable> that is at the beginning of the generated
986 GRUB or GRUB2 config files. This is useful for specifying GRUB's
989 =item --grub-prefix=I<prefix>
991 Specifies I<prefix> that is put in front of every file name in GRUB's
992 F<menu.lst>. The default value is the absolute path to the build directory.
994 If the I<prefix> contains string $NAME, it will be replaced with the
995 name of the novaboot script (see also B<--name>).
997 =item --grub2[=I<filename>]
999 Generate GRUB2 menuentry in I<filename>. If I<filename> is not
1000 specified F<grub.cfg> is used. The content of the menuentry can be
1001 customized with B<--grub-preable>, B<--grub2-prolog> or
1002 B<--grub_prefix> options.
1004 In order to use the the generated menuentry on your development
1005 machine that uses GRUB2, append the following snippet to
1006 F</etc/grub.d/40_custom> file and regenerate your grub configuration,
1007 i.e. run update-grub on Debian/Ubuntu.
1009 if [ -f /path/to/nul/build/grub.cfg ]; then
1010 source /path/to/nul/build/grub.cfg
1013 =item --grub2-prolog=I<prolog>
1015 Specifies text I<preable> that is put at the beginning of the entry
1018 =item -m, --make[=make command]
1020 Runs C<make> to build files that are not generated by novaboot itself.
1022 =item --name=I<string>
1024 Use the name I<string> instead of the name of the novaboot script.
1025 This name is used for things like a title of grub menu or for the
1026 server directory where the boot files are copied to.
1030 Do not generate files on the fly (i.e. "<" syntax) except for the
1031 files generated via "<<WORD" syntax.
1033 =item -p, --pulsar[=mac]
1035 Generates pulsar bootloader configuration file named F<config-I<mac>>
1036 The I<mac> string is typically a MAC address and defaults to
1039 =item --scons[=scons command]
1041 Runs C<scons> to build files that are not generated by novaboot
1046 Exit novaboot after file generation phase.
1050 =head2 Target connection check
1052 If supported by the target, the connection to it is made and it is
1053 checked whether the target is not occupied by another novaboot
1058 =item --iprelay=I<addr[:port]>
1060 Use TCP/IP relay and serial port to access the target's serial port
1061 and powercycle it. The IP address of the relay is given by I<addr>
1062 parameter. If I<port> is not specified, it default to 23.
1064 Note: This option is supposed to work with HWG-ER02a IP relays.
1066 =item -s, --serial[=device]
1068 Target's serial line is connected to host's serial line (device). The
1069 default value for device is F</dev/ttyUSB0>.
1071 The value of this option is exported in NB_NOVABOOT environment
1072 variable to all subprocesses run by C<novaboot>.
1074 =item --stty=I<settings>
1076 Specifies settings passed to C<stty> invoked on the serial line
1077 specified with B<--serial> option. If this option is not given,
1078 C<stty> is called with C<raw -crtscts -onlcr 115200> settings.
1080 =item --remote-cmd=I<cmd>
1082 Command that mediates connection to the target's serial line. For
1083 example C<ssh server 'cu -l /dev/ttyS0'>.
1085 =item --remote-expect=I<string>
1087 Wait for reception of I<string> on the remote connection before
1092 =head2 File deployment phase
1094 In some setups, it is necessary to copy the files needed for booting
1095 to a particular location, e.g. to a TFTP boot server or to the
1100 =item -d, --dhcp-tftp
1102 Turns your workstation into a DHCP and TFTP server so that the OS can
1103 be booted via PXE BIOS (or similar mechanism) on the test machine
1104 directly connected by a plain Ethernet cable to your workstation.
1106 The DHCP and TFTP servers require root privileges and C<novaboot>
1107 uses C<sudo> command to obtain those. You can put the following to
1108 I</etc/sudoers> to allow running the necessary commands without
1109 asking for password.
1111 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
1112 your_login ALL=NOPASSWD: NOVABOOT
1114 =item -i, --iso[=filename]
1116 Generates the ISO image that boots NOVA system via GRUB. If no filename
1117 is given, the image is stored under I<NAME>.iso, where I<NAME> is the name
1118 of the novaboot script (see also B<--name>).
1120 =item --server[=[[user@]server:]path]
1122 Copy all files needed for booting to another location (implies B<-g>
1123 unless B<--grub2> is given). The files will be copied (by B<rsync>
1124 tool) to the directory I<path>. If the I<path> contains string $NAME,
1125 it will be replaced with the name of the novaboot script (see also
1130 If B<--server> is used and its value ends with $NAME, then after
1131 copying the files, a new bootloader configuration file (e.g. menu.lst)
1132 is created at I<path-wo-name>, i.e. the path specified by B<--server>
1133 with $NAME part removed. The content of the file is created by
1134 concatenating all files of the same name from all subdirectories of
1135 I<path-wo-name> found on the "server".
1137 =item --rsync-flags=I<flags>
1139 Specifies which I<flags> are appended to F<rsync> command line when
1140 copying files as a result of I<--server> option.
1144 =head2 Target power-on and reset phase
1150 Switch on/off the target machine. Currently works only with
1153 =item -Q, --qemu[=I<qemu-binary>]
1155 Boot the configuration in qemu. Optionally, the name of qemu binary
1156 can be specified as a parameter.
1158 =item --qemu-append=I<flags>
1160 Append I<flags> to the default qemu flags (QEMU_FLAGS variable or
1161 C<-cpu coreduo -smp 2>).
1163 =item -q, --qemu-flags=I<flags>
1165 Replace the default qemu flags (QEMU_FLAGS variable or C<-cpu coreduo
1166 -smp 2>) with I<flags> specified here.
1168 =item --reset-cmd=I<cmd>
1170 Command that resets the target.
1174 =head2 Interaction with the bootloader on the target
1180 Interact with uBoot bootloader to boot the thing described in the
1181 novaboot script. Implementation of this option is currently tied to a
1182 particular board that we use. It may be subject to changes in the
1187 Command(s) to send the uBoot bootloader before loading the images and
1192 =head2 Target interaction phase
1194 In this phase, target's serial output is passed to C<novaboot> stdout.
1195 If C<novaboot>'s stdin is on TTY, the stdin is passed to the target
1196 allowing interactive work with the target.
1198 This phase end when the target hangs up or when Ctrl-C is pressed.
1200 =head1 NOVABOOT SCRIPT SYNTAX
1202 The syntax tries to mimic POSIX shell syntax. The syntax is defined with the following rules.
1204 Lines starting with "#" are ignored.
1206 Lines that end with "\" are concatenated with the following line after
1207 removal of the final "\" and leading whitespace of the following line.
1209 Lines in the form I<VARIABLE=...> (i.e. matching '^[A-Z_]+=' regular
1210 expression) assign values to internal variables. See VARIABLES
1213 Otherwise, the first word on the line represents the filename
1214 (relative to the build directory (see B<--build-dir>) of the module to
1215 load and the remaining words are passed as the command line
1218 When the line ends with "<<WORD" then the subsequent lines until the
1219 line containing only WORD are copied literally to the file named on
1222 When the line ends with "< CMD" the command CMD is executed with
1223 C</bin/sh> and its standard output is stored in the file named on that
1224 line. The SRCDIR variable in CMD's environment is set to the absolute
1225 path of the directory containing the interpreted novaboot script.
1228 #!/usr/bin/env novaboot
1229 WVDESC=Example program
1230 bin/apps/sigma0.nul S0_DEFAULT script_start:1,1 \
1231 verbose hostkeyb:0,0x60,1,12,2
1233 hello.nulconfig <<EOF
1234 sigma0::mem:16 name::/s0/log name::/s0/timer name::/s0/fs/rom ||
1235 rom://bin/apps/hello.nul
1238 This example will load three modules: sigma0.nul, hello.nul and
1239 hello.nulconfig. sigma0 gets some command line parameters and
1240 hello.nulconfig file is generated on the fly from the lines between
1245 The following variables are interpreted in the novaboot script:
1251 Novaboot chdir()s to this directory before file generation phase. The
1252 directory name specified here is relative to the build directory
1253 specified by other means (see L</--build-dir>).
1257 Description of the wvtest-compliant program.
1259 =item WVTEST_TIMEOUT
1261 The timeout in seconds for WvTest harness. If no complete line appears
1262 in the test output within the time specified here, the test fails. It
1263 is necessary to specify this for long running tests that produce no
1264 intermediate output.
1268 Use a specific qemu binary (can be overriden with B<-Q>) and flags
1269 when booting this script under qemu. If QEMU_FLAGS variable is also
1270 specified flags specified in QEMU variable are replaced by those in
1275 Use specific qemu flags (can be overriden with B<-q>).
1277 =item HYPERVISOR_PARAMS
1279 Parameters passed to hypervisor. The default value is "serial", unless
1280 overriden in configuration file.
1284 The kernel to use instead of NOVA hypervisor specified in the
1285 configuration file. The value should contain the name of the kernel
1286 image as well as its command line parameters. If this variable is
1287 defined and non-empty, the variable HYPERVISOR_PARAMS is not used.
1291 =head1 CONFIGURATION FILE
1293 Novaboot can read its configuration from one or more files. By
1294 default, novaboot looks for files named F<.novaboot> as described in
1295 L</Configuration reading phase>. Alternatively, its location can be
1296 specified with the B<-c> switch or with the NOVABOOT_CONFIG
1297 environment variable. The configuration file has perl syntax and
1298 should set values of certain Perl variables. The current configuration
1299 can be dumped with the B<--dump-config> switch. Some configuration
1300 variables can be overriden by environment variables (see below) or by
1301 command line switches.
1303 Supported configuration variables include:
1309 Build directory location relative to the location of the configuration
1312 =item $default_target
1314 Default target (see below) to use when no target is explicitly
1315 specified on command line with the B<--target> option.
1319 Hash of shortcuts to be used with the B<--target> option. If the hash
1320 contains, for instance, the following pair of values
1322 'mybox' => '--server=boot:/tftproot --serial=/dev/ttyUSB0 --grub',
1324 then the following two commands are equivalent:
1326 ./script --server=boot:/tftproot --serial=/dev/ttyUSB0 --grub
1331 =head1 ENVIRONMENT VARIABLES
1333 Some options can be specified not only via config file or command line
1334 but also through environment variables. Environment variables override
1335 the values from configuration file and command line parameters
1336 override the environment variables.
1340 =item NOVABOOT_CONFIG
1342 Name of the novaboot configuration file to use instead of the default
1345 =item NOVABOOT_BENDER
1347 Defining this variable has the same meaning as B<--bender> option.
1353 Michal Sojka <sojka@os.inf.tu-dresden.de>