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