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