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 = $ENV{PWD} || 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 || -l $dir ) && $dir ne "/") {
87 push @cfgs, "$dir/.novaboot" if -r "$dir/.novaboot";
88 my @dirs = File::Spec->splitdir($dir);
89 $dir = File::Spec->catdir(@dirs[0..$#dirs-1]);
92 my $cfg = $ENV{'NOVABOOT_CONFIG'};
93 Getopt::Long::Configure(qw/no_ignore_case pass_through/);
94 GetOptions ("config|c=s" => \$cfg);
95 read_config($_) foreach $cfg or reverse @cfgs;
97 ## Command line handling
100 GetOptions ("target|t=s" => \$explicit_target);
102 my ($append, $bender, @chainloaders, $concat, $config_name_opt, $dhcp_tftp, $dump_opt, $dump_config, @exiton, $gen_only, $grub_config, $grub_prefix, $grub_preamble, $grub2_prolog, $grub2_config, $help, $iprelay, $iso_image, $interactive, $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);
105 $rom_prefix = 'rom://';
106 $stty = 'raw -crtscts -onlcr 115200';
108 Getopt::Long::Configure(qw/no_ignore_case no_pass_through/);
111 "append|a=s" => \$append,
112 "bender|b" => \$bender,
113 "build-dir=s" => sub { my ($n, $v) = @_; $builddir = File::Spec->rel2abs($v); },
114 "concat" => \$concat,
115 "chainloader=s" => \@chainloaders,
116 "dhcp-tftp|d" => \$dhcp_tftp,
117 "dump" => \$dump_opt,
118 "dump-config" => \$dump_config,
119 "exiton=s" => \@exiton,
120 "gen-only" => \$gen_only,
121 "grub|g:s" => \$grub_config,
122 "grub-preamble=s"=> \$grub_preamble,
123 "grub-prefix=s" => \$grub_prefix,
124 "grub2:s" => \$grub2_config,
125 "grub2-prolog=s" => \$grub2_prolog,
126 "iprelay=s" => \$iprelay,
127 "iso:s" => \$iso_image,
128 "interactive|i" => \$interactive,
129 "name=s" => \$config_name_opt,
130 "make|m:s" => \$make,
131 "no-file-gen" => \$no_file_gen,
134 "pulsar|p:s" => \$pulsar,
135 "pulsar-root=s" => \$pulsar_root,
136 "qemu|Q:s" => \$qemu,
137 "qemu-append=s" => \$qemu_append,
138 "qemu-flags|q=s" => \$qemu_flags_cmd,
139 "remote-cmd=s" => \$remote_cmd,
140 "remote-expect=s"=> \$remote_expect,
141 "reset-cmd=s" => \$reset_cmd,
142 "rsync-flags=s" => \$rsync_flags,
143 "scons:s" => \$scons,
144 "scriptmod=s" => \@scriptmod,
145 "serial|s:s" => \$serial,
146 "server:s" => \$server,
147 "strip-rom" => sub { $rom_prefix = ''; },
150 "uboot-init=s" => \$uboot_init,
155 # First process target options
157 my $t = defined($explicit_target) ? $explicit_target : $CFG::default_target;
159 exists $CFG::targets{$t} or die("Unknown target '$t' (valid targets are: ".join(", ", sort keys(%CFG::targets)).")");
160 GetOptionsFromString($CFG::targets{$t}, %opt_spec);
164 # Then process other command line options - some of them may override
165 # what was specified by the target
166 GetOptions %opt_spec or die("Error in command line arguments");
167 pod2usage(1) if $help;
168 pod2usage(-exitstatus => 0, -verbose => 2) if $man;
170 ### Dump sanitized configuration (if requested)
174 $Data::Dumper::Indent=1;
175 print "# This file is in perl syntax.\n";
176 foreach my $key(sort(keys(%CFG::))) { # See "Symbol Tables" in perlmod(1)
177 if (defined ${$CFG::{$key}}) { print Data::Dumper->Dump([${$CFG::{$key}}], ["*$key"]); }
178 if ( @{$CFG::{$key}}) { print Data::Dumper->Dump([\@{$CFG::{$key}}], ["*$key"]); }
179 if ( %{$CFG::{$key}}) { print Data::Dumper->Dump([\%{$CFG::{$key}}], ["*$key"]); }
185 ### Sanitize configuration
187 if ($interactive && !-t STDIN) {
188 die("novaboot: Interactive mode not supported when not on terminal");
191 if (defined $config_name_opt && scalar(@ARGV) > 1) { die "You cannot use --name with multiple scripts"; }
194 if (defined $serial) {
195 $serial ||= "/dev/ttyUSB0";
196 $ENV{NB_SERIAL} = $serial;
198 if (defined $grub_config) { $grub_config ||= "menu.lst"; }
199 if (defined $grub2_config) { $grub2_config ||= "grub.cfg"; }
201 ## Parse the novaboot script(s)
207 my ($modules, $variables, $generated, $continuation);
209 if ($ARGV ne $last_fn) { # New script
210 die "Missing EOF in $last_fn" if $file;
211 die "Unfinished line in $last_fn" if $line;
213 push @scripts, { 'filename' => $ARGV,
214 'modules' => $modules = [],
215 'variables' => $variables = {},
216 'generated' => $generated = []};
220 next if /^#/ || /^\s*$/; # Skip comments and empty lines
222 foreach my $mod(@scriptmod) { eval $mod; }
224 print "$_\n" if $dump_opt;
226 if (/^([A-Z_]+)=(.*)$/) { # Internal variable
227 $$variables{$1} = $2;
228 push(@exiton, $2) if ($1 eq "EXITON");
231 if (/^([^ ]*)(.*?)[[:space:]]*<<([^ ]*)$/) { # Heredoc start
232 push @$modules, "$1$2";
234 push @$generated, {filename => $1, content => $file};
238 if ($file && $_ eq $EOF) { # Heredoc end
242 if ($file) { # Heredoc content
243 push @{$file}, "$_\n";
246 $_ =~ s/^[[:space:]]*// if ($continuation);
247 if (/\\$/) { # Line continuation
248 $line .= substr($_, 0, length($_)-1);
254 $line .= " $append" if ($append && scalar(@$modules) == 0);
256 if ($line =~ /^([^ ]*)(.*?)[[:space:]]*< ?(.*)$/) { # Command substitution
257 push @$modules, "$1$2";
258 push @$generated, {filename => $1, command => $3};
262 push @$modules, $line;
266 #print Dumper(\@scripts);
272 sub generate_configs($$$) {
273 my ($base, $generated, $filename) = @_;
274 if ($base) { $base = "$base/"; };
275 foreach my $g(@$generated) {
276 if (exists $$g{content}) {
277 my $config = $$g{content};
278 my $fn = $$g{filename};
279 open(my $f, '>', $fn) || die("$fn: $!");
280 map { s|\brom://([^ ]*)|$rom_prefix$base$1|g; print $f "$_"; } @{$config};
282 print "novaboot: Created $fn\n";
283 } elsif (exists $$g{command} && ! $no_file_gen) {
284 $ENV{SRCDIR} = dirname(File::Spec->rel2abs( $filename, $invocation_dir ));
285 system_verbose("( $$g{command} ) > $$g{filename}");
290 sub generate_grub_config($$$$;$)
292 my ($filename, $title, $base, $modules_ref, $preamble) = @_;
293 if ($base) { $base = "$base/"; };
294 open(my $fg, '>', $filename) or die "$filename: $!";
295 print $fg "$preamble\n" if $preamble;
296 print $fg "title $title\n" if $title;
297 #print $fg "root $base\n"; # root doesn't really work for (nd)
299 foreach (@$modules_ref) {
302 my ($kbin, $kcmd) = split(' ', $_, 2);
303 $kcmd = '' if !defined $kcmd;
304 print $fg "kernel ${base}$kbin $kcmd\n";
306 s|\brom://([^ ]*)|$rom_prefix$base$1|g; # Translate rom:// files - needed for vdisk parameter of sigma0
307 print $fg "module $base$_\n";
311 print("novaboot: Created $builddir/$filename\n");
315 sub generate_grub2_config($$$$;$$)
317 my ($filename, $title, $base, $modules_ref, $preamble, $prolog) = @_;
318 if ($base && substr($base,-1,1) ne '/') { $base = "$base/"; };
319 open(my $fg, '>', $filename) or die "$filename: $!";
320 print $fg "$preamble\n" if $preamble;
321 $title ||= 'novaboot';
322 print $fg "menuentry $title {\n";
323 print $fg "$prolog\n" if $prolog;
325 foreach (@$modules_ref) {
328 my ($kbin, $kcmd) = split(' ', $_, 2);
329 $kcmd = '' if !defined $kcmd;
330 print $fg " multiboot ${base}$kbin $kcmd\n";
333 # GRUB2 doesn't pass filename in multiboot info so we have to duplicate it here
334 $_ = join(' ', ($args[0], @args));
335 s|\brom://|$rom_prefix|g; # We do not need to translate path for GRUB2
336 print $fg " module $base$_\n";
341 print("novaboot: Created $builddir/$filename\n");
345 sub generate_pulsar_config($$)
347 my ($filename, $modules_ref) = @_;
348 open(my $fg, '>', $filename) or die "$filename: $!";
349 print $fg "root $pulsar_root\n" if defined $pulsar_root;
352 foreach (@$modules_ref) {
355 ($kbin, $kcmd) = split(' ', $_, 2);
356 $kcmd = '' if !defined $kcmd;
359 s|\brom://|$rom_prefix|g;
360 print $fg "load $_\n";
363 # Put kernel as last - this is needed for booting Linux and has no influence on non-Linux OSes
364 print $fg "exec $kbin $kcmd\n";
366 print("novaboot: Created $builddir/$filename\n");
370 sub shell_cmd_string(@)
372 return join(' ', map((/^[-_=a-zA-Z0-9\/\.\+]+$/ ? "$_" : "'$_'"), @_));
377 print "novaboot: Running: ".shell_cmd_string(@_)."\n";
381 sub system_verbose($)
384 print "novaboot: Running: $cmd\n";
385 my $ret = system($cmd);
386 if ($ret & 0x007f) { die("Command terminated by a signal"); }
387 if ($ret & 0xff00) {die("Command exit with non-zero exit code"); }
388 if ($ret) { die("Command failure $ret"); }
393 if (exists $variables->{WVDESC}) {
394 print "Testing \"$variables->{WVDESC}\" in $last_fn:\n";
395 } elsif ($last_fn =~ /\.wv$/) {
396 print "Testing \"all\" in $last_fn:\n";
399 ## Connect to the target and check whether is not occupied
401 # We have to do this before file generation phase, because file
402 # generation is intermixed with file deployment phase and we want to
403 # check whether the target is not used by somebody else before
404 # deploying files. Otherwise, we may rewrite other user's files on a
407 my $exp; # Expect object to communicate with the target over serial line
409 my ($target_reset, $target_power_on, $target_power_off);
411 if (defined $iprelay) {
413 $iprelay =~ /([.0-9]+)(:([0-9]+))?/;
416 my $paddr = sockaddr_in($port, inet_aton($addr));
417 my $proto = getprotobyname('tcp');
418 socket($IPRELAY, PF_INET, SOCK_STREAM, $proto) || die "socket: $!";
419 print "novaboot: Connecting to IP relay... ";
420 connect($IPRELAY, $paddr) || die "connect: $!";
422 $exp = Expect->init(\*$IPRELAY);
426 print $exp "\xFF\xF6"; # AYT
427 my $connected = $exp->expect(20, # Timeout in seconds
428 '<iprelayd: connected>',
429 '-re', '<WEB51 HW[^>]*>');
434 my ($relay, $onoff) = @_;
435 die unless ($relay == 1 || $relay == 2);
437 my $cmd = ($relay == 1 ? 0x5 : 0x6) | ($onoff ? 0x20 : 0x10);
438 return "\xFF\xFA\x2C\x32".chr($cmd)."\xFF\xF0";
442 my ($relay, $onoff) = @_;
443 die unless ($relay == 1 || $relay == 2);
444 my $cmd = ($relay == 1 ? 0xdf : 0xbf) | ($onoff ? 0x00 : 0xff);
445 return "\xFF\xFA\x2C\x97".chr($cmd)."\xFF\xF0";
449 my ($relay, $onoff, $can_giveup) = @_;
450 my $confirmation = '';
452 print $exp relaycmd($relay, $onoff);
453 my $confirmed = $exp->expect(20, # Timeout in seconds
454 relayconf($relay, $onoff));
457 print("Relay confirmation timeout - ignoring\n");
459 die "Relay confirmation timeout";
465 $target_reset = sub {
466 relay(2, 1, 1); # Reset the machine
471 $target_power_off = sub {
472 relay(1, 1); # Press power button
473 usleep(6000000); # Long press to switch off
477 $target_power_on = sub {
478 relay(1, 1); # Press power button
479 usleep(100000); # Short press
485 system_verbose("stty -F $serial $stty");
486 open($CONN, "+<", $serial) || die "open $serial: $!";
487 $exp = Expect->init(\*$CONN);
488 } elsif ($remote_cmd) {
489 print "novaboot: Running: $remote_cmd\n";
490 $exp = Expect->spawn($remote_cmd);
493 if ($remote_expect) {
494 $exp->expect(10, $remote_expect) || die "Expect for '$remote_expect' timed out";
497 if (defined $reset_cmd) {
498 $target_reset = sub {
499 system_verbose($reset_cmd);
503 if (defined $on_opt && defined $target_power_on) {
507 if (defined $off_opt && defined $target_power_off) {
508 print "novaboot: Switching the target off...\n";
509 &$target_power_off();
513 $builddir ||= dirname(File::Spec->rel2abs( ${$scripts[0]}{filename})) if scalar @scripts;
514 if (defined $builddir) {
515 chdir($builddir) or die "Can't change directory to $builddir: $!";
516 print "novaboot: Entering directory `$builddir'\n";
519 ## File generation phase
520 my (%files_iso, $menu_iso, $filename);
521 my $config_name = '';
523 foreach my $script (@scripts) {
524 $filename = $$script{filename};
525 $modules = $$script{modules};
526 $generated = $$script{generated};
527 $variables = $$script{variables};
529 ($config_name = $filename) =~ s#.*/##;
530 $config_name = $config_name_opt if (defined $config_name_opt);
532 if (exists $variables->{BUILDDIR}) {
533 $builddir = File::Spec->rel2abs($variables->{BUILDDIR});
534 chdir($builddir) or die "Can't change directory to $builddir: $!";
535 print "novaboot: Entering directory `$builddir'\n";
539 if (exists $variables->{KERNEL}) {
540 $kernel = $variables->{KERNEL};
542 if ($CFG::hypervisor) {
543 $kernel = $CFG::hypervisor . " ";
544 if (exists $variables->{HYPERVISOR_PARAMS}) {
545 $kernel .= $variables->{HYPERVISOR_PARAMS};
547 $kernel .= $CFG::hypervisor_params;
551 @$modules = ($kernel, @$modules) if $kernel;
552 @$modules = (@chainloaders, @$modules);
553 @$modules = ("bin/boot/bender", @$modules) if ($bender || defined $ENV{'NOVABOOT_BENDER'});
556 ($prefix = $grub_prefix) =~ s/\$NAME/$config_name/ if defined $grub_prefix;
557 $prefix ||= $builddir;
558 # TODO: use $grub_prefix as first parameter if some switch is given
559 generate_configs('', $generated, $filename);
561 ### Generate bootloader configuration files
562 my @bootloader_configs;
563 push @bootloader_configs, generate_grub_config($grub_config, $config_name, $prefix, $modules, $grub_preamble) if (defined $grub_config);
564 push @bootloader_configs, generate_grub2_config($grub2_config, $config_name, $prefix, $modules, $grub_preamble, $grub2_prolog) if (defined $grub2_config);
565 push @bootloader_configs, generate_pulsar_config('config-'.($pulsar||'novaboot'), $modules) if (defined $pulsar);
567 ### Run scons or make
569 my @files = map({ ($file) = m/([^ ]*)/; $file; } @$modules);
570 # Filter-out generated files
571 my @to_build = grep({ my $file = $_; !scalar(grep($file eq $$_{filename}, @$generated)) } @files);
573 system_verbose($scons || $CFG::scons." ".join(" ", @to_build)) if (defined $scons);
574 system_verbose($make || $CFG::make ." ".join(" ", @to_build)) if (defined $make);
577 ### Copy files (using rsync)
578 if (defined $server && !defined($gen_only)) {
579 (my $real_server = $server) =~ s/\$NAME/$config_name/;
581 my ($hostname, $path) = split(":", $real_server, 2);
582 if (! defined $path) {
586 my $files = join(" ", map({ ($file) = m/([^ ]*)/; $file; } ( @$modules, @bootloader_configs)));
587 map({ my $file = (split)[0]; die "$file: $!" if ! -f $file; } @$modules);
588 my $istty = -t STDOUT && ($ENV{'TERM'} || 'dumb') ne 'dumb';
589 my $progress = $istty ? "--progress" : "";
590 system_verbose("rsync $progress -RLp $rsync_flags $files $real_server");
591 if ($server =~ m|/\$NAME$| && $concat) {
592 my $cmd = join("; ", map { "( cd $path/.. && cat */$_ > $_ )" } @bootloader_configs);
593 system_verbose($hostname ? "ssh $hostname '$cmd'" : $cmd);
597 ### Prepare ISO image generation
598 if (defined $iso_image) {
599 generate_configs("(cd)", $generated, $filename);
601 generate_grub_config(\$menu, $config_name, "(cd)", $modules);
602 $menu_iso .= "$menu\n";
603 map { ($file,undef) = split; $files_iso{$file} = 1; } @$modules;
607 ## Generate ISO image
608 if (defined $iso_image) {
609 open(my $fh, ">menu-iso.lst");
610 print $fh "timeout 5\n\n$menu_iso";
612 my $files = "boot/grub/menu.lst=menu-iso.lst " . join(" ", map("$_=$_", keys(%files_iso)));
613 $iso_image ||= "$config_name.iso";
614 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");
615 print("ISO image created: $builddir/$iso_image\n");
618 exit(0) if defined $gen_only;
620 ## Boot the system using various methods and send serial output to stdout
622 if (scalar(@scripts) > 1 && ( defined $dhcp_tftp || defined $serial || defined $iprelay)) {
623 die "You cannot do this with multiple scripts simultaneously";
626 if ($variables->{WVTEST_TIMEOUT}) {
627 print "wvtest: timeout ", $variables->{WVTEST_TIMEOUT}, "\n";
632 $str =~ s/^\s+|\s+$//g;
640 $qemu ||= $variables->{QEMU} || $CFG::qemu;
641 my @qemu_flags = split(" ", $qemu);
642 $qemu = shift(@qemu_flags);
644 @qemu_flags = split(/ +/, trim($variables->{QEMU_FLAGS})) if exists $variables->{QEMU_FLAGS};
645 @qemu_flags = split(/ +/, trim($qemu_flags_cmd)) if $qemu_flags_cmd;
646 push(@qemu_flags, split(/ +/, trim($qemu_append || '')));
648 if (defined $iso_image) {
649 # Boot NOVA with grub (and test the iso image)
650 push(@qemu_flags, ('-cdrom', "$config_name.iso"));
652 # Boot NOVA without GRUB
654 # Non-patched qemu doesn't like commas, but NUL can live with pluses instead of commans
655 foreach (@$modules) {s/,/+/g;}
656 generate_configs("", $generated, $filename);
658 if (scalar @$modules) {
659 my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);
660 $kcmd = '' if !defined $kcmd;
662 @$modules = map { if (/\.dtb$/) { $dtb=$_; (); } else { $_ } } @$modules;
663 my $initrd = join ",", @$modules;
665 push(@qemu_flags, ('-kernel', $kbin, '-append', $kcmd));
666 push(@qemu_flags, ('-initrd', $initrd)) if $initrd;
667 push(@qemu_flags, ('-dtb', $dtb)) if $dtb;
670 push(@qemu_flags, qw(-serial stdio)); # Redirect serial output (for collecting test restuls)
671 unshift(@qemu_flags, ('-name', $config_name));
672 print "novaboot: Running: ".shell_cmd_string($qemu, @qemu_flags)."\n";
673 $exp = Expect->spawn(($qemu, @qemu_flags)) || die("exec() failed: $!");
676 ### Local DHCPD and TFTPD
678 my ($dhcpd_pid, $tftpd_pid);
680 if (defined $dhcp_tftp)
682 generate_configs("(nd)", $generated, $filename);
683 system_verbose('mkdir -p tftpboot');
684 generate_grub_config("tftpboot/os-menu.lst", $config_name, "(nd)", \@$modules, "timeout 0");
685 open(my $fh, '>', 'dhcpd.conf');
686 my $mac = `cat /sys/class/net/eth0/address`;
688 print $fh "subnet 10.23.23.0 netmask 255.255.255.0 {
689 range 10.23.23.10 10.23.23.100;
690 filename \"bin/boot/grub/pxegrub.pxe\";
691 next-server 10.23.23.1;
694 hardware ethernet $mac;
695 fixed-address 10.23.23.1;
698 system_verbose("sudo ip a add 10.23.23.1/24 dev eth0;
699 sudo ip l set dev eth0 up;
700 sudo touch dhcpd.leases");
702 # We run servers by forking ourselves, because the servers end up
703 # in our process group and get killed by signals sent to the
704 # process group (e.g. Ctrl-C on terminal).
706 exec_verbose("sudo dhcpd -d -cf dhcpd.conf -lf dhcpd.leases -pf dhcpd.pid") if ($dhcpd_pid == 0);
708 exec_verbose("sudo in.tftpd --foreground --secure -v -v -v --pidfile tftpd.pid $builddir") if ($tftpd_pid == 0);
710 # Kill server when we die
711 $SIG{__DIE__} = sub { system_verbose('sudo pkill --pidfile=dhcpd.pid');
712 system_verbose('sudo pkill --pidfile=tftpd.pid'); };
715 ### Serial line or IP relay
717 if (defined $target_reset) {
718 print "novaboot: Reseting the test box... ";
723 if (defined $uboot) {
724 print "novaboot: Waiting for uBoot prompt...\n";
726 #$exp->exp_internal(1);
728 [qr/Hit any key to stop autoboot:/, sub { $exp->send("\n"); exp_continue; }],
729 '=> ') || die "No uBoot prompt deteceted";
730 $exp->send("$uboot_init\n") if $uboot_init;
731 $exp->expect(10, '=> ') || die "uBoot prompt timeout";
733 my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);
735 @$modules = map { if (/\.dtb$/) { $dtb=$_; (); } else { $_ } } @$modules;
736 my $initrd = shift @$modules;
738 my $kern_addr = '800000';
739 my $initrd_addr = '-';
742 $exp->send("tftp $kern_addr $kbin\n");
744 [qr/#/, sub { exp_continue; }],
745 '=> ') || die "Kernel load failed";
747 $dtb_addr = '7f0000';
748 $exp->send("tftp $dtb_addr $dtb\n");
750 [qr/#/, sub { exp_continue; }],
751 '=> ') || die "Device tree load failed";
753 if (defined $initrd) {
754 $initrd_addr = 'b00000';
755 $exp->send("tftp $initrd_addr $initrd\n");
757 [qr/#/, sub { exp_continue; }],
758 '=> ') || die "Initrd load failed";
760 $exp->send("set bootargs '$kcmd'\n");
761 $exp->expect(5, '=> ') || die "uBoot prompt timeout";
762 $exp->send("bootm $kern_addr $initrd_addr $dtb_addr\n");
763 $exp->expect(5, "\n") || die "uBoot command timeout";
767 # Serial line of the target is available
768 my $interrupt = 'Ctrl-C';
769 if ($interactive && !@exiton) {
770 $interrupt = '"~~."';
772 print "novaboot: Serial line interaction (press $interrupt to interrupt)...\n";
775 $exp->expect(undef, @exiton);
778 if (-t STDIN) { # Set up bi-directional communication if we run on terminal
779 my $infile = new IO::File;
780 $infile->IO::File::fdopen(*STDIN,'r');
781 my $in_object = Expect->exp_init($infile);
782 $in_object->set_group($exp);
785 $in_object->set_seq('~~\.', sub { print "novaboot: Escape sequence detected\r\n"; undef; });
786 $in_object->manual_stty(0); # Use raw terminal mode
788 $in_object->manual_stty(1); # Do not modify terminal settings
790 push(@inputs, $in_object);
792 Expect::interconnect(@inputs);
796 ## Kill dhcpc or tftpd
797 if (defined $dhcp_tftp) {
798 die("novaboot: This should kill servers on background\n");
805 novaboot - A tool for booting various operating systems on various hardware or in qemu
809 B<novaboot> [ options ] [--] script...
811 B<./script> [ options ]
815 This program makes it easier to boot NOVA or other operating system
816 (OS) on different targets (machines or emulators). It reads a so
817 called novaboot script, that specifies the boot configuration, and
818 setups the target to boot that configuration. Setting up the target
819 means to generate the bootloader configuration files, deploy the
820 binaries and other needed files to proper locations, perhaps on a
821 remote boot server and reset the target. Then, target's serial output
822 is redirected to standard output if that is possible.
824 A typical way of using novaboot is to make the novaboot script
825 executable and set its first line to I<#!/usr/bin/env novaboot>. Then,
826 booting a particular OS configuration becomes the same as executing a
827 local program - the novaboot script.
829 For example, with C<novaboot> you can:
835 Run an OS in Qemu. This is the default action when no other action is
836 specified by command line switches. Thus running C<novaboot ./script>
837 (or C<./script> as described above) will run Qemu and make it boot the
838 configuration specified in the I<script>.
842 Create a bootloader configuration file (currently supported
843 bootloaders are GRUB, GRUB2, Pulsar and uBoot) and copy it with all
844 other files needed for booting to another, perhaps remote, location.
846 ./script --server=192.168.1.1:/tftp --iprelay=192.168.1.2
848 This command copies files to the TFTP server and uses
849 TCP/IP-controlled relay to reset the test box and receive its serial
854 Run DHCP and TFTP server on developer's machine to PXE-boot the OS
859 When a PXE-bootable machine is connected via Ethernet to developer's
860 machine, it will boot the configuration described in I<script>.
864 Create bootable ISO images. E.g.
866 novaboot --iso -- script1 script2
868 The created ISO image will have GRUB bootloader installed on it and
869 the boot menu will allow selecting between I<script1> and I<script2>
874 Note that the options needed for a specific target can be stored in a
875 L</"CONFIGURATION FILE"> and then it is sufficient to use only the
876 B<-t> option to specify the name of the target.
878 =head1 PHASES AND OPTIONS
880 Novaboot performs its work in several phases. Each phase can be
881 influenced by several options, certain phases can be skipped. The list
882 of phases (in the execution order) and the corresponding options
885 =head2 Configuration reading phase
887 After starting, novaboot reads configuration files. By default, it
888 searches for files named F<.novaboot> starting from the directory of
889 the novaboot script (or working directory, see bellow) and continuing
890 upwards up to the root directory. The configuration files are read in
891 order from the root directory downwards with latter files overriding
892 settings from the former ones.
894 In certain cases, the location of the novaboot script cannot be
895 determined in this early phase. This happens either when the script is
896 read from the standard input or when novaboot is invoked explicitly
897 and options precede the script name, as in the example L</"4."> above.
898 In this case the current working directory is used as a starting point
899 for configuration file search.
903 =item -c, --config=I<filename>
905 Use the specified configuration file instead of the default one(s).
909 =head2 Command line processing phase
915 Dump the current configuration to stdout end exits. Useful as an
916 initial template for a configuration file.
920 Print short (B<-h>) or long (B<--help>) help.
922 =item -t, --target=I<target>
924 This option serves as a user configurable shortcut for other novaboot
925 options. The effect of this option is the same as the options stored
926 in the C<%targets> configuration variable under key I<target>. See
927 also L</"CONFIGURATION FILE">.
931 =head2 Script preprocessing phase
933 This phases allows to modify the parsed novaboot script before it is
934 used in the later phases.
938 =item -a, --append=I<parameters>
940 Appends a string to the first "filename" line in the novaboot script.
941 This can be used to append parameters to the kernel's or root task's
946 Use F<bender> chainloader. Bender scans the PCI bus for PCI serial
947 ports and stores the information about them in the BIOS data area for
950 =item --chainloader=I<chainloader>
952 Chainloader that is loaded before the kernel and other files specified
953 in the novaboot script. E.g. 'bin/boot/bender promisc'.
957 Prints the content of the novaboot script after removing comments and
958 evaluating all I<--scriptmod> expressions. Exit after reading (and
961 =item --scriptmod=I<perl expression>
963 When novaboot script is read, I<perl expression> is executed for every
964 line (in $_ variable). For example, C<novaboot
965 --scriptmod=s/sigma0/omega6/g> replaces every occurrence of I<sigma0>
966 in the script with I<omega6>.
968 When this option is present, it overrides I<$script_modifier> variable
969 from the configuration file, which has the same effect. If this option
970 is given multiple times all expressions are evaluated in the command
975 Strip I<rom://> prefix from command lines and generated config files.
976 The I<rom://> prefix is used by NUL. For NRE, it has to be stripped.
980 =head2 File generation phase
982 In this phase, files needed for booting are generated in a so called
983 I<build directory> (see TODO). In most cases configuration for a
984 bootloader is generated automatically by novaboot. It is also possible
985 to generate other files using I<heredoc> or I<"<"> syntax in novaboot
986 scripts. Finally, binaries can be generated in this phases by running
991 =item --build-dir=I<directory>
993 Overrides the default build directory location.
995 The default build directory location is determined as follows: If the
996 configuration file defines the C<$builddir> variable, its value is
997 used. Otherwise, it is the directory that contains the first processed
1000 =item -g, --grub[=I<filename>]
1002 Generates grub bootloader menu file. If the I<filename> is not
1003 specified, F<menu.lst> is used. The I<filename> is relative to the
1004 build directory (see B<--build-dir>).
1006 =item --grub-preamble=I<prefix>
1008 Specifies the I<preable> that is at the beginning of the generated
1009 GRUB or GRUB2 config files. This is useful for specifying GRUB's
1012 =item --grub-prefix=I<prefix>
1014 Specifies I<prefix> that is put in front of every file name in GRUB's
1015 F<menu.lst>. The default value is the absolute path to the build directory.
1017 If the I<prefix> contains string $NAME, it will be replaced with the
1018 name of the novaboot script (see also B<--name>).
1020 =item --grub2[=I<filename>]
1022 Generate GRUB2 menuentry in I<filename>. If I<filename> is not
1023 specified F<grub.cfg> is used. The content of the menuentry can be
1024 customized with B<--grub-preable>, B<--grub2-prolog> or
1025 B<--grub_prefix> options.
1027 In order to use the the generated menuentry on your development
1028 machine that uses GRUB2, append the following snippet to
1029 F</etc/grub.d/40_custom> file and regenerate your grub configuration,
1030 i.e. run update-grub on Debian/Ubuntu.
1032 if [ -f /path/to/nul/build/grub.cfg ]; then
1033 source /path/to/nul/build/grub.cfg
1036 =item --grub2-prolog=I<prolog>
1038 Specifies text I<preable> that is put at the beginning of the entry
1041 =item -m, --make[=make command]
1043 Runs C<make> to build files that are not generated by novaboot itself.
1045 =item --name=I<string>
1047 Use the name I<string> instead of the name of the novaboot script.
1048 This name is used for things like a title of grub menu or for the
1049 server directory where the boot files are copied to.
1053 Do not generate files on the fly (i.e. "<" syntax) except for the
1054 files generated via "<<WORD" syntax.
1056 =item -p, --pulsar[=mac]
1058 Generates pulsar bootloader configuration file named F<config-I<mac>>
1059 The I<mac> string is typically a MAC address and defaults to
1062 =item --scons[=scons command]
1064 Runs C<scons> to build files that are not generated by novaboot
1069 Exit novaboot after file generation phase.
1073 =head2 Target connection check
1075 If supported by the target, the connection to it is made and it is
1076 checked whether the target is not occupied by another novaboot
1081 =item --iprelay=I<addr[:port]>
1083 Use TCP/IP relay and serial port to access the target's serial port
1084 and powercycle it. The IP address of the relay is given by I<addr>
1085 parameter. If I<port> is not specified, it default to 23.
1087 Note: This option is supposed to work with HWG-ER02a IP relays.
1089 =item -s, --serial[=device]
1091 Target's serial line is connected to host's serial line (device). The
1092 default value for device is F</dev/ttyUSB0>.
1094 The value of this option is exported in NB_NOVABOOT environment
1095 variable to all subprocesses run by C<novaboot>.
1097 =item --stty=I<settings>
1099 Specifies settings passed to C<stty> invoked on the serial line
1100 specified with B<--serial> option. If this option is not given,
1101 C<stty> is called with C<raw -crtscts -onlcr 115200> settings.
1103 =item --remote-cmd=I<cmd>
1105 Command that mediates connection to the target's serial line. For
1106 example C<ssh server 'cu -l /dev/ttyS0'>.
1108 =item --remote-expect=I<string>
1110 Wait for reception of I<string> on the remote connection before
1115 =head2 File deployment phase
1117 In some setups, it is necessary to copy the files needed for booting
1118 to a particular location, e.g. to a TFTP boot server or to the
1123 =item -d, --dhcp-tftp
1125 Turns your workstation into a DHCP and TFTP server so that the OS can
1126 be booted via PXE BIOS (or similar mechanism) on the test machine
1127 directly connected by a plain Ethernet cable to your workstation.
1129 The DHCP and TFTP servers require root privileges and C<novaboot>
1130 uses C<sudo> command to obtain those. You can put the following to
1131 I</etc/sudoers> to allow running the necessary commands without
1132 asking for password.
1134 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
1135 your_login ALL=NOPASSWD: NOVABOOT
1137 =item --iso[=filename]
1139 Generates the ISO image that boots NOVA system via GRUB. If no filename
1140 is given, the image is stored under I<NAME>.iso, where I<NAME> is the name
1141 of the novaboot script (see also B<--name>).
1143 =item --server[=[[user@]server:]path]
1145 Copy all files needed for booting to another location (implies B<-g>
1146 unless B<--grub2> is given). The files will be copied (by B<rsync>
1147 tool) to the directory I<path>. If the I<path> contains string $NAME,
1148 it will be replaced with the name of the novaboot script (see also
1153 If B<--server> is used and its value ends with $NAME, then after
1154 copying the files, a new bootloader configuration file (e.g. menu.lst)
1155 is created at I<path-wo-name>, i.e. the path specified by B<--server>
1156 with $NAME part removed. The content of the file is created by
1157 concatenating all files of the same name from all subdirectories of
1158 I<path-wo-name> found on the "server".
1160 =item --rsync-flags=I<flags>
1162 Specifies which I<flags> are appended to F<rsync> command line when
1163 copying files as a result of I<--server> option.
1167 =head2 Target power-on and reset phase
1173 Switch on/off the target machine. Currently works only with
1176 =item -Q, --qemu[=I<qemu-binary>]
1178 Boot the configuration in qemu. Optionally, the name of qemu binary
1179 can be specified as a parameter.
1181 =item --qemu-append=I<flags>
1183 Append I<flags> to the default qemu flags (QEMU_FLAGS variable or
1184 C<-cpu coreduo -smp 2>).
1186 =item -q, --qemu-flags=I<flags>
1188 Replace the default qemu flags (QEMU_FLAGS variable or C<-cpu coreduo
1189 -smp 2>) with I<flags> specified here.
1191 =item --reset-cmd=I<cmd>
1193 Command that resets the target.
1197 =head2 Interaction with the bootloader on the target
1203 Interact with uBoot bootloader to boot the thing described in the
1204 novaboot script. Implementation of this option is currently tied to a
1205 particular board that we use. It may be subject to changes in the
1210 Command(s) to send the uBoot bootloader before loading the images and
1215 =head2 Target interaction phase
1217 In this phase, target's serial output is passed to C<novaboot> stdout.
1218 If C<novaboot>'s stdin is on TTY, the stdin is passed to the target
1219 allowing interactive work with the target.
1221 This phase end when the target hangs up or when Ctrl-C is pressed.
1225 =item --exiton=I<string>
1227 When I<string> is sent by the target, novaboot exits. This option can
1228 be specified multiple times.
1230 If I<string> is C<-re>, then the next B<--exiton>'s I<string> is
1231 treated as regular expression. For example:
1233 --exiton -re --exiton 'error:.*failed'
1235 =item -i, --interactive
1237 Setup things for interactive use of target. Your terminal will be
1238 switched to raw mode. In raw mode, your system does not process input
1239 in any way (no echoing of entered characters, no interpretation
1240 special characters). This, among others, means that Ctrl-C is passed
1241 to the target and does no longer interrupt novaboot. Use "~~."
1242 sequence to exit novaboot.
1246 =head1 NOVABOOT SCRIPT SYNTAX
1248 The syntax tries to mimic POSIX shell syntax. The syntax is defined with the following rules.
1250 Lines starting with "#" are ignored.
1252 Lines that end with "\" are concatenated with the following line after
1253 removal of the final "\" and leading whitespace of the following line.
1255 Lines in the form I<VARIABLE=...> (i.e. matching '^[A-Z_]+=' regular
1256 expression) assign values to internal variables. See VARIABLES
1259 Otherwise, the first word on the line represents the filename
1260 (relative to the build directory (see B<--build-dir>) of the module to
1261 load and the remaining words are passed as the command line
1264 When the line ends with "<<WORD" then the subsequent lines until the
1265 line containing only WORD are copied literally to the file named on
1268 When the line ends with "< CMD" the command CMD is executed with
1269 C</bin/sh> and its standard output is stored in the file named on that
1270 line. The SRCDIR variable in CMD's environment is set to the absolute
1271 path of the directory containing the interpreted novaboot script.
1274 #!/usr/bin/env novaboot
1275 WVDESC=Example program
1276 bin/apps/sigma0.nul S0_DEFAULT script_start:1,1 \
1277 verbose hostkeyb:0,0x60,1,12,2
1279 hello.nulconfig <<EOF
1280 sigma0::mem:16 name::/s0/log name::/s0/timer name::/s0/fs/rom ||
1281 rom://bin/apps/hello.nul
1284 This example will load three modules: sigma0.nul, hello.nul and
1285 hello.nulconfig. sigma0 gets some command line parameters and
1286 hello.nulconfig file is generated on the fly from the lines between
1291 The following variables are interpreted in the novaboot script:
1297 Novaboot chdir()s to this directory before file generation phase. The
1298 directory name specified here is relative to the build directory
1299 specified by other means (see L</--build-dir>).
1303 Assigning this variable has the same effect as specifying L</--exiton>
1306 =item HYPERVISOR_PARAMS
1308 Parameters passed to hypervisor. The default value is "serial", unless
1309 overriden in configuration file.
1313 The kernel to use instead of NOVA hypervisor specified in the
1314 configuration file. The value should contain the name of the kernel
1315 image as well as its command line parameters. If this variable is
1316 defined and non-empty, the variable HYPERVISOR_PARAMS is not used.
1320 Use a specific qemu binary (can be overriden with B<-Q>) and flags
1321 when booting this script under qemu. If QEMU_FLAGS variable is also
1322 specified flags specified in QEMU variable are replaced by those in
1327 Use specific qemu flags (can be overriden with B<-q>).
1331 Description of the wvtest-compliant program.
1333 =item WVTEST_TIMEOUT
1335 The timeout in seconds for WvTest harness. If no complete line appears
1336 in the test output within the time specified here, the test fails. It
1337 is necessary to specify this for long running tests that produce no
1338 intermediate output.
1342 =head1 CONFIGURATION FILE
1344 Novaboot can read its configuration from one or more files. By
1345 default, novaboot looks for files named F<.novaboot> as described in
1346 L</Configuration reading phase>. Alternatively, its location can be
1347 specified with the B<-c> switch or with the NOVABOOT_CONFIG
1348 environment variable. The configuration file has perl syntax and
1349 should set values of certain Perl variables. The current configuration
1350 can be dumped with the B<--dump-config> switch. Some configuration
1351 variables can be overriden by environment variables (see below) or by
1352 command line switches.
1354 Supported configuration variables include:
1360 Build directory location relative to the location of the configuration
1363 =item $default_target
1365 Default target (see below) to use when no target is explicitly
1366 specified on command line with the B<--target> option.
1370 Hash of shortcuts to be used with the B<--target> option. If the hash
1371 contains, for instance, the following pair of values
1373 'mybox' => '--server=boot:/tftproot --serial=/dev/ttyUSB0 --grub',
1375 then the following two commands are equivalent:
1377 ./script --server=boot:/tftproot --serial=/dev/ttyUSB0 --grub
1382 =head1 ENVIRONMENT VARIABLES
1384 Some options can be specified not only via config file or command line
1385 but also through environment variables. Environment variables override
1386 the values from configuration file and command line parameters
1387 override the environment variables.
1391 =item NOVABOOT_CONFIG
1393 Name of the novaboot configuration file to use instead of the default
1396 =item NOVABOOT_BENDER
1398 Defining this variable has the same meaning as B<--bender> option.
1404 Michal Sojka <sojka@os.inf.tu-dresden.de>