]> rtime.felk.cvut.cz Git - novaboot.git/blob - novaboot
Add --exiton option and EXITON variable
[novaboot.git] / novaboot
1 #!/usr/bin/perl -w
2
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.
7
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.
12
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/>.
15
16 use strict;
17 use warnings;
18 use warnings (exists $ENV{NOVABOOT_TEST} ? (FATAL => 'all') : ());
19 use Getopt::Long qw(GetOptionsFromString);
20 use Pod::Usage;
21 use File::Basename;
22 use File::Spec;
23 use IO::Handle;
24 use Time::HiRes("usleep");
25 use Socket;
26 use FileHandle;
27 use IPC::Open2;
28 use POSIX qw(:errno_h);
29 use Cwd qw(getcwd abs_path);
30 use Expect;
31
32 # always flush
33 $| = 1;
34
35 my $invocation_dir = $ENV{PWD} || getcwd();
36
37 ## Configuration file handling
38
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';
45 %CFG::targets = (
46     'qemu' => '--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"',
52
53     );
54 chomp(my $nproc = `nproc`);
55 $CFG::scons = "scons -j$nproc";
56 $CFG::make = "make -j$nproc";
57
58 my $builddir;
59
60 sub read_config($) {
61     my ($cfg) = @_;
62     {
63         package CFG; # Put config data into a separate namespace
64         my $rc = do($cfg);
65
66         # Check for errors
67         if ($@) {
68             die("ERROR: Failure compiling '$cfg' - $@");
69         } elsif (! defined($rc)) {
70             die("ERROR: Failure reading '$cfg' - $!");
71         } elsif (! $rc) {
72             die("ERROR: Failure processing '$cfg'");
73         }
74     }
75     $builddir = File::Spec->rel2abs($CFG::builddir, dirname($cfg)) if defined $CFG::builddir;
76     print STDERR "novaboot: Read $cfg\n";
77 }
78
79 my @cfgs;
80 {
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]);
90     }
91 }
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;
96
97 ## Command line handling
98
99 my $explicit_target;
100 GetOptions ("target|t=s" => \$explicit_target);
101
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, $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);
103
104 $rsync_flags = '';
105 $rom_prefix = 'rom://';
106 $stty = 'raw -crtscts -onlcr 115200';
107
108 Getopt::Long::Configure(qw/no_ignore_case no_pass_through/);
109 my %opt_spec;
110 %opt_spec = (
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|i:s"        => \$iso_image,
128     "name=s"         => \$config_name_opt,
129     "make|m:s"       => \$make,
130     "no-file-gen"    => \$no_file_gen,
131     "off"            => \$off_opt,
132     "on"             => \$on_opt,
133     "pulsar|p:s"     => \$pulsar,
134     "pulsar-root=s"  => \$pulsar_root,
135     "qemu|Q:s"       => \$qemu,
136     "qemu-append=s"  => \$qemu_append,
137     "qemu-flags|q=s" => \$qemu_flags_cmd,
138     "remote-cmd=s"   => \$remote_cmd,
139     "remote-expect=s"=> \$remote_expect,
140     "reset-cmd=s"    => \$reset_cmd,
141     "rsync-flags=s"  => \$rsync_flags,
142     "scons:s"        => \$scons,
143     "scriptmod=s"    => \@scriptmod,
144     "serial|s:s"     => \$serial,
145     "server:s"       => \$server,
146     "strip-rom"      => sub { $rom_prefix = ''; },
147     "stty=s"         => \$stty,
148     "uboot"          => \$uboot,
149     "uboot-init=s"   => \$uboot_init,
150     "h"              => \$help,
151     "help"           => \$man,
152     );
153
154 # First process target options
155 GetOptionsFromString($CFG::targets{$explicit_target || $CFG::default_target}, %opt_spec);
156
157 # Then process other command line options - some of them may override
158 # what was specified by the target
159 GetOptions %opt_spec;
160 pod2usage(1) if $help;
161 pod2usage(-exitstatus => 0, -verbose => 2) if $man;
162
163 ### Dump sanitized configuration (if requested)
164
165 if ($dump_config) {
166     use Data::Dumper;
167     $Data::Dumper::Indent=1;
168     print "# This file is in perl syntax.\n";
169     foreach my $key(sort(keys(%CFG::))) { # See "Symbol Tables" in perlmod(1)
170         if (defined ${$CFG::{$key}}) { print Data::Dumper->Dump([${$CFG::{$key}}], ["*$key"]); }
171         if (        @{$CFG::{$key}}) { print Data::Dumper->Dump([\@{$CFG::{$key}}], ["*$key"]); }
172         if (        %{$CFG::{$key}}) { print Data::Dumper->Dump([\%{$CFG::{$key}}], ["*$key"]); }
173     }
174     print "1;\n";
175     exit;
176 }
177
178 ### Sanitize configuration
179
180 if (defined $config_name_opt && scalar(@ARGV) > 1) { die "You cannot use --name with multiple scripts"; }
181
182 # Default options
183 if (defined $serial) {
184     $serial ||= "/dev/ttyUSB0";
185     $ENV{NB_SERIAL} = $serial;
186 }
187 if (defined $grub_config) { $grub_config ||= "menu.lst"; }
188 if (defined $grub2_config) { $grub2_config ||= "grub.cfg"; }
189
190 ## Parse the novaboot script(s)
191 my @scripts;
192 my $file;
193 my $line;
194 my $EOF;
195 my $last_fn = '';
196 my ($modules, $variables, $generated, $continuation);
197 while (<>) {
198     if ($ARGV ne $last_fn) { # New script
199         die "Missing EOF in $last_fn" if $file;
200         die "Unfinished line in $last_fn" if $line;
201         $last_fn = $ARGV;
202         push @scripts, { 'filename' => $ARGV,
203                          'modules' => $modules = [],
204                          'variables' => $variables = {},
205                          'generated' => $generated = []};
206
207     }
208     chomp();
209     next if /^#/ || /^\s*$/;    # Skip comments and empty lines
210
211     foreach my $mod(@scriptmod) { eval $mod; }
212
213     print "$_\n" if $dump_opt;
214
215     if (/^([A-Z_]+)=(.*)$/) {   # Internal variable
216         $$variables{$1} = $2;
217         push(@exiton, $2) if ($1 eq "EXITON");
218         next;
219     }
220     if (/^([^ ]*)(.*?)[[:space:]]*<<([^ ]*)$/) { # Heredoc start
221         push @$modules, "$1$2";
222         $file = [];
223         push @$generated, {filename => $1, content => $file};
224         $EOF = $3;
225         next;
226     }
227     if ($file && $_ eq $EOF) {  # Heredoc end
228         undef $file;
229         next;
230     }
231     if ($file) {                # Heredoc content
232         push @{$file}, "$_\n";
233         next;
234     }
235     $_ =~ s/^[[:space:]]*// if ($continuation);
236     if (/\\$/) {                # Line continuation
237         $line .= substr($_, 0, length($_)-1);
238         $continuation = 1;
239         next;
240     }
241     $continuation = 0;
242     $line .= $_;
243     $line .= " $append" if ($append && scalar(@$modules) == 0);
244
245     if ($line =~ /^([^ ]*)(.*?)[[:space:]]*< ?(.*)$/) { # Command substitution
246         push @$modules, "$1$2";
247         push @$generated, {filename => $1, command => $3};
248         $line = '';
249         next;
250     }
251     push @$modules, $line;
252     $line = '';
253 }
254 #use Data::Dumper;
255 #print Dumper(\@scripts);
256
257 exit if $dump_opt;
258
259 ## Helper functions
260
261 sub generate_configs($$$) {
262     my ($base, $generated, $filename) = @_;
263     if ($base) { $base = "$base/"; };
264     foreach my $g(@$generated) {
265       if (exists $$g{content}) {
266         my $config = $$g{content};
267         my $fn = $$g{filename};
268         open(my $f, '>', $fn) || die("$fn: $!");
269         map { s|\brom://([^ ]*)|$rom_prefix$base$1|g; print $f "$_"; } @{$config};
270         close($f);
271         print "novaboot: Created $fn\n";
272       } elsif (exists $$g{command} && ! $no_file_gen) {
273         $ENV{SRCDIR} = dirname(File::Spec->rel2abs( $filename, $invocation_dir ));
274         system_verbose("( $$g{command} ) > $$g{filename}");
275       }
276     }
277 }
278
279 sub generate_grub_config($$$$;$)
280 {
281     my ($filename, $title, $base, $modules_ref, $preamble) = @_;
282     if ($base) { $base = "$base/"; };
283     open(my $fg, '>', $filename) or die "$filename: $!";
284     print $fg "$preamble\n" if $preamble;
285     print $fg "title $title\n" if $title;
286     #print $fg "root $base\n"; # root doesn't really work for (nd)
287     my $first = 1;
288     foreach (@$modules_ref) {
289         if ($first) {
290             $first = 0;
291             my ($kbin, $kcmd) = split(' ', $_, 2);
292             $kcmd = '' if !defined $kcmd;
293             print $fg "kernel ${base}$kbin $kcmd\n";
294         } else {
295             s|\brom://([^ ]*)|$rom_prefix$base$1|g; # Translate rom:// files - needed for vdisk parameter of sigma0
296             print $fg "module $base$_\n";
297         }
298     }
299     close($fg);
300     print("novaboot: Created $builddir/$filename\n");
301     return $filename;
302 }
303
304 sub generate_grub2_config($$$$;$$)
305 {
306     my ($filename, $title, $base, $modules_ref, $preamble, $prolog) = @_;
307     if ($base && substr($base,-1,1) ne '/') { $base = "$base/"; };
308     open(my $fg, '>', $filename) or die "$filename: $!";
309     print $fg "$preamble\n" if $preamble;
310     $title ||= 'novaboot';
311     print $fg "menuentry $title {\n";
312     print $fg "$prolog\n" if $prolog;
313     my $first = 1;
314     foreach (@$modules_ref) {
315         if ($first) {
316             $first = 0;
317             my ($kbin, $kcmd) = split(' ', $_, 2);
318             $kcmd = '' if !defined $kcmd;
319             print $fg "  multiboot ${base}$kbin $kcmd\n";
320         } else {
321             my @args = split;
322             # GRUB2 doesn't pass filename in multiboot info so we have to duplicate it here
323             $_ = join(' ', ($args[0], @args));
324             s|\brom://|$rom_prefix|g; # We do not need to translate path for GRUB2
325             print $fg "  module $base$_\n";
326         }
327     }
328     print $fg "}\n";
329     close($fg);
330     print("novaboot: Created $builddir/$filename\n");
331     return $filename;
332 }
333
334 sub generate_pulsar_config($$)
335 {
336     my ($filename, $modules_ref) = @_;
337     open(my $fg, '>', $filename) or die "$filename: $!";
338     print $fg "root $pulsar_root\n" if defined $pulsar_root;
339     my $first = 1;
340     my ($kbin, $kcmd);
341     foreach (@$modules_ref) {
342         if ($first) {
343             $first = 0;
344             ($kbin, $kcmd) = split(' ', $_, 2);
345             $kcmd = '' if !defined $kcmd;
346         } else {
347             my @args = split;
348             s|\brom://|$rom_prefix|g;
349             print $fg "load $_\n";
350         }
351     }
352     # Put kernel as last - this is needed for booting Linux and has no influence on non-Linux OSes
353     print $fg "exec $kbin $kcmd\n";
354     close($fg);
355     print("novaboot: Created $builddir/$filename\n");
356     return $filename;
357 }
358
359 sub shell_cmd_string(@)
360 {
361     return join(' ', map((/^[-_=a-zA-Z0-9\/\.\+]+$/ ? "$_" : "'$_'"), @_));
362 }
363
364 sub exec_verbose(@)
365 {
366     print "novaboot: Running: ".shell_cmd_string(@_)."\n";
367     exec(@_);
368 }
369
370 sub system_verbose($)
371 {
372     my $cmd = shift;
373     print "novaboot: Running: $cmd\n";
374     my $ret = system($cmd);
375     if ($ret & 0x007f) { die("Command terminated by a signal"); }
376     if ($ret & 0xff00) {die("Command exit with non-zero exit code"); }
377     if ($ret) { die("Command failure $ret"); }
378 }
379
380 ## WvTest handline
381
382 if (exists $variables->{WVDESC}) {
383     print "Testing \"$variables->{WVDESC}\" in $last_fn:\n";
384 } elsif ($last_fn =~ /\.wv$/) {
385     print "Testing \"all\" in $last_fn:\n";
386 }
387
388 ## Connect to the target and check whether is not occupied
389
390 # We have to do this before file generation phase, because file
391 # generation is intermixed with file deployment phase and we want to
392 # check whether the target is not used by somebody else before
393 # deploying files. Otherwise, we may rewrite other user's files on a
394 # boot server.
395
396 my $exp; # Expect object to communicate with the target over serial line
397
398 my ($target_reset, $target_power_on, $target_power_off);
399
400 if (defined $iprelay) {
401     my $IPRELAY;
402     $iprelay =~ /([.0-9]+)(:([0-9]+))?/;
403     my $addr = $1;
404     my $port = $3 || 23;
405     my $paddr   = sockaddr_in($port, inet_aton($addr));
406     my $proto   = getprotobyname('tcp');
407     socket($IPRELAY, PF_INET, SOCK_STREAM, $proto)  || die "socket: $!";
408     print "novaboot: Connecting to IP relay... ";
409     connect($IPRELAY, $paddr)    || die "connect: $!";
410     print "done\n";
411     $exp = Expect->init(\*$IPRELAY);
412     $exp->log_stdout(1);
413
414     while (1) {
415         print $exp "\xFF\xF6";  # AYT
416         my $connected = $exp->expect(20, # Timeout in seconds
417                                      '<iprelayd: connected>',
418                                      '-re', '<WEB51 HW[^>]*>');
419         last if $connected;
420     }
421
422     sub relaycmd($$) {
423         my ($relay, $onoff) = @_;
424         die unless ($relay == 1 || $relay == 2);
425
426         my $cmd = ($relay == 1 ? 0x5 : 0x6) | ($onoff ? 0x20 : 0x10);
427         return "\xFF\xFA\x2C\x32".chr($cmd)."\xFF\xF0";
428     }
429
430     sub relayconf($$) {
431         my ($relay, $onoff) = @_;
432         die unless ($relay == 1 || $relay == 2);
433         my $cmd = ($relay == 1 ? 0xdf : 0xbf) | ($onoff ? 0x00 : 0xff);
434         return "\xFF\xFA\x2C\x97".chr($cmd)."\xFF\xF0";
435     }
436
437     sub relay($$;$) {
438         my ($relay, $onoff, $can_giveup) = @_;
439         my $confirmation = '';
440         $exp->log_stdout(0);
441         print $exp relaycmd($relay, $onoff);
442         my $confirmed = $exp->expect(20, # Timeout in seconds
443                                      relayconf($relay, $onoff));
444         if (!$confirmed) {
445             if ($can_giveup) {
446                 print("Relay confirmation timeout - ignoring\n");
447             } else {
448                 die "Relay confirmation timeout";
449             }
450         }
451         $exp->log_stdout(1);
452     }
453
454     $target_reset = sub {
455         relay(2, 1, 1); # Reset the machine
456         usleep(100000);
457         relay(2, 0);
458     };
459
460     $target_power_off = sub {
461         relay(1, 1);            # Press power button
462         usleep(6000000);        # Long press to switch off
463         relay(1, 0);
464     };
465
466     $target_power_on = sub {
467         relay(1, 1);            # Press power button
468         usleep(100000);         # Short press
469         relay(1, 0);
470     };
471 }
472 elsif ($serial) {
473     my $CONN;
474     system_verbose("stty -F $serial $stty");
475     open($CONN, "+<", $serial) || die "open $serial: $!";
476     $exp = Expect->init(\*$CONN);
477 } elsif ($remote_cmd) {
478     print "novaboot: Running: $remote_cmd\n";
479     $exp = Expect->spawn($remote_cmd);
480 }
481
482 if ($remote_expect) {
483     $exp->expect(10, $remote_expect) || die "Expect for '$remote_expect' timed out";
484 }
485
486 if (defined $reset_cmd) {
487     $target_reset = sub {
488         system_verbose($reset_cmd);
489     };
490 }
491
492 if (defined $on_opt && defined $target_power_on) {
493     &$target_power_on();
494     exit;
495 }
496 if (defined $off_opt && defined $target_power_off) {
497     print "novaboot: Switching the target off...\n";
498     &$target_power_off();
499     exit;
500 }
501
502 $builddir ||= dirname(File::Spec->rel2abs( ${$scripts[0]}{filename})) if scalar @scripts;
503 if (defined $builddir) {
504     chdir($builddir) or die "Can't change directory to $builddir: $!";
505     print "novaboot: Entering directory `$builddir'\n";
506 }
507
508 ## File generation phase
509 my (%files_iso, $menu_iso, $filename);
510 my $config_name = '';
511
512 foreach my $script (@scripts) {
513     $filename = $$script{filename};
514     $modules = $$script{modules};
515     $generated = $$script{generated};
516     $variables = $$script{variables};
517
518     ($config_name = $filename) =~ s#.*/##;
519     $config_name = $config_name_opt if (defined $config_name_opt);
520
521     if (exists $variables->{BUILDDIR}) {
522         $builddir = File::Spec->rel2abs($variables->{BUILDDIR});
523         chdir($builddir) or die "Can't change directory to $builddir: $!";
524         print "novaboot: Entering directory `$builddir'\n";
525     }
526
527     my $kernel;
528     if (exists $variables->{KERNEL}) {
529         $kernel = $variables->{KERNEL};
530     } else {
531         if ($CFG::hypervisor) {
532             $kernel = $CFG::hypervisor . " ";
533             if (exists $variables->{HYPERVISOR_PARAMS}) {
534                 $kernel .= $variables->{HYPERVISOR_PARAMS};
535             } else {
536                 $kernel .= $CFG::hypervisor_params;
537             }
538         }
539     }
540     @$modules = ($kernel, @$modules) if $kernel;
541     @$modules = (@chainloaders, @$modules);
542     @$modules = ("bin/boot/bender", @$modules) if ($bender || defined $ENV{'NOVABOOT_BENDER'});
543
544     my $prefix;
545     ($prefix = $grub_prefix) =~ s/\$NAME/$config_name/ if defined $grub_prefix;
546     $prefix ||= $builddir;
547     # TODO: use $grub_prefix as first parameter if some switch is given
548     generate_configs('', $generated, $filename);
549
550 ### Generate bootloader configuration files
551     my @bootloader_configs;
552     push @bootloader_configs, generate_grub_config($grub_config, $config_name, $prefix, $modules, $grub_preamble) if (defined $grub_config);
553     push @bootloader_configs, generate_grub2_config($grub2_config, $config_name, $prefix, $modules, $grub_preamble, $grub2_prolog) if (defined $grub2_config);
554     push @bootloader_configs, generate_pulsar_config('config-'.($pulsar||'novaboot'), $modules) if (defined $pulsar);
555
556 ### Run scons or make
557     {
558         my @files = map({ ($file) = m/([^ ]*)/; $file; } @$modules);
559         # Filter-out generated files
560         my @to_build = grep({ my $file = $_; !scalar(grep($file eq $$_{filename}, @$generated)) } @files);
561
562         system_verbose($scons || $CFG::scons." ".join(" ", @to_build)) if (defined $scons);
563         system_verbose($make  || $CFG::make ." ".join(" ", @to_build)) if (defined $make);
564     }
565
566 ### Copy files (using rsync)
567     if (defined $server && !defined($gen_only)) {
568         (my $real_server = $server) =~ s/\$NAME/$config_name/;
569
570         my ($hostname, $path) = split(":", $real_server, 2);
571         if (! defined $path) {
572             $path = $hostname;
573             $hostname = "";
574         }
575         my $files = join(" ", map({ ($file) = m/([^ ]*)/; $file; } ( @$modules, @bootloader_configs)));
576         map({ my $file = (split)[0]; die "$file: $!" if ! -f $file; } @$modules);
577         my $istty = -t STDOUT && ($ENV{'TERM'} || 'dumb') ne 'dumb';
578         my $progress = $istty ? "--progress" : "";
579         system_verbose("rsync $progress -RLp $rsync_flags $files $real_server");
580         if ($server =~ m|/\$NAME$| && $concat) {
581             my $cmd = join("; ", map { "( cd $path/.. && cat */$_ > $_ )" } @bootloader_configs);
582             system_verbose($hostname ? "ssh $hostname '$cmd'" : $cmd);
583         }
584     }
585
586 ### Prepare ISO image generation
587     if (defined $iso_image) {
588         generate_configs("(cd)", $generated, $filename);
589         my $menu;
590         generate_grub_config(\$menu, $config_name, "(cd)", $modules);
591         $menu_iso .= "$menu\n";
592         map { ($file,undef) = split; $files_iso{$file} = 1; } @$modules;
593     }
594 }
595
596 ## Generate ISO image
597 if (defined $iso_image) {
598     open(my $fh, ">menu-iso.lst");
599     print $fh "timeout 5\n\n$menu_iso";
600     close($fh);
601     my $files = "boot/grub/menu.lst=menu-iso.lst " . join(" ", map("$_=$_", keys(%files_iso)));
602     $iso_image ||= "$config_name.iso";
603     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");
604     print("ISO image created: $builddir/$iso_image\n");
605 }
606
607 exit(0) if defined $gen_only;
608
609 ## Boot the system using various methods and send serial output to stdout
610
611 if (scalar(@scripts) > 1 && ( defined $dhcp_tftp || defined $serial || defined $iprelay)) {
612     die "You cannot do this with multiple scripts simultaneously";
613 }
614
615 if ($variables->{WVTEST_TIMEOUT}) {
616     print "wvtest: timeout ", $variables->{WVTEST_TIMEOUT}, "\n";
617 }
618
619 sub trim($) {
620     my ($str) = @_;
621     $str =~ s/^\s+|\s+$//g;
622     return $str
623 }
624
625 ### Qemu
626
627 if (defined $qemu) {
628     # Qemu
629     $qemu ||= $variables->{QEMU} || $CFG::qemu;
630     my @qemu_flags = split(" ", $qemu);
631     $qemu = shift(@qemu_flags);
632
633     @qemu_flags = split(/ +/, trim($variables->{QEMU_FLAGS})) if exists $variables->{QEMU_FLAGS};
634     @qemu_flags = split(/ +/, trim($qemu_flags_cmd)) if $qemu_flags_cmd;
635     push(@qemu_flags, split(/ +/, trim($qemu_append || '')));
636
637     if (defined $iso_image) {
638         # Boot NOVA with grub (and test the iso image)
639         push(@qemu_flags, ('-cdrom', "$config_name.iso"));
640     } else {
641         # Boot NOVA without GRUB
642
643         # Non-patched qemu doesn't like commas, but NUL can live with pluses instead of commans
644         foreach (@$modules) {s/,/+/g;}
645         generate_configs("", $generated, $filename);
646
647         if (scalar @$modules) {
648             my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);
649             $kcmd = '' if !defined $kcmd;
650             my $dtb;
651             @$modules = map { if (/\.dtb$/) { $dtb=$_; (); } else { $_ } } @$modules;
652             my $initrd = join ",", @$modules;
653
654             push(@qemu_flags, ('-kernel', $kbin, '-append', $kcmd));
655             push(@qemu_flags, ('-initrd', $initrd)) if $initrd;
656             push(@qemu_flags, ('-dtb', $dtb)) if $dtb;
657         }
658     }
659     push(@qemu_flags,  qw(-serial stdio)); # Redirect serial output (for collecting test restuls)
660     unshift(@qemu_flags, ('-name', $config_name));
661     print "novaboot: Running: ".shell_cmd_string($qemu, @qemu_flags)."\n";
662     $exp = Expect->spawn(($qemu, @qemu_flags)) || die("exec() failed: $!");
663 }
664
665 ### Local DHCPD and TFTPD
666
667 my ($dhcpd_pid, $tftpd_pid);
668
669 if (defined $dhcp_tftp)
670 {
671     generate_configs("(nd)", $generated, $filename);
672     system_verbose('mkdir -p tftpboot');
673     generate_grub_config("tftpboot/os-menu.lst", $config_name, "(nd)", \@$modules, "timeout 0");
674     open(my $fh, '>', 'dhcpd.conf');
675     my $mac = `cat /sys/class/net/eth0/address`;
676     chomp $mac;
677     print $fh "subnet 10.23.23.0 netmask 255.255.255.0 {
678                       range 10.23.23.10 10.23.23.100;
679                       filename \"bin/boot/grub/pxegrub.pxe\";
680                       next-server 10.23.23.1;
681 }
682 host server {
683         hardware ethernet $mac;
684         fixed-address 10.23.23.1;
685 }";
686     close($fh);
687     system_verbose("sudo ip a add 10.23.23.1/24 dev eth0;
688             sudo ip l set dev eth0 up;
689             sudo touch dhcpd.leases");
690
691     # We run servers by forking ourselves, because the servers end up
692     # in our process group and get killed by signals sent to the
693     # process group (e.g. Ctrl-C on terminal).
694     $dhcpd_pid = fork();
695     exec_verbose("sudo dhcpd -d -cf dhcpd.conf -lf dhcpd.leases -pf dhcpd.pid") if ($dhcpd_pid == 0);
696     $tftpd_pid = fork();
697     exec_verbose("sudo in.tftpd --foreground --secure -v -v -v --pidfile tftpd.pid $builddir") if ($tftpd_pid == 0);
698
699     # Kill server when we die
700     $SIG{__DIE__} = sub { system_verbose('sudo pkill --pidfile=dhcpd.pid');
701                           system_verbose('sudo pkill --pidfile=tftpd.pid'); };
702 }
703
704 ### Serial line or IP relay
705
706 if (defined $target_reset) {
707     print "novaboot: Reseting the test box... ";
708     &$target_reset();
709     print "done\n";
710 }
711
712 if (defined $uboot) {
713     print "novaboot: Waiting for uBoot prompt...\n";
714     $exp->log_stdout(1);
715     $exp->expect(20,
716                  [qr/Hit any key to stop autoboot:/, sub { $exp->send("\n"); exp_continue; }],
717                  '=> ') || die "No uBoot prompt deteceted";
718     $exp->send("$uboot_init\n") if $uboot_init;
719     $exp->expect(10, '=> ') || die "uBoot prompt timeout";
720
721     my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);
722     my $dtb;
723     @$modules = map { if (/\.dtb$/) { $dtb=$_; (); } else { $_ } } @$modules;
724     my $initrd = shift @$modules;
725
726     my $kern_addr = '800000';
727     my $initrd_addr = '-';
728     my $dtb_addr = '';
729
730     $exp->send("tftp $kern_addr $kbin\n");
731     $exp->expect(10,
732                  [qr/#/, sub { exp_continue; }],
733                  '=> ') || die "Kernel load failed";
734     if (defined $dtb) {
735         $dtb_addr = '7f0000';
736         $exp->send("tftp $dtb_addr $dtb\n");
737         $exp->expect(10,
738                      [qr/#/, sub { exp_continue; }],
739                      '=> ') || die "Device tree load failed";
740     }
741     if (defined $initrd) {
742         $initrd_addr = 'b00000';
743         $exp->send("tftp $initrd_addr $initrd\n");
744         $exp->expect(10,
745                      [qr/#/, sub { exp_continue; }],
746                      '=> ') || die "Initrd load failed";
747     }
748     $exp->send("set bootargs $kcmd\n");
749     $exp->expect(1, '=> ')  || die "uBoot prompt timeout";
750     $exp->send("bootm $kern_addr $initrd_addr $dtb_addr\n");
751     $exp->expect(1, "\n")  || die "uBoot command timeout";
752 }
753
754 if (defined $exp) {
755     # Serial line of the target is available
756     print "novaboot: Serial line interaction (press Ctrl-C to interrupt)...\n";
757     $exp->log_stdout(1);
758     if (@exiton) {
759         $exp->expect(undef, @exiton);
760     } else {
761         my @inputs = ($exp);
762         if (-t STDIN) { # Set up bi-directional communication if we run on terminal
763             my $infile = new IO::File;
764             $infile->IO::File::fdopen(*STDIN,'r');
765             my $in_object = Expect->exp_init($infile);
766             $in_object->set_group($exp);
767             #$in_object->set_seq("\cC",undef);
768
769             # I'm not sure when to use raw mode and when not. With
770             # --dhcp-tftp, I want the output of daemons to be normally
771             # formated (no raw mode). On the other hand, I don't want
772             # input for qemu to be echoed. Need to think more about this.
773             $in_object->manual_stty(1);
774             push(@inputs, $in_object);
775         }
776         Expect::interconnect(@inputs);
777     }
778 }
779
780 ## Kill dhcpc or tftpd
781 if (defined $dhcp_tftp) {
782     die("novaboot: This should kill servers on background\n");
783 }
784
785 ## Documentation
786
787 =head1 NAME
788
789 novaboot - A tool for booting various operating systems on various hardware or in qemu
790
791 =head1 SYNOPSIS
792
793 B<novaboot> [ options ] [--] script...
794
795 B<./script> [ options ]
796
797 =head1 DESCRIPTION
798
799 This program makes it easier to boot NOVA or other operating system
800 (OS) on different targets (machines or emulators). It reads a so
801 called novaboot script, that specifies the boot configuration, and
802 setups the target to boot that configuration. Setting up the target
803 means to generate the bootloader configuration files, deploy the
804 binaries and other needed files to proper locations, perhaps on a
805 remote boot server and reset the target. Then, target's serial output
806 is redirected to standard output if that is possible.
807
808 A typical way of using novaboot is to make the novaboot script
809 executable and set its first line to I<#!/usr/bin/env novaboot>. Then,
810 booting a particular OS configuration becomes the same as executing a
811 local program - the novaboot script.
812
813 For example, with C<novaboot> you can:
814
815 =over 3
816
817 =item 1.
818
819 Run an OS in Qemu. This is the default action when no other action is
820 specified by command line switches. Thus running C<novaboot ./script>
821 (or C<./script> as described above) will run Qemu and make it boot the
822 configuration specified in the I<script>.
823
824 =item 2.
825
826 Create a bootloader configuration file (currently supported
827 bootloaders are GRUB, GRUB2, Pulsar and uBoot) and copy it with all
828 other files needed for booting to another, perhaps remote, location.
829
830  ./script --server=192.168.1.1:/tftp --iprelay=192.168.1.2
831
832 This command copies files to the TFTP server and uses
833 TCP/IP-controlled relay to reset the test box and receive its serial
834 output.
835
836 =item 3.
837
838 Run DHCP and TFTP server on developer's machine to PXE-boot the OS
839 from it. E.g.
840
841  ./script --dhcp-tftp
842
843 When a PXE-bootable machine is connected via Ethernet to developer's
844 machine, it will boot the configuration described in I<script>.
845
846 =item 4.
847
848 Create bootable ISO images. E.g.
849
850  novaboot --iso -- script1 script2
851
852 The created ISO image will have GRUB bootloader installed on it and
853 the boot menu will allow selecting between I<script1> and I<script2>
854 configurations.
855
856 =back
857
858 Note that the options needed for a specific target can be stored in a
859 L</"CONFIGURATION FILE"> and then it is sufficient to use only the
860 B<-t> option to specify the name of the target.
861
862 =head1 PHASES AND OPTIONS
863
864 Novaboot performs its work in several phases. Each phase can be
865 influenced by several options, certain phases can be skipped. The list
866 of phases (in the execution order) and the corresponding options
867 follow.
868
869 =head2 Configuration reading phase
870
871 After starting, novaboot reads configuration files. By default, it
872 searches for files named F<.novaboot> starting from the directory of
873 the novaboot script (or working directory, see bellow) and continuing
874 upwards up to the root directory. The configuration files are read in
875 order from the root directory downwards with latter files overriding
876 settings from the former ones.
877
878 In certain cases, the location of the novaboot script cannot be
879 determined in this early phase. This happens either when the script is
880 read from the standard input or when novaboot is invoked explicitly
881 and options precede the script name, as in the example L</"4."> above.
882 In this case the current working directory is used as a starting point
883 for configuration file search.
884
885 =over 8
886
887 =item -c, --config=I<filename>
888
889 Use the specified configuration file instead of the default one(s).
890
891 =back
892
893 =head2 Command line processing phase
894
895 =over 8
896
897 =item --dump-config
898
899 Dump the current configuration to stdout end exits. Useful as an
900 initial template for a configuration file.
901
902 =item -h, --help
903
904 Print short (B<-h>) or long (B<--help>) help.
905
906 =item -t, --target=I<target>
907
908 This option serves as a user configurable shortcut for other novaboot
909 options. The effect of this option is the same as the options stored
910 in the C<%targets> configuration variable under key I<target>. See
911 also L</"CONFIGURATION FILE">.
912
913 =back
914
915 =head2 Script preprocessing phase
916
917 This phases allows to modify the parsed novaboot script before it is
918 used in the later phases.
919
920 =over 8
921
922 =item -a, --append=I<parameters>
923
924 Appends a string to the first "filename" line in the novaboot script.
925 This can be used to append parameters to the kernel's or root task's
926 command line.
927
928 =item -b, --bender
929
930 Use F<bender> chainloader. Bender scans the PCI bus for PCI serial
931 ports and stores the information about them in the BIOS data area for
932 use by the kernel.
933
934 =item --chainloader=I<chainloader>
935
936 Chainloader that is loaded before the kernel and other files specified
937 in the novaboot script. E.g. 'bin/boot/bender promisc'.
938
939 =item --dump
940
941 Prints the content of the novaboot script after removing comments and
942 evaluating all I<--scriptmod> expressions. Exit after reading (and
943 dumping) the script.
944
945 =item --scriptmod=I<perl expression>
946
947 When novaboot script is read, I<perl expression> is executed for every
948 line (in $_ variable). For example, C<novaboot
949 --scriptmod=s/sigma0/omega6/g> replaces every occurrence of I<sigma0>
950 in the script with I<omega6>.
951
952 When this option is present, it overrides I<$script_modifier> variable
953 from the configuration file, which has the same effect. If this option
954 is given multiple times all expressions are evaluated in the command
955 line order.
956
957 =item --strip-rom
958
959 Strip I<rom://> prefix from command lines and generated config files.
960 The I<rom://> prefix is used by NUL. For NRE, it has to be stripped.
961
962 =back
963
964 =head2 File generation phase
965
966 In this phase, files needed for booting are generated in a so called
967 I<build directory> (see TODO). In most cases configuration for a
968 bootloader is generated automatically by novaboot. It is also possible
969 to generate other files using I<heredoc> or I<"<"> syntax in novaboot
970 scripts. Finally, binaries can be generated in this phases by running
971 C<scons> or C<make>.
972
973 =over 8
974
975 =item --build-dir=I<directory>
976
977 Overrides the default build directory location.
978
979 The default build directory location is determined as follows: If the
980 configuration file defines the C<$builddir> variable, its value is
981 used. Otherwise, it is the directory that contains the first processed
982 novaboot script.
983
984 =item -g, --grub[=I<filename>]
985
986 Generates grub bootloader menu file. If the I<filename> is not
987 specified, F<menu.lst> is used. The I<filename> is relative to the
988 build directory (see B<--build-dir>).
989
990 =item --grub-preamble=I<prefix>
991
992 Specifies the I<preable> that is at the beginning of the generated
993 GRUB or GRUB2 config files. This is useful for specifying GRUB's
994 timeout.
995
996 =item --grub-prefix=I<prefix>
997
998 Specifies I<prefix> that is put in front of every file name in GRUB's
999 F<menu.lst>. The default value is the absolute path to the build directory.
1000
1001 If the I<prefix> contains string $NAME, it will be replaced with the
1002 name of the novaboot script (see also B<--name>).
1003
1004 =item --grub2[=I<filename>]
1005
1006 Generate GRUB2 menuentry in I<filename>. If I<filename> is not
1007 specified F<grub.cfg> is used. The content of the menuentry can be
1008 customized with B<--grub-preable>, B<--grub2-prolog> or
1009 B<--grub_prefix> options.
1010
1011 In order to use the the generated menuentry on your development
1012 machine that uses GRUB2, append the following snippet to
1013 F</etc/grub.d/40_custom> file and regenerate your grub configuration,
1014 i.e. run update-grub on Debian/Ubuntu.
1015
1016   if [ -f /path/to/nul/build/grub.cfg ]; then
1017     source /path/to/nul/build/grub.cfg
1018   fi
1019
1020 =item --grub2-prolog=I<prolog>
1021
1022 Specifies text I<preable> that is put at the beginning of the entry
1023 GRUB2 entry.
1024
1025 =item -m, --make[=make command]
1026
1027 Runs C<make> to build files that are not generated by novaboot itself.
1028
1029 =item --name=I<string>
1030
1031 Use the name I<string> instead of the name of the novaboot script.
1032 This name is used for things like a title of grub menu or for the
1033 server directory where the boot files are copied to.
1034
1035 =item --no-file-gen
1036
1037 Do not generate files on the fly (i.e. "<" syntax) except for the
1038 files generated via "<<WORD" syntax.
1039
1040 =item -p, --pulsar[=mac]
1041
1042 Generates pulsar bootloader configuration file named F<config-I<mac>>
1043 The I<mac> string is typically a MAC address and defaults to
1044 I<novaboot>.
1045
1046 =item --scons[=scons command]
1047
1048 Runs C<scons> to build files that are not generated by novaboot
1049 itself.
1050
1051 =item --gen-only
1052
1053 Exit novaboot after file generation phase.
1054
1055 =back
1056
1057 =head2 Target connection check
1058
1059 If supported by the target, the connection to it is made and it is
1060 checked whether the target is not occupied by another novaboot
1061 user/instance.
1062
1063 =over 8
1064
1065 =item --iprelay=I<addr[:port]>
1066
1067 Use TCP/IP relay and serial port to access the target's serial port
1068 and powercycle it. The IP address of the relay is given by I<addr>
1069 parameter. If I<port> is not specified, it default to 23.
1070
1071 Note: This option is supposed to work with HWG-ER02a IP relays.
1072
1073 =item -s, --serial[=device]
1074
1075 Target's serial line is connected to host's serial line (device). The
1076 default value for device is F</dev/ttyUSB0>.
1077
1078 The value of this option is exported in NB_NOVABOOT environment
1079 variable to all subprocesses run by C<novaboot>.
1080
1081 =item --stty=I<settings>
1082
1083 Specifies settings passed to C<stty> invoked on the serial line
1084 specified with B<--serial> option. If this option is not given,
1085 C<stty> is called with C<raw -crtscts -onlcr 115200> settings.
1086
1087 =item --remote-cmd=I<cmd>
1088
1089 Command that mediates connection to the target's serial line. For
1090 example C<ssh server 'cu -l /dev/ttyS0'>.
1091
1092 =item --remote-expect=I<string>
1093
1094 Wait for reception of I<string> on the remote connection before
1095 continuing.
1096
1097 =back
1098
1099 =head2 File deployment phase
1100
1101 In some setups, it is necessary to copy the files needed for booting
1102 to a particular location, e.g. to a TFTP boot server or to the
1103 F</boot> partition.
1104
1105 =over 8
1106
1107 =item -d, --dhcp-tftp
1108
1109 Turns your workstation into a DHCP and TFTP server so that the OS can
1110 be booted via PXE BIOS (or similar mechanism) on the test machine
1111 directly connected by a plain Ethernet cable to your workstation.
1112
1113 The DHCP and TFTP servers require root privileges and C<novaboot>
1114 uses C<sudo> command to obtain those. You can put the following to
1115 I</etc/sudoers> to allow running the necessary commands without
1116 asking for password.
1117
1118  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
1119  your_login ALL=NOPASSWD: NOVABOOT
1120
1121 =item -i, --iso[=filename]
1122
1123 Generates the ISO image that boots NOVA system via GRUB. If no filename
1124 is given, the image is stored under I<NAME>.iso, where I<NAME> is the name
1125 of the novaboot script (see also B<--name>).
1126
1127 =item --server[=[[user@]server:]path]
1128
1129 Copy all files needed for booting to another location (implies B<-g>
1130 unless B<--grub2> is given). The files will be copied (by B<rsync>
1131 tool) to the directory I<path>. If the I<path> contains string $NAME,
1132 it will be replaced with the name of the novaboot script (see also
1133 B<--name>).
1134
1135 =item --concat
1136
1137 If B<--server> is used and its value ends with $NAME, then after
1138 copying the files, a new bootloader configuration file (e.g. menu.lst)
1139 is created at I<path-wo-name>, i.e. the path specified by B<--server>
1140 with $NAME part removed. The content of the file is created by
1141 concatenating all files of the same name from all subdirectories of
1142 I<path-wo-name> found on the "server".
1143
1144 =item --rsync-flags=I<flags>
1145
1146 Specifies which I<flags> are appended to F<rsync> command line when
1147 copying files as a result of I<--server> option.
1148
1149 =back
1150
1151 =head2 Target power-on and reset phase
1152
1153 =over 8
1154
1155 =item --on, --off
1156
1157 Switch on/off the target machine. Currently works only with
1158 B<--iprelay>.
1159
1160 =item -Q, --qemu[=I<qemu-binary>]
1161
1162 Boot the configuration in qemu. Optionally, the name of qemu binary
1163 can be specified as a parameter.
1164
1165 =item --qemu-append=I<flags>
1166
1167 Append I<flags> to the default qemu flags (QEMU_FLAGS variable or
1168 C<-cpu coreduo -smp 2>).
1169
1170 =item -q, --qemu-flags=I<flags>
1171
1172 Replace the default qemu flags (QEMU_FLAGS variable or C<-cpu coreduo
1173 -smp 2>) with I<flags> specified here.
1174
1175 =item --reset-cmd=I<cmd>
1176
1177 Command that resets the target.
1178
1179 =back
1180
1181 =head2 Interaction with the bootloader on the target
1182
1183 =over 8
1184
1185 =item --uboot
1186
1187 Interact with uBoot bootloader to boot the thing described in the
1188 novaboot script. Implementation of this option is currently tied to a
1189 particular board that we use. It may be subject to changes in the
1190 future!
1191
1192 =item --uboot-init
1193
1194 Command(s) to send the uBoot bootloader before loading the images and
1195 botting them.
1196
1197 =back
1198
1199 =head2 Target interaction phase
1200
1201 In this phase, target's serial output is passed to C<novaboot> stdout.
1202 If C<novaboot>'s stdin is on TTY, the stdin is passed to the target
1203 allowing interactive work with the target.
1204
1205 This phase end when the target hangs up or when Ctrl-C is pressed.
1206
1207 =over 8
1208
1209 =item --exiton=I<string>
1210
1211 When I<string> is sent by the target, novaboot exits. This option can
1212 be specified multiple times.
1213
1214 If I<string> is C<-re>, then the next B<--exiton>'s I<string> is
1215 treated as regular expression. For example:
1216
1217     --exiton -re --exiton 'error:.*failed'
1218
1219 =back
1220
1221 =head1 NOVABOOT SCRIPT SYNTAX
1222
1223 The syntax tries to mimic POSIX shell syntax. The syntax is defined with the following rules.
1224
1225 Lines starting with "#" are ignored.
1226
1227 Lines that end with "\" are concatenated with the following line after
1228 removal of the final "\" and leading whitespace of the following line.
1229
1230 Lines in the form I<VARIABLE=...> (i.e. matching '^[A-Z_]+=' regular
1231 expression) assign values to internal variables. See VARIABLES
1232 section.
1233
1234 Otherwise, the first word on the line represents the filename
1235 (relative to the build directory (see B<--build-dir>) of the module to
1236 load and the remaining words are passed as the command line
1237 parameters.
1238
1239 When the line ends with "<<WORD" then the subsequent lines until the
1240 line containing only WORD are copied literally to the file named on
1241 that line.
1242
1243 When the line ends with "< CMD" the command CMD is executed with
1244 C</bin/sh> and its standard output is stored in the file named on that
1245 line. The SRCDIR variable in CMD's environment is set to the absolute
1246 path of the directory containing the interpreted novaboot script.
1247
1248 Example:
1249   #!/usr/bin/env novaboot
1250   WVDESC=Example program
1251   bin/apps/sigma0.nul S0_DEFAULT script_start:1,1 \
1252     verbose hostkeyb:0,0x60,1,12,2
1253   bin/apps/hello.nul
1254   hello.nulconfig <<EOF
1255   sigma0::mem:16 name::/s0/log name::/s0/timer name::/s0/fs/rom ||
1256   rom://bin/apps/hello.nul
1257   EOF
1258
1259 This example will load three modules: sigma0.nul, hello.nul and
1260 hello.nulconfig. sigma0 gets some command line parameters and
1261 hello.nulconfig file is generated on the fly from the lines between
1262 <<EOF and EOF.
1263
1264 =head2 VARIABLES
1265
1266 The following variables are interpreted in the novaboot script:
1267
1268 =over 8
1269
1270 =item BUILDDIR
1271
1272 Novaboot chdir()s to this directory before file generation phase. The
1273 directory name specified here is relative to the build directory
1274 specified by other means (see L</--build-dir>).
1275
1276 =item EXITON
1277
1278 Assigning this variable has the same effect as specifying L</--exiton>
1279 option.
1280
1281 =item HYPERVISOR_PARAMS
1282
1283 Parameters passed to hypervisor. The default value is "serial", unless
1284 overriden in configuration file.
1285
1286 =item KERNEL
1287
1288 The kernel to use instead of NOVA hypervisor specified in the
1289 configuration file. The value should contain the name of the kernel
1290 image as well as its command line parameters. If this variable is
1291 defined and non-empty, the variable HYPERVISOR_PARAMS is not used.
1292
1293 =item QEMU
1294
1295 Use a specific qemu binary (can be overriden with B<-Q>) and flags
1296 when booting this script under qemu. If QEMU_FLAGS variable is also
1297 specified flags specified in QEMU variable are replaced by those in
1298 QEMU_FLAGS.
1299
1300 =item QEMU_FLAGS
1301
1302 Use specific qemu flags (can be overriden with B<-q>).
1303
1304 =item WVDESC
1305
1306 Description of the wvtest-compliant program.
1307
1308 =item WVTEST_TIMEOUT
1309
1310 The timeout in seconds for WvTest harness. If no complete line appears
1311 in the test output within the time specified here, the test fails. It
1312 is necessary to specify this for long running tests that produce no
1313 intermediate output.
1314
1315 =back
1316
1317 =head1 CONFIGURATION FILE
1318
1319 Novaboot can read its configuration from one or more files. By
1320 default, novaboot looks for files named F<.novaboot> as described in
1321 L</Configuration reading phase>. Alternatively, its location can be
1322 specified with the B<-c> switch or with the NOVABOOT_CONFIG
1323 environment variable. The configuration file has perl syntax and
1324 should set values of certain Perl variables. The current configuration
1325 can be dumped with the B<--dump-config> switch. Some configuration
1326 variables can be overriden by environment variables (see below) or by
1327 command line switches.
1328
1329 Supported configuration variables include:
1330
1331 =over 8
1332
1333 =item $builddir
1334
1335 Build directory location relative to the location of the configuration
1336 file.
1337
1338 =item $default_target
1339
1340 Default target (see below) to use when no target is explicitly
1341 specified on command line with the B<--target> option.
1342
1343 =item %targets
1344
1345 Hash of shortcuts to be used with the B<--target> option. If the hash
1346 contains, for instance, the following pair of values
1347
1348  'mybox' => '--server=boot:/tftproot --serial=/dev/ttyUSB0 --grub',
1349
1350 then the following two commands are equivalent:
1351
1352  ./script --server=boot:/tftproot --serial=/dev/ttyUSB0 --grub
1353  ./script -t mybox
1354
1355 =back
1356
1357 =head1 ENVIRONMENT VARIABLES
1358
1359 Some options can be specified not only via config file or command line
1360 but also through environment variables. Environment variables override
1361 the values from configuration file and command line parameters
1362 override the environment variables.
1363
1364 =over 8
1365
1366 =item NOVABOOT_CONFIG
1367
1368 Name of the novaboot configuration file to use instead of the default
1369 one(s).
1370
1371 =item NOVABOOT_BENDER
1372
1373 Defining this variable has the same meaning as B<--bender> option.
1374
1375 =back
1376
1377 =head1 AUTHORS
1378
1379 Michal Sojka <sojka@os.inf.tu-dresden.de>