5 use Getopt::Long qw(GetOptionsFromString);
\r
10 use Time::HiRes("usleep");
\r
14 use POSIX qw(:errno_h);
\r
20 my $invocation_dir = getcwd();
\r
22 chomp(my $gittop = `git rev-parse --show-toplevel 2>/dev/null`);
\r
24 # Default configuration
\r
25 $CFG::hypervisor = "bin/apps/hypervisor";
\r
26 $CFG::hypervisor_params = "serial";
\r
27 $CFG::server = "erwin.inf.tu-dresden.de:boot/novaboot/\$NAME";
\r
28 $CFG::server_grub_prefix = "(nd)/tftpboot/sojka/novaboot/\$NAME";
\r
29 $CFG::grub_keys = ''; #"/novaboot\n\n/\$NAME\n\n";
\r
30 $CFG::grub2_prolog = " set root='(hd0,msdos1)'";
\r
31 $CFG::genisoimage = "genisoimage";
\r
32 $CFG::iprelay_addr = '141.76.48.80:2324'; #'141.76.48.252';
\r
33 $CFG::qemu = 'qemu';
\r
34 $CFG::script_modifier = ''; # Depricated, use --scriptmod commandline option or custom_options.
\r
35 @CFG::chainloaders = (); #('bin/boot/bender promisc');
\r
36 $CFG::pulsar_root = '';
\r
37 $CFG::pulsar_mac = '52-54-00-12-34-56';
\r
38 %CFG::custom_options = ('I' => '--server=erwin.inf.tu-dresden.de:~passive/boot --rsync-flags="--chmod=Dg+s,ug+w,o-w,+rX --rsync-path=\"umask 002 && rsync\"" --grub-prefix=(nd)/tftpboot/passive/ --iprelay=141.76.48.80:2324 --scriptmod=s/\\\\bhostserial\\\\b/hostserialpci/g',
\r
39 'J' => '--server=rtime.felk.cvut.cz:/srv/tftp/novaboot --rsync-flags="--chmod=Dg+s,ug+w,o-w,+rX --rsync-path=\"umask 002 && rsync\"" --pulsar=novaboot --iprelay=147.32.86.92:2324');
\r
40 $CFG::scons = "scons -j2";
\r
42 my @qemu_flags = qw(-cpu coreduo -smp 2);
\r
43 sub read_config($) {
\r
46 package CFG; # Put config data into a separate namespace
\r
51 die("ERROR: Failure compiling '$cfg' - $@");
\r
52 } elsif (! defined($rc)) {
\r
53 die("ERROR: Failure reading '$cfg' - $!");
\r
55 die("ERROR: Failure processing '$cfg'");
\r
60 my $cfg = $ENV{'NOVABOOT_CONFIG'} || $ENV{'HOME'}."/.novaboot";
\r
61 Getopt::Long::Configure(qw/no_ignore_case pass_through/);
\r
62 GetOptions ("config|c=s" => \$cfg);
\r
63 if (-s $cfg) { read_config($cfg); }
\r
66 my ($append, $bender, $builddir, $config_name_opt, $dhcp_tftp, $dump_opt, $dump_config, $grub_config, $grub_prefix, $grub2_config, $help, $iprelay, $iso_image, $man, $no_file_gen, $off_opt, $on_opt, $pulsar, $qemu, $qemu_append, $qemu_flags_cmd, $rsync_flags, @scriptmod, $scons, $serial, $server);
\r
70 Getopt::Long::Configure(qw/no_ignore_case no_pass_through/);
\r
72 "append|a=s" => \$append,
\r
73 "bender|b" => \$bender,
\r
74 "build-dir=s" => \$builddir,
\r
75 "dhcp-tftp|d" => \$dhcp_tftp,
\r
76 "dump" => \$dump_opt,
\r
77 "dump-config" => \$dump_config,
\r
78 "grub|g:s" => \$grub_config,
\r
79 "grub-prefix=s" => \$grub_prefix,
\r
80 "grub2:s" => \$grub2_config,
\r
81 "iprelay:s" => \$iprelay,
\r
82 "iso|i:s" => \$iso_image,
\r
83 "name=s" => \$config_name_opt,
\r
84 "no-file-gen" => \$no_file_gen,
\r
87 "pulsar|p:s" => \$pulsar,
\r
88 "qemu|Q=s" => \$qemu,
\r
89 "qemu-append=s" => \$qemu_append,
\r
90 "qemu-flags|q=s" => \$qemu_flags_cmd,
\r
91 "rsync-flags=s" => \$rsync_flags,
\r
92 "scons:s" => \$scons,
\r
93 "scriptmod=s" => \@scriptmod,
\r
94 "serial|s:s" => \$serial,
\r
95 "server:s" => \$server,
\r
99 foreach my $opt(keys(%CFG::custom_options)) {
\r
100 $opt_spec{$opt} = sub { GetOptionsFromString($CFG::custom_options{$opt}, %opt_spec); };
\r
102 GetOptions %opt_spec or pod2usage(2);
\r
103 pod2usage(1) if $help;
\r
104 pod2usage(-exitstatus => 0, -verbose => 2) if $man;
\r
106 $CFG::iprelay_addr = $ENV{'NOVABOOT_IPRELAY'} if $ENV{'NOVABOOT_IPRELAY'};
\r
107 if ($iprelay && $iprelay ne "on" && $iprelay ne "off") { $CFG::iprelay_addr = $iprelay; }
\r
109 if (defined $config_name_opt && scalar(@ARGV) > 1) { die "You cannot use --name with multiple scripts"; }
\r
111 if ($server) { $CFG::server = $server; }
\r
112 if ($qemu) { $CFG::qemu = $qemu; }
\r
113 $qemu_append ||= '';
\r
115 if (defined $pulsar) {
\r
116 $CFG::pulsar_mac = $pulsar;
\r
119 if ($scons) { $CFG::scons = $scons; }
\r
120 if (!@scriptmod && $CFG::script_modifier) { @scriptmod = ( $CFG::script_modifier ); }
\r
121 if (defined $grub_prefix) { $CFG::server_grub_prefix = $grub_prefix; }
\r
123 if ($dump_config) {
\r
125 $Data::Dumper::Indent=0;
\r
126 print "# This file is in perl syntax.\n";
\r
127 foreach my $key(sort(keys(%CFG::))) { # See "Symbol Tables" in perlmod(1)
\r
128 if (defined ${$CFG::{$key}}) { print Data::Dumper->Dump([${$CFG::{$key}}], ["*$key"]); }
\r
129 if (defined @{$CFG::{$key}}) { print Data::Dumper->Dump([\@{$CFG::{$key}}], ["*$key"]); }
\r
130 if ( %{$CFG::{$key}}) { print Data::Dumper->Dump([\%{$CFG::{$key}}], ["*$key"]); }
\r
137 if (defined $serial) {
\r
138 $serial ||= "/dev/ttyUSB0";
\r
141 if (defined $grub_config) {
\r
142 $grub_config ||= "menu.lst";
\r
145 if (defined $grub2_config) {
\r
146 $grub2_config ||= "grub.cfg";
\r
149 if ($on_opt) { $iprelay="on"; }
\r
150 if ($off_opt) { $iprelay="off"; }
\r
152 # Parse the config(s)
\r
158 my ($modules, $variables, $generated, $continuation);
\r
160 if ($ARGV ne $last_fn) { # New script
\r
161 die "Missing EOF in $last_fn" if $file;
\r
162 die "Unfinished line in $last_fn" if $line;
\r
164 push @scripts, { 'filename' => $ARGV,
\r
165 'modules' => $modules = [],
\r
166 'variables' => $variables = {},
\r
167 'generated' => $generated = []};
\r
171 next if /^#/ || /^\s*$/; # Skip comments and empty lines
\r
173 foreach my $mod(@scriptmod) { eval $mod; }
\r
175 print "$_\n" if $dump_opt;
\r
177 if (/^([A-Z_]+)=(.*)$/) { # Internal variable
\r
178 $$variables{$1} = $2;
\r
181 if (/^([^ ]*)(.*?)[[:space:]]*<<([^ ]*)$/) { # Heredoc start
\r
182 push @$modules, "$1$2";
\r
184 push @$generated, {filename => $1, content => $file};
\r
188 if ($file && $_ eq $EOF) { # Heredoc end
\r
192 if ($file) { # Heredoc content
\r
193 push @{$file}, "$_\n";
\r
196 $_ =~ s/^[[:space:]]*// if ($continuation);
\r
197 if (/\\$/) { # Line continuation
\r
198 $line .= substr($_, 0, length($_)-1);
\r
204 $line .= " $append" if ($append && scalar(@$modules) == 0);
\r
206 if ($line =~ /^([^ ]*)(.*?)[[:space:]]*< ?(.*)$/) { # Command substitution
\r
207 push @$modules, "$1$2";
\r
208 push @$generated, {filename => $1, command => $3};
\r
212 push @$modules, $line;
\r
216 #print Dumper(\@scripts);
\r
220 sub generate_configs($$$) {
\r
221 my ($base, $generated, $filename) = @_;
\r
222 if ($base) { $base = "$base/"; };
\r
223 foreach my $g(@$generated) {
\r
224 if (exists $$g{content}) {
\r
225 my $config = $$g{content};
\r
226 my $fn = $$g{filename};
\r
227 open(my $f, '>', $fn) || die("$fn: $!");
\r
228 map { s|\brom://([^ ]*)|rom://$base$1|g; print $f "$_"; } @{$config};
\r
230 print "novaboot: Created $fn\n";
\r
231 } elsif (exists $$g{command} && ! $no_file_gen) {
\r
232 $ENV{SRCDIR} = dirname(File::Spec->rel2abs( $filename, $invocation_dir ));
\r
233 system_verbose("( $$g{command} ) > $$g{filename}");
\r
238 sub generate_grub_config($$$$;$)
\r
240 my ($filename, $title, $base, $modules_ref, $prepend) = @_;
\r
241 if ($base) { $base = "$base/"; };
\r
242 open(my $fg, '>', $filename) or die "$filename: $!";
\r
243 print $fg "$prepend\n" if $prepend;
\r
244 my $endmark = ($serial || defined $iprelay) ? ';' : '';
\r
245 print $fg "title $title$endmark\n" if $title;
\r
246 #print $fg "root $base\n"; # root doesn't really work for (nd)
\r
248 foreach (@$modules_ref) {
\r
251 my ($kbin, $kcmd) = split(' ', $_, 2);
\r
252 $kcmd = '' if !defined $kcmd;
\r
253 print $fg "kernel ${base}$kbin $kcmd\n";
\r
255 s|\brom://([^ ]*)|rom://$base$1|g; # Translate rom:// files - needed for vdisk parameter of sigma0
\r
256 print $fg "module $base$_\n";
\r
262 sub generate_grub2_config($$$$;$)
\r
264 my ($filename, $title, $base, $modules_ref, $prepend) = @_;
\r
265 if ($base && substr($base,-1,1) ne '/') { $base = "$base/"; };
\r
266 open(my $fg, '>', $filename) or die "$filename: $!";
\r
267 print $fg "$prepend\n" if $prepend;
\r
268 my $endmark = ($serial || defined $iprelay) ? ';' : '';
\r
269 $title ||= 'novaboot';
\r
270 print $fg "menuentry $title$endmark {\n";
\r
271 print $fg "$CFG::grub2_prolog\n";
\r
273 foreach (@$modules_ref) {
\r
276 my ($kbin, $kcmd) = split(' ', $_, 2);
\r
277 $kcmd = '' if !defined $kcmd;
\r
278 print $fg " multiboot ${base}$kbin $kcmd\n";
\r
281 # GRUB2 doesn't pass filename in multiboot info so we have to duplicate it here
\r
282 $_ = join(' ', ($args[0], @args));
\r
283 #s|\brom://([^ ]*)|rom://$base$1|g; # Translate rom:// files - needed for vdisk parameter of sigma0
\r
284 print $fg " module $base$_\n";
\r
291 sub generate_pulsar_config($$)
\r
293 my ($filename, $modules_ref) = @_;
\r
294 open(my $fg, '>', $filename) or die "$filename: $!";
\r
295 print $fg "root $CFG::pulsar_root\n" if $CFG::pulsar_root;
\r
298 foreach (@$modules_ref) {
\r
301 ($kbin, $kcmd) = split(' ', $_, 2);
\r
302 $kcmd = '' if !defined $kcmd;
\r
305 print $fg "load $_\n";
\r
308 # Put kernel as last - this is needed for booting Linux and has no influence on non-Linux OSes
\r
309 print $fg "exec $kbin $kcmd\n";
\r
313 sub exec_verbose(@)
\r
315 print "novaboot: Running: ".join(' ', map("'$_'", @_))."\n";
\r
319 sub system_verbose($)
\r
322 print "novaboot: Running: $cmd\n";
\r
323 my $ret = system($cmd);
\r
324 if ($ret & 0x007f) { die("Command terminated by a signal"); }
\r
325 if ($ret & 0xff00) {die("Command exit with non-zero exit code"); }
\r
326 if ($ret) { die("Command failure $ret"); }
\r
329 if (exists $variables->{WVDESC}) {
\r
330 print "Testing \"$variables->{WVDESC}\" in $last_fn:\n";
\r
331 } elsif ($last_fn =~ /\.wv$/) {
\r
332 print "Testing \"all\" in $last_fn:\n";
\r
336 if (defined $iprelay) {
\r
337 $CFG::iprelay_addr =~ /([.0-9]+)(:([0-9]+))?/;
\r
339 my $port = $3 || 23;
\r
340 my $paddr = sockaddr_in($port, inet_aton($addr));
\r
341 my $proto = getprotobyname('tcp');
\r
342 socket($IPRELAY, PF_INET, SOCK_STREAM, $proto) || die "socket: $!";
\r
343 print "novaboot: Connecting to IP relay... ";
\r
344 connect($IPRELAY, $paddr) || die "connect: $!";
\r
346 $IPRELAY->autoflush(1);
\r
349 print $IPRELAY "\xFF\xF6";
\r
351 local $SIG{ALRM} = sub { die "Relay AYT timeout"; };
\r
352 my $ayt_reponse = "";
\r
353 my $read = sysread($IPRELAY, $ayt_reponse, 100);
\r
356 chomp($ayt_reponse);
\r
357 print "$ayt_reponse\n";
\r
358 if ($ayt_reponse =~ /<iprelayd: not connected/) {
\r
366 my ($relay, $onoff) = @_;
\r
367 die unless ($relay == 1 || $relay == 2);
\r
369 my $cmd = ($relay == 1 ? 0x5 : 0x6) | ($onoff ? 0x20 : 0x10);
\r
370 return "\xFF\xFA\x2C\x32".chr($cmd)."\xFF\xF0";
\r
373 sub relayconf($$) {
\r
374 my ($relay, $onoff) = @_;
\r
375 die unless ($relay == 1 || $relay == 2);
\r
376 my $cmd = ($relay == 1 ? 0xdf : 0xbf) | ($onoff ? 0x00 : 0xff);
\r
377 return "\xFF\xFA\x2C\x97".chr($cmd)."\xFF\xF0";
\r
381 my ($relay, $onoff, $can_giveup) = @_;
\r
382 my $confirmation = '';
\r
383 print $IPRELAY relaycmd($relay, $onoff);
\r
385 # We use non-blocking I/O and polling here because for some
\r
386 # reason read() on blocking FD returns only after all
\r
387 # requested data is available. If we get during the first
\r
388 # read() only a part of confirmation, we may get the rest
\r
389 # after the system boots and print someting, which may be too
\r
391 $IPRELAY->blocking(0);
\r
393 alarm(20); # Timeout in seconds
\r
395 local $SIG{ALRM} = sub {
\r
396 if ($can_giveup) { print("Relay confirmation timeout - ignoring\n"); $giveup = 1;}
\r
397 else {die "Relay confirmation timeout";}
\r
400 while (($index=index($confirmation, relayconf($relay, $onoff))) < 0 && !$giveup) {
\r
401 my $read = read($IPRELAY, $confirmation, 70, length($confirmation));
\r
402 if (!defined($read)) {
\r
403 die("IP relay: $!") unless $! == EAGAIN;
\r
407 #use MIME::QuotedPrint;
\r
408 #print "confirmation = ".encode_qp($confirmation)."\n";
\r
411 $IPRELAY->blocking(1);
\r
415 if ($iprelay && ($iprelay eq "on" || $iprelay eq "off")) {
\r
416 relay(1, 1); # Press power button
\r
417 if ($iprelay eq "on") {
\r
418 usleep(100000); # Short press
\r
420 usleep(6000000); # Long press to switch off
\r
422 print $IPRELAY relay(1, 0);
\r
427 $CFG::builddir = $builddir;
\r
429 if (! defined $CFG::builddir) {
\r
430 $CFG::builddir = ( $gittop || $ENV{'HOME'}."/nul" ) . "/build";
\r
431 if (! -d $CFG::builddir) {
\r
432 $CFG::builddir = $ENV{SRCDIR} = dirname(File::Spec->rel2abs( ${$scripts[0]}{filename}, $invocation_dir ));
\r
437 chdir($CFG::builddir) or die "Can't change directory to $CFG::builddir: $!";
\r
438 print "novaboot: Entering directory `$CFG::builddir'\n";
\r
440 my (%files_iso, $menu_iso, $config_name, $filename);
\r
442 foreach my $script (@scripts) {
\r
443 $filename = $$script{filename};
\r
444 $modules = $$script{modules};
\r
445 $generated = $$script{generated};
\r
446 $variables = $$script{variables};
\r
447 my ($server_grub_prefix);
\r
449 if (defined $config_name_opt) {
\r
450 $config_name = $config_name_opt;
\r
452 ($config_name = $filename) =~ s#.*/##;
\r
456 if (exists $variables->{KERNEL}) {
\r
457 $kernel = $variables->{KERNEL};
\r
459 $kernel = $CFG::hypervisor . " ";
\r
460 if (exists $variables->{HYPERVISOR_PARAMS}) {
\r
461 $kernel .= $variables->{HYPERVISOR_PARAMS};
\r
463 $kernel .= $CFG::hypervisor_params;
\r
466 @$modules = ($kernel, @$modules);
\r
467 @$modules = (@CFG::chainloaders, @$modules);
\r
468 @$modules = ("bin/boot/bender", @$modules) if ($bender || defined $ENV{'NOVABOOT_BENDER'});
\r
470 if (defined $grub_config) {
\r
471 generate_configs("", $generated, $filename);
\r
472 generate_grub_config($grub_config, $config_name, "", $modules);
\r
473 print("GRUB menu created: $CFG::builddir/$grub_config\n");
\r
477 if (defined $grub2_config && !defined $server) {
\r
478 generate_configs('', $generated, $filename);
\r
479 generate_grub2_config($grub2_config, $config_name, $CFG::builddir, $modules);
\r
480 print("GRUB2 configuration created: $CFG::builddir/$grub2_config\n");
\r
485 if (defined $pulsar) {
\r
486 $pulsar_config = "config-$CFG::pulsar_mac";
\r
487 generate_configs('', $generated, $filename);
\r
488 generate_pulsar_config($pulsar_config, $modules);
\r
489 if (!defined $server) {
\r
490 print("Pulsar configuration created: $CFG::builddir/$pulsar_config\n");
\r
495 if (defined $scons) {
\r
496 my @files = map({ ($file) = m/([^ ]*)/; $file; } @$modules);
\r
497 # Filter-out generated files
\r
498 my @to_build = grep({ my $file = $_; !scalar(grep($file eq $$_{filename}, @$generated)) } @files);
\r
499 system_verbose($CFG::scons." ".join(" ", @to_build));
\r
502 if (defined $server) {
\r
503 ($server_grub_prefix = $CFG::server_grub_prefix) =~ s/\$NAME/$config_name/;
\r
504 ($server = $CFG::server) =~ s/\$NAME/$config_name/;
\r
505 my $bootloader_config;
\r
506 if ($grub2_config) {
\r
507 generate_configs('', $generated, $filename);
\r
508 $bootloader_config ||= "grub.cfg";
\r
509 generate_grub2_config($grub2_config, $config_name, $server_grub_prefix, $modules);
\r
510 } elsif (defined $pulsar) {
\r
511 $bootloader_config = $pulsar_config;
\r
513 generate_configs($server_grub_prefix, $generated, $filename);
\r
514 $bootloader_config ||= "menu.lst";
\r
515 if (!grep { $_ eq $bootloader_config } @$modules) {
\r
516 generate_grub_config($bootloader_config, $config_name, $server_grub_prefix, $modules,
\r
517 $server_grub_prefix eq $CFG::server_grub_prefix ? "timeout 0" : undef);
\r
520 my ($hostname, $path) = split(":", $server, 2);
\r
521 if (! defined $path) {
\r
525 my $files = "$bootloader_config " . join(" ", map({ ($file) = m/([^ ]*)/; $file; } @$modules));
\r
526 my $combined_menu_lst = ($CFG::server =~ m|/\$NAME$|);
\r
527 map({ my $file = (split)[0]; die "$file: $!" if ! -f $file; } @$modules);
\r
528 my $istty = -t STDOUT && $ENV{'TERM'} ne "dumb";
\r
529 my $progress = $istty ? "--progress" : "";
\r
530 system_verbose("rsync $progress -RLp $rsync_flags $files $server");
\r
531 my $cmd = "cd $path/.. && cat */menu.lst > menu.lst";
\r
532 if ($combined_menu_lst) { system_verbose($hostname ? "ssh $hostname '$cmd'" : $cmd); }
\r
535 if (defined $iso_image) {
\r
536 generate_configs("(cd)", $generated, $filename);
\r
538 generate_grub_config(\$menu, $config_name, "(cd)", $modules);
\r
539 $menu_iso .= "$menu\n";
\r
540 map { ($file,undef) = split; $files_iso{$file} = 1; } @$modules;
\r
544 if (defined $iso_image) {
\r
545 open(my $fh, ">menu-iso.lst");
\r
546 print $fh "timeout 5\n\n$menu_iso";
\r
548 my $files = "boot/grub/menu.lst=menu-iso.lst " . join(" ", map("$_=$_", keys(%files_iso)));
\r
549 $iso_image ||= "$config_name.iso";
\r
550 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");
\r
551 print("ISO image created: $CFG::builddir/$iso_image\n");
\r
554 ######################################################################
\r
555 # Boot NOVA using various methods and send serial output to stdout
\r
556 ######################################################################
\r
558 if (scalar(@scripts) > 1 && ( defined $dhcp_tftp || defined $serial || defined $iprelay)) {
\r
559 die "You cannot do this with multiple scripts simultaneously";
\r
562 if ($variables->{WVTEST_TIMEOUT}) {
\r
563 print "wvtest: timeout ", $variables->{WVTEST_TIMEOUT}, "\n";
\r
566 if (!(defined $dhcp_tftp || defined $serial || defined $iprelay || defined $server || defined $iso_image)) {
\r
568 @qemu_flags = split(/ /, $variables->{QEMU_FLAGS}) if exists $variables->{QEMU_FLAGS};
\r
569 @qemu_flags = split(/ /, $qemu_flags_cmd) if $qemu_flags_cmd;
\r
570 push(@qemu_flags, split(/ /, $qemu_append));
\r
572 if (defined $iso_image) {
\r
573 # Boot NOVA with grub (and test the iso image)
\r
574 push(@qemu_flags, ('-cdrom', "$config_name.iso"));
\r
576 # Boot NOVA without GRUB
\r
578 # Non-patched qemu doesn't like commas, but NUL can live with pluses instead of commans
\r
579 foreach (@$modules) {s/,/+/g;}
\r
580 generate_configs("", $generated, $filename);
\r
582 my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);
\r
583 $kcmd = '' if !defined $kcmd;
\r
584 my $initrd = join ",", @$modules;
\r
586 push(@qemu_flags, ('-kernel', $kbin, '-append', $kcmd));
\r
587 push(@qemu_flags, ('-initrd', $initrd)) if $initrd;
\r
589 push(@qemu_flags, qw(-serial stdio)); # Redirect serial output (for collecting test restuls)
\r
590 exec_verbose(($CFG::qemu, '-name', $config_name, @qemu_flags));
\r
593 my ($dhcpd_pid, $tftpd_pid);
\r
595 if (defined $dhcp_tftp)
\r
597 generate_configs("(nd)", $generated, $filename);
\r
598 system_verbose('mkdir -p tftpboot');
\r
599 generate_grub_config("tftpboot/os-menu.lst", $config_name, "(nd)", \@$modules, "timeout 0");
\r
600 open(my $fh, '>', 'dhcpd.conf');
\r
601 my $mac = `cat /sys/class/net/eth0/address`;
\r
603 print $fh "subnet 10.23.23.0 netmask 255.255.255.0 {
\r
604 range 10.23.23.10 10.23.23.100;
\r
605 filename \"bin/boot/grub/pxegrub.pxe\";
\r
606 next-server 10.23.23.1;
\r
609 hardware ethernet $mac;
\r
610 fixed-address 10.23.23.1;
\r
613 system_verbose("sudo ip a add 10.23.23.1/24 dev eth0;
\r
614 sudo ip l set dev eth0 up;
\r
615 sudo touch dhcpd.leases");
\r
617 $dhcpd_pid = fork();
\r
618 if ($dhcpd_pid == 0) {
\r
619 # This way, the spawned server are killed when this script is killed.
\r
620 exec_verbose("sudo dhcpd -d -cf dhcpd.conf -lf dhcpd.leases -pf dhcpd.pid");
\r
622 $tftpd_pid = fork();
\r
623 if ($tftpd_pid == 0) {
\r
624 exec_verbose("sudo in.tftpd --foreground --secure -v -v -v $CFG::builddir");
\r
626 $SIG{TERM} = sub { print "CHILDS KILLED\n"; kill 15, $dhcpd_pid, $tftpd_pid; };
\r
629 if ($serial || defined $iprelay) {
\r
631 if (defined $iprelay) {
\r
632 print "novaboot: Reseting the test box... ";
\r
633 relay(2, 1, 1); # Reset the machine
\r
639 } elsif ($serial) {
\r
640 system("stty -F $serial raw -crtscts -onlcr 115200");
\r
641 open($CONN, "+<", $serial) || die "open $serial: $!";
\r
642 $CONN->autoflush(1);
\r
644 if (!defined $dhcp_tftp && $CFG::grub_keys) {
\r
645 # Control grub via serial line
\r
646 print "Waiting for GRUB's serial output... ";
\r
648 if (/Press any key to continue/) { print $CONN "\n"; last; }
\r
650 $CFG::grub_keys =~ s/\$NAME/$config_name;/;
\r
651 my @characters = split(//, $CFG::grub_keys);
\r
652 foreach (@characters) {
\r
654 usleep($_ eq "\n" ? 100000 : 10000);
\r
659 # Pass the NOVA output to stdout.
\r
663 kill 15, $dhcpd_pid, $tftpd_pid if ($dhcp_tftp);
\r
667 if (defined $dhcp_tftp) {
\r
669 if ($pid == $dhcpd_pid) { print "dhcpd exited!\n"; }
\r
670 elsif ($pid == $tftpd_pid) { print "tftpd exited!\n"; }
\r
671 else { print "wait returned: $pid\n"; }
\r
672 kill(15, 0); # Kill current process group i.e. all remaining children
\r
677 novaboot - NOVA boot script interpreter
\r
681 B<novaboot> [ options ] [--] script...
\r
683 B<./script> [ options ]
\r
689 This program makes it easier to boot NOVA or other operating system
\r
690 (OS) in different environments. It reads a so called novaboot script
\r
691 and uses it either to boot the OS in an emulator (e.g. in qemu) or to
\r
692 generate the configuration for a specific bootloader and optionally to
\r
693 copy the necessary binaries and other needed files to proper
\r
694 locations, perhaps on a remote server. In case the system is actually
\r
695 booted, its serial output is redirected to standard output if that is
\r
698 A typical way of using novaboot is to make the novaboot script
\r
699 executable and set its first line to I<#!/usr/bin/env novaboot>. Then,
\r
700 booting a particular OS configuration becomes the same as executing a
\r
701 local program - the novaboot script.
\r
703 With C<novaboot> you can:
\r
709 Run NOVA in Qemu. This is the default action when no other action is
\r
710 specified by command line switches. Thus running C<novaboot ./script>
\r
711 (or C<./script> as described above) will run Qemu with configuration
\r
712 specified in the I<script>.
\r
716 Create a bootloader configuration file (currently supported
\r
717 bootloaders are GRUB, GRUB2 and Pulsar) and copy it with all files
\r
718 needed for booting another, perhaps remote, location.
\r
720 ./script --server --iprelay
\r
722 This command copies files to a TFTP server specified in the
\r
723 configuration file and uses TCP/IP-controlled relay to reset the test
\r
724 box and receive its serial output.
\r
728 Run DHCP and TFTP server on developer's machine to PXE-boot NOVA from
\r
731 ./script --dhcp-tftp
\r
733 When a PXE-bootable machine is connected via Ethernet to developer's
\r
734 machine, it will boot the configuration described in I<script>.
\r
738 Create bootable ISO images. E.g.
\r
740 novaboot --iso -- script1 script2
\r
742 The created ISO image will have GRUB bootloader installed on it and
\r
743 the boot menu will allow selecting between I<script1> and I<script2>
\r
752 =item -a, --append=<parameters>
\r
754 Appends a string to the root task's command line.
\r
758 Boot bender tool before the kernel to find PCI serial ports.
\r
760 =item --build-dir=<directory>
\r
762 Overrides the default build directory location.
\r
764 The default build directory location is determined as follows:
\r
766 If there is a configuration file, the value specified in I<$builddir>
\r
767 variable is used. Otherwise, if the current working directory is
\r
768 inside git work tree and there is F<build> directory at the top of
\r
769 that tree, it is used. Otherwise, if directory F<~/nul/build> exists,
\r
770 it is used. Otherwise, it is the directory that contains the first
\r
771 processed novaboot script.
\r
773 =item -c, --config=<filename>
\r
775 Use a different configuration file than the default one (i.e.
\r
778 =item -d, --dhcp-tftp
\r
780 Turns your workstation into a DHCP and TFTP server so that NOVA
\r
781 can be booted via PXE BIOS on a test machine directly connected by
\r
782 a plain Ethernet cable to your workstation.
\r
784 The DHCP and TFTP servers require root privileges and C<novaboot>
\r
785 uses C<sudo> command to obtain those. You can put the following to
\r
786 I</etc/sudoers> to allow running the necessary commands without
\r
787 asking for password.
\r
789 Cmnd_Alias NOVABOOT = /bin/ip a add 10.23.23.1/24 dev eth0, /bin/ip l set dev eth0 up, /usr/sbin/dhcpd -d -cf dhcpd.conf -lf dhcpd.leases -pf dhcpd.pid, /usr/sbin/in.tftpd --foreground --secure -v -v -v *, /usr/bin/touch dhcpd.leases
\r
790 your_login ALL=NOPASSWD: NOVABOOT
\r
794 Prints the content of the novaboot script after removing comments and
\r
795 evaluating all I<--scriptmod> expressions.
\r
797 =item --dump-config
\r
799 Dumps current configuration to stdout end exits. Useful as an initial
\r
800 template for a configuration file.
\r
802 =item -g, --grub[=I<filename>]
\r
804 Generates grub menu file. If the I<filename> is not specified,
\r
805 F<menu.lst> is used. The I<filename> is relative to NUL build
\r
808 =item --grub-prefix=I<prefix>
\r
810 Specifies I<prefix> that is put before every file in GRUB's menu.lst.
\r
811 This overrides the value of I<$server_grub_prefix> from the
\r
812 configuration file.
\r
814 =item --grub2[=I<filename>]
\r
816 Generate GRUB2 menuentry in I<filename>. If I<filename> is not
\r
817 specified grub.cfg is used. The content of the menuentry can be
\r
818 customized by I<$grub2_prolog> and I<$server_grub_prefix>
\r
819 configuration variables.
\r
821 In order to use the the generated menuentry on your development
\r
822 machine that uses GRUB2, append the following snippet to
\r
823 F</etc/grub.d/40_custom> file and regenerate your grub configuration,
\r
824 i.e. run update-grub on Debian/Ubuntu.
\r
826 if [ -f /path/to/nul/build/grub.cfg ]; then
\r
827 source /path/to/nul/build/grub.cfg
\r
833 Print short (B<-h>) or long (B<--help>) help.
\r
835 =item --iprelay[=addr or cmd]
\r
837 If no I<cmd> is given, use IP relay to reset the machine and to get
\r
838 the serial output. The IP address of the relay is given by I<addr>
\r
839 parameter if specified or by $iprelay_addr variable in the
\r
840 configuration file.
\r
842 If I<cmd> is one of "on" or "off", the IP relay is used to press power
\r
843 button for a short (in case of "on") or long (in case of "off") time.
\r
844 Then, novaboot exits.
\r
846 Note: This option is expected to work with HWG-ER02a IP relays.
\r
848 =item -i, --iso[=filename]
\r
850 Generates the ISO image that boots NOVA system via GRUB. If no filename
\r
851 is given, the image is stored under I<NAME>.iso, where I<NAME> is the name
\r
852 of novaboot script (see also B<--name>).
\r
856 This is an alias (see C<%custom_options> below) defined in the default
\r
857 configuration. When used, it causes novaboot to use Michal's remotely
\r
858 controllable test bed.
\r
862 This is an alias (see C<%custom_options> below) defined in the default
\r
863 configuration. When used, it causes novaboot to use another remotely
\r
864 controllable test bed.
\r
868 Synonym for --iprelay=on/off.
\r
870 =item --name=I<string>
\r
872 Use the name I<string> instead of the name of the novaboot script.
\r
873 This name is used for things like a title of grub menu or for the
\r
874 server directory where the boot files are copied to.
\r
876 =item --no-file-gen
\r
878 Do not generate files on the fly (i.e. "<" syntax) except for the
\r
879 files generated via "<<WORD" syntax.
\r
881 =item -p, --pulsar[=mac]
\r
883 Generates pulsar bootloader configuration file whose name is based on
\r
884 the MAC address specified either on the command line or taken from
\r
885 I<.novaboot> configuration file.
\r
887 =item -Q, --qemu=I<qemu-binary>
\r
889 Use specific version of qemu binary. The default is 'qemu'.
\r
891 =item --qemu-append=I<flags>
\r
893 Append I<flags> to the default qemu flags (QEMU_FLAGS variable or
\r
894 C<-cpu coreduo -smp 2>).
\r
896 =item -q, --qemu-flags=I<flags>
\r
898 Replace the default qemu flags (QEMU_FLAGS variable or C<-cpu coreduo
\r
899 -smp 2>) with I<flags> specified here.
\r
901 =item --rsync-flags=I<flags>
\r
903 Specifies which I<flags> are appended to F<rsync> command line when
\r
904 copying files as a result of I<--server> option.
\r
906 =item --scons[=scons command]
\r
908 Runs I<scons> to build files that are not generated by novaboot
\r
911 =item --scriptmod=I<perl expression>
\r
913 When novaboot script is read, I<perl expression> is executed for every
\r
914 line (in $_ variable). For example, C<novaboot
\r
915 --scriptmod=s/sigma0/omega6/g> replaces every occurrence of I<sigma0>
\r
916 in the script with I<omega6>.
\r
918 When this option is present, it overrides I<$script_modifier> variable
\r
919 from the configuration file, which has the same effect. If this option
\r
920 is given multiple times all expressions are evaluated in the command
\r
923 =item --server[=[[user@]server:]path]
\r
925 Copy all files needed for booting to a server (implies B<-g> unless
\r
926 B<--grub2> is given). The files will be copied to the directory
\r
927 I<path>. If the I<path> contains string $NAME, it will be replaced
\r
928 with the name of the novaboot script (see also B<--name>).
\r
930 Additionally, if $NAME is the last component of the I<path>, a file
\r
931 named I<path>/menu.lst (with $NAME removed from the I<path>) will be
\r
932 created on the server by concatenating all I<path>/*/menu.lst (with
\r
933 $NAME removed from the I<path>) files found on the server.
\r
935 =item -s, --serial[=device]
\r
937 Use serial line to control GRUB bootloader and to get the serial
\r
938 output of the machine. The default value is /dev/ttyUSB0.
\r
942 =head1 NOVABOOT SCRIPT SYNTAX
\r
944 The syntax tries to mimic POSIX shell syntax. The syntax is defined with the following rules.
\r
946 Lines starting with "#" are ignored.
\r
948 Lines that end with "\" are concatenated with the following line after
\r
949 removal of the final "\" and leading whitespace of the following line.
\r
951 Lines in the form I<VARIABLE=...> (i.e. matching '^[A-Z_]+=' regular
\r
952 expression) assign values to internal variables. See VARIABLES
\r
955 Otherwise, the first word on the line represents the filename
\r
956 (relative to the build directory (see I<--build-dir>) of the module to
\r
957 load and the remaining words are passed as the command line
\r
960 When the line ends with "<<WORD" then the subsequent lines until the
\r
961 line containing only WORD are copied literally to the file named on
\r
964 When the line ends with "< CMD" the command CMD is executed with
\r
965 C</bin/sh> and its standard output is stored in the file named on that
\r
966 line. The SRCDIR variable in CMD's environment is set to the absolute
\r
967 path of the directory containing the interpreted novaboot script.
\r
970 #!/usr/bin/env novaboot
\r
971 WVDESC=Example program
\r
972 bin/apps/sigma0.nul S0_DEFAULT script_start:1,1 \
\r
973 verbose hostkeyb:0,0x60,1,12,2
\r
975 hello.nulconfig <<EOF
\r
976 sigma0::mem:16 name::/s0/log name::/s0/timer name::/s0/fs/rom ||
\r
977 rom://bin/apps/hello.nul
\r
980 This example will load three modules: sigma0.nul, hello.nul and
\r
981 hello.nulconfig. sigma0 gets some command line parameters and
\r
982 hello.nulconfig file is generated on the fly from the lines between
\r
987 The following variables are interpreted in the novaboot script:
\r
993 Description of the wvtest-compliant program.
\r
995 =item WVTEST_TIMEOUT
\r
997 The timeout in seconds for WvTest harness. If no complete line appears
\r
998 in the test output within the time specified here, the test fails. It
\r
999 is necessary to specify this for long running tests that produce no
\r
1000 intermediate output.
\r
1004 Use specific qemu flags (can be overriden by B<-q>).
\r
1006 =item HYPERVISOR_PARAMS
\r
1008 Parameters passed to hypervisor. The default value is "serial", unless
\r
1009 overriden in configuration file.
\r
1013 The kernel to use instead of NOVA hypervisor specified in the
\r
1014 configuration file. The value should contain the name of the kernel
\r
1015 image as well as its command line parameters. If this variable is
\r
1016 defined and non-empty, the variable HYPERVISOR_PARAMS is not used.
\r
1020 =head1 CONFIGURATION FILE
\r
1022 novaboot can read its configuration from ~/.novaboot file (or another
\r
1023 file specified with B<-c> parameter or NOVABOOT_CONFIG environment
\r
1024 variable). It is a file with perl syntax, which sets values of certain
\r
1025 variables. The current configuration can be dumped with
\r
1026 B<--dump-config> switch. Use
\r
1028 novaboot --dump-config > ~/.novaboot
\r
1030 to create a default configuration file and modify it to your needs.
\r
1031 Some configuration variables can be overriden by environment variables
\r
1032 (see below) or by command line switches.
\r
1034 Documentation of some configuration variables follows:
\r
1038 =item @chainloaders
\r
1040 Custom chainloaders to load before hypervisor and files specified in
\r
1041 novaboot script. E.g. ('bin/boot/bender promisc', 'bin/boot/zapp').
\r
1043 =item %custom_options
\r
1045 Defines custom command line options that can serve as aliases for
\r
1046 other options. E.g. 'S' => '--server=boot:/tftproot
\r
1047 --serial=/dev/ttyUSB0'.
\r
1051 =head1 ENVIRONMENT VARIABLES
\r
1053 Some options can be specified not only via config file or command line
\r
1054 but also through environment variables. Environment variables override
\r
1055 the values from configuration file and command line parameters
\r
1056 override the environment variables.
\r
1060 =item NOVABOOT_CONFIG
\r
1062 A name of default novaboot configuration file.
\r
1064 =item NOVABOOT_BENDER
\r
1066 Defining this variable has the same meaning as B<--bender> option.
\r
1068 =item NOVABOOT_IPRELAY
\r
1070 The IP address (and optionally the port) of the IP relay. This
\r
1071 overrides $iprelay_addr variable from the configuration file.
\r
1077 Michal Sojka <sojka@os.inf.tu-dresden.de>
\r