]> rtime.felk.cvut.cz Git - novaboot.git/blob - novaboot
c28b7e28fe9c44e9bd2872747e5d3ae7044b4aa7
[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 GetOptionsFromArray);
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-system-i386 -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     "ryu" =>  '--uboot --uboot-init="mw f0000b00 \${psc_cfg}; sleep 1" --uboot-addr kernel=800000 --uboot-addr ramdisk=b00000 --uboot-addr fdt=7f0000',
51     "ryuglab" => '--target ryu --server=pc-sojkam.felk.cvut.cz:/srv/tftp --remote-cmd="ssh -tt pc-sojkam.felk.cvut.cz \"sterm -d -s 115200 /dev/ttyUSB0\""',
52     "ryulocal" => '--target ryu --dhcp-tftp --serial --reset-cmd="if which dtrrts; then dtrrts $NB_SERIAL 0 1; sleep 0.1; dtrrts $NB_SERIAL 1 1; fi"',
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     @cfgs = reverse @cfgs;
92     $dir = $ENV{'NOVABOOT_CONFIG_DIR'} || '/etc/novaboot.d';
93     if (opendir(my $dh, $dir)) {
94         my @etccfg = map { "$dir/$_" } grep { /^[-_a-zA-Z0-9]+$/ && -f "$dir/$_" } readdir($dh);
95         closedir $dh;
96         @etccfg = sort(@etccfg);
97         @cfgs = ( @etccfg, @cfgs );
98     }
99 }
100 my $cfg = $ENV{'NOVABOOT_CONFIG'};
101 Getopt::Long::Configure(qw/no_ignore_case pass_through/);
102 GetOptions ("config|c=s" => \$cfg);
103 read_config($_) foreach $cfg or @cfgs;
104
105 ## Command line handling
106
107 my $explicit_target;
108 GetOptions ("target|t=s" => \$explicit_target);
109
110 my ($amt, @append, $bender, @chainloaders, $concat, $config_name_opt, $dhcp_tftp, $dump_opt, $dump_config, @exiton, @expect_raw, $gen_only, $grub_config, $grub_prefix, $grub_preamble, $grub2_prolog, $grub2_config, $help, $ider, $iprelay, $iso_image, $interactive, $kernel_opt, $make, $man, $no_file_gen, $off_opt, $on_opt, $pulsar, $pulsar_root, $qemu, $qemu_append, $qemu_flags_cmd, $remote_cmd, $remote_expect, $reset, $reset_cmd, $rom_prefix, $rsync_flags, @scriptmod, $scons, $serial, $server, $stty, $tftp, $tftp_port, $uboot, %uboot_addr, @uboot_init);
111
112 $rsync_flags = '';
113 $rom_prefix = 'rom://';
114 $stty = 'raw -crtscts -onlcr 115200';
115 $reset = 1;                     # Reset target by default
116
117 my @expect_seen = ();
118 sub handle_expect
119 {
120     my ($n, $v) = @_;
121     push(@expect_seen, '-re') if $n eq "expect-re";
122     push(@expect_seen, $v);
123 }
124
125 sub handle_send
126 {
127     my ($n, $v) = @_;
128     unless (@expect_seen) { die("No --expect before --send"); }
129     my $ret = ($n eq "sendcont") ? exp_continue : 0;
130     unshift(@expect_raw, sub { shift->send(eval("\"$v\"")); $ret; });
131     unshift(@expect_raw, @expect_seen);
132     @expect_seen = ();
133 }
134
135 my %opt_spec;
136 %opt_spec = (
137     "amt=s"          => \$amt,
138     "append|a=s"     => \@append,
139     "bender|b"       => \$bender,
140     "build-dir=s"    => sub { my ($n, $v) = @_; $builddir = File::Spec->rel2abs($v); },
141     "concat"         => \$concat,
142     "chainloader=s"  => \@chainloaders,
143     "dhcp-tftp|d"    => \$dhcp_tftp,
144     "dump"           => \$dump_opt,
145     "dump-config"    => \$dump_config,
146     "exiton=s"       => \@exiton,
147     "expect=s"       => \&handle_expect,
148     "expect-re=s"    => \&handle_expect,
149     "expect-raw=s"   => sub { my ($n, $v) = @_; unshift(@expect_raw, eval($v)); },
150     "gen-only"       => \$gen_only,
151     "grub|g:s"       => \$grub_config,
152     "grub-preamble=s"=> \$grub_preamble,
153     "prefix|grub-prefix=s" => \$grub_prefix,
154     "grub2:s"        => \$grub2_config,
155     "grub2-prolog=s" => \$grub2_prolog,
156     "ider"           => \$ider,
157     "iprelay=s"      => \$iprelay,
158     "iso:s"          => \$iso_image,
159     "kernel|k=s"     => \$kernel_opt,
160     "interactive|i"  => \$interactive,
161     "name=s"         => \$config_name_opt,
162     "make|m:s"       => \$make,
163     "no-file-gen"    => \$no_file_gen,
164     "off"            => \$off_opt,
165     "on"             => \$on_opt,
166     "pulsar|p:s"     => \$pulsar,
167     "pulsar-root=s"  => \$pulsar_root,
168     "qemu|Q:s"       => \$qemu,
169     "qemu-append=s"  => \$qemu_append,
170     "qemu-flags|q=s" => \$qemu_flags_cmd,
171     "remote-cmd=s"   => \$remote_cmd,
172     "remote-expect=s"=> \$remote_expect,
173     "reset!"         => \$reset,
174     "reset-cmd=s"    => \$reset_cmd,
175     "rsync-flags=s"  => \$rsync_flags,
176     "scons:s"        => \$scons,
177     "scriptmod=s"    => \@scriptmod,
178     "send=s"         => \&handle_send,
179     "sendcont=s"     => \&handle_send,
180     "serial|s:s"     => \$serial,
181     "server:s"       => \$server,
182     "strip-rom"      => sub { $rom_prefix = ''; },
183     "stty=s"         => \$stty,
184     "tftp"           => \$tftp,
185     "tftp-port=i"    => \$tftp_port,
186     "uboot:s"        => \$uboot,
187     "uboot-addr=s"   => \%uboot_addr,
188     "uboot-init=s"   => \@uboot_init,
189     "h"              => \$help,
190     "help"           => \$man,
191     );
192
193 # First process target options
194 {
195     my $t = defined($explicit_target) ? $explicit_target : $CFG::default_target;
196     my @target_expanded;
197     Getopt::Long::Configure(qw/no_ignore_case pass_through/);
198     while ($t) {
199         exists $CFG::targets{$t} or die("Unknown target '$t' (valid targets are: ".join(", ", sort keys(%CFG::targets)).")");
200
201         undef $explicit_target;
202         my ($ret, $remaining_args) = GetOptionsFromString ($CFG::targets{$t}, ("target|t=s" => \$explicit_target));
203         if (!$ret) { die "Error parsing target $t option"; }
204         push(@target_expanded, @$remaining_args);
205         $t = $explicit_target;
206     }
207     Getopt::Long::Configure(qw/no_ignore_case no_pass_through/);
208     GetOptionsFromArray(\@target_expanded, %opt_spec) or die ("Error in target definition");
209 }
210
211 # Then process other command line options - some of them may override
212 # what was specified by the target
213 GetOptions %opt_spec or die("Error in command line arguments");
214 pod2usage(1) if $help;
215 pod2usage(-exitstatus => 0, -verbose => 2) if $man;
216
217 ### Dump sanitized configuration (if requested)
218
219 if ($dump_config) {
220     use Data::Dumper;
221     $Data::Dumper::Indent=1;
222     print "# This file is in perl syntax.\n";
223     foreach my $key(sort(keys(%CFG::))) { # See "Symbol Tables" in perlmod(1)
224         if (defined ${$CFG::{$key}}) { print Data::Dumper->Dump([${$CFG::{$key}}], ["*$key"]); }
225         if (        @{$CFG::{$key}}) { print Data::Dumper->Dump([\@{$CFG::{$key}}], ["*$key"]); }
226         if (        %{$CFG::{$key}}) { print Data::Dumper->Dump([\%{$CFG::{$key}}], ["*$key"]); }
227     }
228     print "1;\n";
229     exit;
230 }
231
232 ### Sanitize configuration
233
234 if ($interactive && !-t STDIN) {
235     die("novaboot: Interactive mode not supported when not on terminal");
236 }
237
238 if (defined $config_name_opt && scalar(@ARGV) > 1) { die "You cannot use --name with multiple scripts"; }
239
240 if ($ider) {
241     $iso_image //= ''; # IDE-R needs an ISO image
242     if (!defined $amt) { die "Error: --ider requires --amt"; }
243 }
244
245 # Default options
246 if (defined $serial) {
247     $serial ||= "/dev/ttyUSB0";
248     $ENV{NB_SERIAL} = $serial;
249 }
250 if (defined $grub_config) { $grub_config ||= "menu.lst"; }
251 if (defined $grub2_config) { $grub2_config ||= "grub.cfg"; }
252
253 ## Parse the novaboot script(s)
254 my @scripts;
255 my $file;
256 my $EOF;
257 my $last_fn = '';
258 my ($modules, $variables, $generated, $continuation) = ([], {}, []);
259 my $skip_reading = defined($on_opt) || defined($off_opt);
260 while (!$skip_reading && ($_ = <>)) {
261     if ($ARGV ne $last_fn) { # New script
262         die "Missing EOF in $last_fn" if $file;
263         die "Unfinished line in $last_fn" if $continuation;
264         $last_fn = $ARGV;
265         push @scripts, { 'filename' => $ARGV,
266                          'modules' => $modules = [],
267                          'variables' => $variables = {},
268                          'generated' => $generated = []};
269
270     }
271     chomp();
272     next if /^#/ || /^\s*$/;    # Skip comments and empty lines
273
274     $_ =~ s/^[[:space:]]*// if ($continuation);
275
276     if (/\\$/) {                # Line continuation
277         $continuation .= substr($_, 0, length($_)-1);
278         next;
279     }
280
281     if ($continuation) {        # Last continuation line
282         $_ = $continuation . $_;
283         $continuation = '';
284     }
285
286     foreach my $mod(@scriptmod) { eval $mod; }
287
288     if ($file && $_ eq $EOF) {  # Heredoc end
289         undef $file;
290         next;
291     }
292     if ($file) {                # Heredoc content
293         push @{$file}, "$_\n";
294         next;
295     }
296     if (/^([A-Z_]+)=(.*)$/) {   # Internal variable
297         $$variables{$1} = $2;
298         push(@exiton, $2) if ($1 eq "EXITON");
299         next;
300     }
301     if (s/^load *//) {          # Load line
302         die("novaboot: '$last_fn' line $.: Missing file name\n") unless /^[^ <]+/;
303         if (/^([^ ]*)(.*?)[[:space:]]*<<([^ ]*)$/) { # Heredoc start
304             push @$modules, "$1$2";
305             $file = [];
306             push @$generated, {filename => $1, content => $file};
307             $EOF = $3;
308             next;
309         }
310         if (/^([^ ]*)(.*?)[[:space:]]*< ?(.*)$/) { # Command substitution
311             push @$modules, "$1$2";
312             push @$generated, {filename => $1, command => $3};
313             next;
314         }
315         push @$modules, $_;
316         next;
317     }
318     if (/^run (.*)/) {          # run line
319         push @$generated, {command => $1};
320         next;
321     }
322     if (/^uboot (.*)/) {        # uboot line
323         # TODO: If U-Boot supports some interactive menud, it might
324         # make sense to store uboot lines per novaboot script.
325         push @uboot_init, $1;
326         next;
327     }
328
329     die("novaboot: Cannot parse script '$last_fn' line $.. Didn't you forget 'load' keyword?\n");
330 }
331 # use Data::Dumper;
332 # print Dumper(\@scripts);
333
334 foreach my $script (@scripts) {
335     $modules = $$script{modules};
336     @$modules[0] =~ s/^[^ ]*/$kernel_opt/ if $kernel_opt;
337     @$modules[0] .= ' ' . join(' ', @append) if @append;
338
339     my $kernel;
340     if (exists $variables->{KERNEL}) {
341         $kernel = $variables->{KERNEL};
342     } else {
343         if ($CFG::hypervisor) {
344             $kernel = $CFG::hypervisor . " ";
345             if (exists $variables->{HYPERVISOR_PARAMS}) {
346                 $kernel .= $variables->{HYPERVISOR_PARAMS};
347             } else {
348                 $kernel .= $CFG::hypervisor_params;
349             }
350         }
351     }
352     @$modules = ($kernel, @$modules) if $kernel;
353     @$modules = (@chainloaders, @$modules);
354     @$modules = ("bin/boot/bender", @$modules) if ($bender || defined $ENV{'NOVABOOT_BENDER'});
355 }
356
357 if ($dump_opt) {
358     foreach my $script (@scripts) {
359         print join("\n", @{$$script{modules}})."\n";
360     }
361     exit(0);
362 }
363
364 ## Helper functions
365
366 sub generate_configs($$$) {
367     my ($base, $generated, $filename) = @_;
368     if ($base) { $base = "$base/"; };
369     foreach my $g(@$generated) {
370       if (exists $$g{content}) {
371         my $config = $$g{content};
372         my $fn = $$g{filename};
373         open(my $f, '>', $fn) || die("$fn: $!");
374         map { s|\brom://([^ ]*)|$rom_prefix$base$1|g; print $f "$_"; } @{$config};
375         close($f);
376         print "novaboot: Created $fn\n";
377       } elsif (exists $$g{command} && ! $no_file_gen) {
378         $ENV{SRCDIR} = dirname(File::Spec->rel2abs( $filename, $invocation_dir ));
379         if (exists $$g{filename}) {
380             system_verbose("( $$g{command} ) > $$g{filename}");
381         } else {
382             system_verbose($$g{command});
383         }
384       }
385     }
386 }
387
388 sub generate_grub_config($$$$;$)
389 {
390     my ($filename, $title, $base, $modules_ref, $preamble) = @_;
391     if ($base) { $base = "$base/"; };
392     open(my $fg, '>', $filename) or die "$filename: $!";
393     print $fg "$preamble\n" if $preamble;
394     print $fg "title $title\n" if $title;
395     #print $fg "root $base\n"; # root doesn't really work for (nd)
396     my $first = 1;
397     foreach (@$modules_ref) {
398         if ($first) {
399             $first = 0;
400             my ($kbin, $kcmd) = split(' ', $_, 2);
401             $kcmd = '' if !defined $kcmd;
402             print $fg "kernel ${base}$kbin $kcmd\n";
403         } else {
404             s|\brom://([^ ]*)|$rom_prefix$base$1|g; # Translate rom:// files - needed for vdisk parameter of sigma0
405             print $fg "module $base$_\n";
406         }
407     }
408     close($fg);
409     print("novaboot: Created $builddir/$filename\n");
410     return $filename;
411 }
412
413 sub generate_syslinux_config($$$$)
414 {
415     my ($filename, $title, $base, $modules_ref) = @_;
416     if ($base && $base !~ /\/$/) { $base = "$base/"; };
417     open(my $fg, '>', $filename) or die "$filename: $!";
418     print $fg "LABEL $title\n";
419     #TODO print $fg "MENU LABEL $human_readable_title\n";
420
421     my ($kbin, $kcmd) = split(' ', @$modules_ref[0], 2);
422
423     if (system("file $kbin|grep 'Linux kernel'") == 0) {
424         my $initrd = @$modules_ref[1];
425         die('To many "load" lines for Linux kernel') if (scalar @$modules_ref > 2);
426         print $fg "LINUX $base$kbin\n";
427         print $fg "APPEND $kcmd\n";
428         print $fg "INITRD $base$initrd\n";
429     } else {
430         print $fg "KERNEL mboot.c32\n";
431         my @append;
432         foreach (@$modules_ref) {
433             s|\brom://([^ ]*)|$rom_prefix$base$1|g; # Translate rom:// files - needed for vdisk parameter of sigma0
434             push @append, "$base$_";
435             print $fg "APPEND ".join(' --- ', @append)."\n";
436         }
437     }
438     #TODO print $fg "TEXT HELP\n";
439     #TODO print $fg "some help here\n";
440     #TODO print $fg "ENDTEXT\n";
441     close($fg);
442     print("novaboot: Created $builddir/$filename\n");
443     return $filename;
444 }
445
446 sub generate_grub2_config($$$$;$$)
447 {
448     my ($filename, $title, $base, $modules_ref, $preamble, $prolog) = @_;
449     if ($base && substr($base,-1,1) ne '/') { $base = "$base/"; };
450     open(my $fg, '>', $filename) or die "$filename: $!";
451     print $fg "$preamble\n" if $preamble;
452     $title ||= 'novaboot';
453     print $fg "menuentry $title {\n";
454     print $fg "$prolog\n" if $prolog;
455     my $first = 1;
456     foreach (@$modules_ref) {
457         if ($first) {
458             $first = 0;
459             my ($kbin, $kcmd) = split(' ', $_, 2);
460             $kcmd = '' if !defined $kcmd;
461             print $fg "  multiboot ${base}$kbin $kcmd\n";
462         } else {
463             my @args = split;
464             # GRUB2 doesn't pass filename in multiboot info so we have to duplicate it here
465             $_ = join(' ', ($args[0], @args));
466             s|\brom://|$rom_prefix|g; # We do not need to translate path for GRUB2
467             print $fg "  module $base$_\n";
468         }
469     }
470     print $fg "}\n";
471     close($fg);
472     print("novaboot: Created $builddir/$filename\n");
473     return $filename;
474 }
475
476 sub generate_pulsar_config($$)
477 {
478     my ($filename, $modules_ref) = @_;
479     open(my $fg, '>', $filename) or die "$filename: $!";
480     print $fg "root $pulsar_root\n" if defined $pulsar_root;
481     my $first = 1;
482     my ($kbin, $kcmd);
483     foreach (@$modules_ref) {
484         if ($first) {
485             $first = 0;
486             ($kbin, $kcmd) = split(' ', $_, 2);
487             $kcmd = '' if !defined $kcmd;
488         } else {
489             my @args = split;
490             s|\brom://|$rom_prefix|g;
491             print $fg "load $_\n";
492         }
493     }
494     # Put kernel as last - this is needed for booting Linux and has no influence on non-Linux OSes
495     print $fg "exec $kbin $kcmd\n";
496     close($fg);
497     print("novaboot: Created $builddir/$filename\n");
498     return $filename;
499 }
500
501 sub shell_cmd_string(@)
502 {
503     return join(' ', map((/^[-_=a-zA-Z0-9\/\.\+]+$/ ? "$_" : "'$_'"), @_));
504 }
505
506 sub exec_verbose(@)
507 {
508     print "novaboot: Running: ".shell_cmd_string(@_)."\n";
509     exec(@_);
510     exit(1); # should not be reached
511 }
512
513 sub system_verbose($)
514 {
515     my $cmd = shift;
516     print "novaboot: Running: $cmd\n";
517     my $ret = system($cmd);
518     if ($ret & 0x007f) { die("Command terminated by a signal"); }
519     if ($ret & 0xff00) {die("Command exit with non-zero exit code"); }
520     if ($ret) { die("Command failure $ret"); }
521 }
522
523 ## WvTest headline
524
525 if (exists $variables->{WVDESC}) {
526     print "Testing \"$variables->{WVDESC}\" in $last_fn:\n";
527 } elsif ($last_fn =~ /\.wv$/) {
528     print "Testing \"all\" in $last_fn:\n";
529 }
530
531 ## Connect to the target and check whether it is not occupied
532
533 # We have to do this before file generation phase, because file
534 # generation is intermixed with file deployment phase and we want to
535 # check whether the target is not used by somebody else before
536 # deploying files. Otherwise, we may rewrite other user's files on a
537 # boot server.
538
539 my $exp; # Expect object to communicate with the target over serial line
540
541 my ($target_reset, $target_power_on, $target_power_off);
542 my ($amt_user, $amt_password, $amt_host, $amt_port);
543
544 if (defined $iprelay) {
545     my $IPRELAY;
546     $iprelay =~ /([.0-9]+)(:([0-9]+))?/;
547     my $addr = $1;
548     my $port = $3 || 23;
549     my $paddr   = sockaddr_in($port, inet_aton($addr));
550     my $proto   = getprotobyname('tcp');
551     socket($IPRELAY, PF_INET, SOCK_STREAM, $proto)  || die "socket: $!";
552     print "novaboot: Connecting to IP relay... ";
553     connect($IPRELAY, $paddr)    || die "connect: $!";
554     print "done\n";
555     $exp = Expect->init(\*$IPRELAY);
556     $exp->log_stdout(1);
557
558     while (1) {
559         print $exp "\xFF\xF6";  # AYT
560         my $connected = $exp->expect(20, # Timeout in seconds
561                                      '<iprelayd: connected>',
562                                      '-re', '<WEB51 HW[^>]*>');
563         last if $connected;
564     }
565
566     sub relaycmd($$) {
567         my ($relay, $onoff) = @_;
568         die unless ($relay == 1 || $relay == 2);
569
570         my $cmd = ($relay == 1 ? 0x5 : 0x6) | ($onoff ? 0x20 : 0x10);
571         return "\xFF\xFA\x2C\x32".chr($cmd)."\xFF\xF0";
572     }
573
574     sub relayconf($$) {
575         my ($relay, $onoff) = @_;
576         die unless ($relay == 1 || $relay == 2);
577         my $cmd = ($relay == 1 ? 0xdf : 0xbf) | ($onoff ? 0x00 : 0xff);
578         return "\xFF\xFA\x2C\x97".chr($cmd)."\xFF\xF0";
579     }
580
581     sub relay($$;$) {
582         my ($relay, $onoff, $can_giveup) = @_;
583         my $confirmation = '';
584         $exp->log_stdout(0);
585         print $exp relaycmd($relay, $onoff);
586         my $confirmed = $exp->expect(20, # Timeout in seconds
587                                      relayconf($relay, $onoff));
588         if (!$confirmed) {
589             if ($can_giveup) {
590                 print("Relay confirmation timeout - ignoring\n");
591             } else {
592                 die "Relay confirmation timeout";
593             }
594         }
595         $exp->log_stdout(1);
596     }
597
598     $target_reset = sub {
599         relay(2, 1, 1); # Reset the machine
600         usleep(100000);
601         relay(2, 0);
602     };
603
604     $target_power_off = sub {
605         relay(1, 1);            # Press power button
606         usleep(6000000);        # Long press to switch off
607         relay(1, 0);
608     };
609
610     $target_power_on = sub {
611         relay(1, 1);            # Press power button
612         usleep(100000);         # Short press
613         relay(1, 0);
614     };
615 }
616 elsif ($serial) {
617     my $CONN;
618     system_verbose("stty -F $serial $stty");
619     open($CONN, "+<", $serial) || die "open $serial: $!";
620     $exp = Expect->init(\*$CONN);
621 }
622 elsif ($remote_cmd) {
623     print "novaboot: Running: $remote_cmd\n";
624     $exp = Expect->spawn($remote_cmd);
625 }
626 elsif (defined $amt) {
627     require LWP::UserAgent;
628     require LWP::Authen::Digest;
629
630     sub genXML {
631         my ($host, $username, $password, $schema, $className, $pstate) = @_;
632         #AMT numbers for PowerStateChange (MNI => bluescreen on windows;-)
633         my %pstates = ("on"        => 2,
634                        "standby"   => 4,
635                        "hibernate" => 7,
636                        "off"       => 8,
637                        "reset"     => 10,
638                        "MNI"       => 11);
639         return <<END;
640                 <s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:w="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd">
641                 <s:Header><a:To>http://$host:16992/wsman</a:To>
642                 <w:ResourceURI s:mustUnderstand="true">$schema</w:ResourceURI>
643                 <a:ReplyTo><a:Address s:mustUnderstand="true">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address></a:ReplyTo>
644                 <a:Action s:mustUnderstand="true">$schema$className</a:Action>
645                 <w:MaxEnvelopeSize s:mustUnderstand="true">153600</w:MaxEnvelopeSize>
646                 <a:MessageID>uuid:709072C9-609C-4B43-B301-075004043C7C</a:MessageID>
647                 <w:Locale xml:lang="en-US" s:mustUnderstand="false" />
648                 <w:OperationTimeout>PT60.000S</w:OperationTimeout>
649                 <w:SelectorSet><w:Selector Name="Name">Intel(r) AMT Power Management Service</w:Selector></w:SelectorSet>
650                 </s:Header><s:Body>
651                 <p:RequestPowerStateChange_INPUT xmlns:p="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_PowerManagementService">
652                 <p:PowerState>$pstates{$pstate}</p:PowerState>
653                 <p:ManagedElement><a:Address>http://$host:16992/wsman</a:Address>
654                 <a:ReferenceParameters><w:ResourceURI>http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ComputerSystem</w:ResourceURI>
655                 <w:SelectorSet><w:Selector Name="Name">ManagedSystem</w:Selector></w:SelectorSet>
656                 </a:ReferenceParameters></p:ManagedElement>
657                 </p:RequestPowerStateChange_INPUT>
658                 </s:Body></s:Envelope>
659 END
660     }
661
662     sub sendPOST {
663         my ($host, $username, $password, $content) = @_;
664
665         my $ua = LWP::UserAgent->new();
666         $ua->agent("novaboot");
667
668         my $req = HTTP::Request->new(POST => "http://$host:16992/wsman");
669         my $res = $ua->request($req);
670         die ("Unexpected AMT response: " . $res->status_line) unless $res->code == 401;
671
672         my ($realm) = $res->header("WWW-Authenticate") =~ /Digest realm="(.*?)"/;
673         $ua->credentials("$host:16992", $realm, $username => $password);
674
675         # Create a request
676         $req = HTTP::Request->new(POST => "http://$host:16992/wsman");
677         $req->content_type('application/x-www-form-urlencoded');
678         $req->content($content);
679         $res = $ua->request($req);
680         die ("AMT power change request failed: " . $res->status_line) unless $res->is_success;
681         $res->content() =~ /<g:ReturnValue>(\d+)<\/g:ReturnValue>/;
682         return $1;
683     }
684
685     sub powerChange  {
686         my ($host, $username, $password, $pstate)=@_;
687         my $schema="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_PowerManagementService";
688         my $className="/RequestPowerStateChange";
689         my $content = genXML($host, $username, $password ,$schema, $className, $pstate);
690         return sendPOST($host, $username, $password, $content);
691     }
692
693     ($amt_user,$amt_password,$amt_host,$amt_port) = ($amt =~ /(?:(.*?)(?::(.*))?@)?([^:]*)(?::([0-9]*))?/);;
694     $amt_user ||= "admin";
695     $amt_password ||= $ENV{'AMT_PASSWORD'} || die "AMT password not specified";
696     $amt_host || die "AMT host not specified";
697     $amt_port ||= 16994;
698
699
700     $target_power_off = sub {
701         $exp->close();
702         my $result = powerChange($amt_host,$amt_user,$amt_password, "off");
703         die "AMT power off failed (ReturnValue $result)" if $result != 0;
704     };
705
706     $target_power_on = sub {
707         my $result = powerChange($amt_host,$amt_user,$amt_password, "on");
708         die "AMT power on failed (ReturnValue $result)" if $result != 0;
709     };
710
711     $target_reset = sub {
712         my $result = powerChange($amt_host,$amt_user,$amt_password, "reset");
713         if ($result != 0) {
714             print STDERR "Warning: Cannot reset $amt_host, trying power on. ";
715             $result = powerChange($amt_host,$amt_user,$amt_password, "on");
716         }
717         die "AMT reset failed (ReturnValue $result)" if $result != 0;
718     };
719
720     my $cmd = "amtterm -u $amt_user -p $amt_password $amt_host $amt_port";
721     print "novaboot: Running: $cmd\n" =~ s/\Q$amt_password\E/???/r;
722     $exp = Expect->spawn($cmd);
723     $exp->expect(10, "RUN_SOL") || die "Expect for 'RUN_SOL' timed out";
724
725 }
726
727
728 if ($remote_expect) {
729     $exp->expect(10, $remote_expect) || die "Expect for '$remote_expect' timed out";
730 }
731
732 if (defined $reset_cmd) {
733     $target_reset = sub {
734         system_verbose($reset_cmd);
735     };
736 }
737
738 if (defined $on_opt && defined $target_power_on) {
739     &$target_power_on();
740     exit;
741 }
742 if (defined $off_opt && defined $target_power_off) {
743     print "novaboot: Switching the target off...\n";
744     &$target_power_off();
745     exit;
746 }
747
748 $builddir ||= dirname(File::Spec->rel2abs( ${$scripts[0]}{filename})) if scalar @scripts;
749 if (defined $builddir) {
750     chdir($builddir) or die "Can't change directory to $builddir: $!";
751     print "novaboot: Entering directory `$builddir'\n";
752 } else {
753     $builddir = $invocation_dir;
754 }
755
756 ## File generation phase
757 my (%files_iso, $menu_iso, $filename);
758 my $config_name = '';
759 my $prefix = '';
760
761 foreach my $script (@scripts) {
762     $filename = $$script{filename};
763     $modules = $$script{modules};
764     $generated = $$script{generated};
765     $variables = $$script{variables};
766
767     ($config_name = $filename) =~ s#.*/##;
768     $config_name = $config_name_opt if (defined $config_name_opt);
769
770     if (exists $variables->{BUILDDIR}) {
771         $builddir = File::Spec->rel2abs($variables->{BUILDDIR});
772         chdir($builddir) or die "Can't change directory to $builddir: $!";
773         print "novaboot: Entering directory `$builddir'\n";
774     }
775
776     if ($grub_prefix) {
777         $prefix = $grub_prefix;
778         $prefix =~ s/\$NAME/$config_name/;
779         $prefix =~ s/\$BUILDDIR/$builddir/;
780     }
781     # TODO: use $grub_prefix as first parameter if some switch is given
782     generate_configs('', $generated, $filename);
783
784 ### Generate bootloader configuration files
785     my @bootloader_configs;
786     push @bootloader_configs, generate_grub_config($grub_config, $config_name, $prefix, $modules, $grub_preamble) if (defined $grub_config);
787     push @bootloader_configs, generate_grub2_config($grub2_config, $config_name, $prefix, $modules, $grub_preamble, $grub2_prolog) if (defined $grub2_config);
788     push @bootloader_configs, generate_pulsar_config('config-'.($pulsar||'novaboot'), $modules) if (defined $pulsar);
789
790 ### Run scons or make
791     {
792         my @files = map({ ($file) = m/([^ ]*)/; $file; } @$modules);
793         # Filter-out generated files
794         my @to_build = grep({ my $file = $_; !scalar(grep($file eq ($$_{filename} || ''), @$generated)) } @files);
795
796         system_verbose($scons || $CFG::scons." ".join(" ", @to_build)) if (defined $scons);
797         system_verbose($make  || $CFG::make ." ".join(" ", @to_build)) if (defined $make);
798     }
799
800 ### Copy files (using rsync)
801     if (defined $server && !defined($gen_only)) {
802         (my $real_server = $server) =~ s/\$NAME/$config_name/;
803
804         my ($hostname, $path) = split(":", $real_server, 2);
805         if (! defined $path) {
806             $path = $hostname;
807             $hostname = "";
808         }
809         my $files = join(" ", map({ ($file) = m/([^ ]*)/; $file; } ( @$modules, @bootloader_configs)));
810         map({ my $file = (split)[0]; die "$file: $!" if ! -f $file; } @$modules);
811         my $istty = -t STDOUT && ($ENV{'TERM'} || 'dumb') ne 'dumb';
812         my $progress = $istty ? "--progress" : "";
813         system_verbose("rsync $progress -RLp $rsync_flags $files $real_server");
814         if ($server =~ m|/\$NAME$| && $concat) {
815             my $cmd = join("; ", map { "( cd $path/.. && cat */$_ > $_ )" } @bootloader_configs);
816             system_verbose($hostname ? "ssh $hostname '$cmd'" : $cmd);
817         }
818     }
819
820 ### Prepare ISO image generation
821     if (defined $iso_image) {
822         generate_configs("(cd)", $generated, $filename);
823         my $menu;
824         generate_syslinux_config(\$menu, $config_name, "/", $modules);
825         $menu_iso .= "$menu\n";
826         map { ($file,undef) = split; $files_iso{$file} = 1; } @$modules;
827     }
828 }
829
830 ## Generate ISO image
831 if (defined $iso_image) {
832     system_verbose("mkdir -p isolinux");
833
834     my @files;
835     if (-f '/usr/lib/ISOLINUX/isolinux.bin') {
836         # Newer ISOLINUX version
837         @files = qw(/usr/lib/ISOLINUX/isolinux.bin /usr/lib/syslinux/modules/bios/mboot.c32 /usr/lib/syslinux/modules/bios/libcom32.c32 /usr/lib/syslinux/modules/bios/menu.c32 /usr/lib/syslinux/modules/bios/ldlinux.c32);
838     } else {
839         # Older ISOLINUX version
840         @files = qw(/usr/lib/syslinux/isolinux.bin /usr/lib/syslinux/mboot.c32 /usr/lib/syslinux/menu.c32);
841     }
842     system_verbose("cp @files isolinux");
843     open(my $fh, ">isolinux/isolinux.cfg");
844     if ($#scripts) {
845         print $fh "TIMEOUT 50\n";
846         print $fh "DEFAULT menu\n";
847     } else {
848         print $fh "DEFAULT $config_name\n";
849     }
850     print $fh "$menu_iso";
851     close($fh);
852
853     my $files = join(" ", map("$_=$_", (keys(%files_iso), 'isolinux/isolinux.cfg', map(s|.*/|isolinux/|r, @files))));
854     $iso_image ||= "$config_name.iso";
855
856     # Note: We use -U flag below to "Allow 'untranslated' filenames,
857     # completely violating the ISO9660 standards". Without this
858     # option, isolinux is not able to read files names for example
859     # bzImage-3.0.
860     system_verbose("$CFG::genisoimage -R -b isolinux/isolinux.bin -c isolinux/boot.cat -no-emul-boot -boot-load-size 4 -boot-info-table -hide-rr-moved -U -o $iso_image -graft-points $files");
861     print("ISO image created: $builddir/$iso_image\n");
862 }
863
864 exit(0) if defined $gen_only;
865
866 ## Boot the system using various methods and send serial output to stdout
867
868 if (scalar(@scripts) > 1 && ( defined $dhcp_tftp || defined $serial || defined $iprelay)) {
869     die "You cannot do this with multiple scripts simultaneously";
870 }
871
872 if ($variables->{WVTEST_TIMEOUT}) {
873     print "wvtest: timeout ", $variables->{WVTEST_TIMEOUT}, "\n";
874 }
875
876 sub trim($) {
877     my ($str) = @_;
878     $str =~ s/^\s+|\s+$//g;
879     return $str
880 }
881
882 ### Start in Qemu
883
884 if (defined $qemu) {
885     # Qemu
886     $qemu ||= $variables->{QEMU} || $CFG::qemu;
887     my @qemu_flags = split(" ", $qemu);
888     $qemu = shift(@qemu_flags);
889
890     @qemu_flags = split(/ +/, trim($variables->{QEMU_FLAGS})) if exists $variables->{QEMU_FLAGS};
891     @qemu_flags = split(/ +/, trim($qemu_flags_cmd)) if $qemu_flags_cmd;
892     push(@qemu_flags, split(/ +/, trim($qemu_append || '')));
893
894     if (defined $iso_image) {
895         # Boot NOVA with grub (and test the iso image)
896         push(@qemu_flags, ('-cdrom', $iso_image));
897     } else {
898         # Boot NOVA without GRUB
899
900         # Non-patched qemu doesn't like commas, but NUL can live with pluses instead of commans
901         foreach (@$modules) {s/,/+/g;}
902         generate_configs("", $generated, $filename);
903
904         if (scalar @$modules) {
905             my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);
906             $kcmd = '' if !defined $kcmd;
907             my $dtb;
908             @$modules = map { if (/\.dtb$/) { $dtb=$_; (); } else { $_ } } @$modules;
909             my $initrd = join ",", @$modules;
910
911             push(@qemu_flags, ('-kernel', $kbin, '-append', $kcmd));
912             push(@qemu_flags, ('-initrd', $initrd)) if $initrd;
913             push(@qemu_flags, ('-dtb', $dtb)) if $dtb;
914         }
915     }
916     push(@qemu_flags,  qw(-serial stdio)); # Redirect serial output (for collecting test restuls)
917     unshift(@qemu_flags, ('-name', $config_name));
918     print "novaboot: Running: ".shell_cmd_string($qemu, @qemu_flags)."\n";
919     $exp = Expect->spawn(($qemu, @qemu_flags)) || die("exec() failed: $!");
920 }
921
922 ### Local DHCPD and TFTPD
923
924 my ($dhcpd_pid, $tftpd_pid);
925
926 $tftp=1 if $tftp_port;
927
928 if (defined $dhcp_tftp)
929 {
930     generate_configs("(nd)", $generated, $filename);
931     system_verbose('mkdir -p tftpboot');
932     generate_grub_config("tftpboot/os-menu.lst", $config_name, "(nd)", \@$modules, "timeout 0");
933     open(my $fh, '>', 'dhcpd.conf');
934     my $mac = `cat /sys/class/net/eth0/address`;
935     chomp $mac;
936     print $fh "subnet 10.23.23.0 netmask 255.255.255.0 {
937                       range 10.23.23.10 10.23.23.100;
938                       filename \"bin/boot/grub/pxegrub.pxe\";
939                       next-server 10.23.23.1;
940 }
941 host server {
942         hardware ethernet $mac;
943         fixed-address 10.23.23.1;
944 }";
945     close($fh);
946     system_verbose("sudo ip a add 10.23.23.1/24 dev eth0;
947             sudo ip l set dev eth0 up;
948             sudo touch dhcpd.leases");
949
950     # We run servers by forking ourselves, because the servers end up
951     # in our process group and get killed by signals sent to the
952     # process group (e.g. Ctrl-C on terminal).
953     $dhcpd_pid = fork();
954     exec_verbose("sudo dhcpd -d -cf dhcpd.conf -lf dhcpd.leases -pf dhcpd.pid") if ($dhcpd_pid == 0);
955 }
956
957 if (defined $dhcp_tftp || defined $tftp) {
958     $tftp_port ||= 69;
959     # Unfortunately, tftpd requires root privileges even with
960     # non-privileged (>1023) port due to initgroups().
961     system_verbose("sudo in.tftpd --listen --secure -v -v -v --pidfile tftpd.pid  --address :$tftp_port $builddir");
962
963     # Kill server when we die
964     $SIG{__DIE__} = sub { system_verbose('sudo pkill --pidfile=dhcpd.pid') if (defined $dhcp_tftp);
965                           system_verbose('sudo pkill --pidfile=tftpd.pid'); };
966
967     # We have to kill tftpd explicitely, because it is not in our process group
968     $SIG{INT} = sub { system_verbose('sudo pkill --pidfile=tftpd.pid'); exit(0); };
969 }
970
971 ### AMT IDE-R
972 if (defined $ider) {
973     my $ider_cmd= "amtider -c $iso_image -u $amt_user -p $amt_password $amt_host $amt_port"  ;
974     print "novaboot: Running: $ider_cmd\n" =~ s/\Q$amt_password\E/???/r;
975     my $ider_pid = fork();
976     if ($ider_pid == 0) {
977         exec($ider_cmd);
978         die "IDE redirection failed";
979     }
980     # FIXME: This collides with --tftp option. Hopefully, nobody needs
981     # to use both simultaneously.
982     $SIG{__DIE__} = sub { system_verbose('kill $ider_pid'); };
983 }
984
985 ### Reset target (IP relay, AMT, ...)
986
987 if (defined $target_reset && $reset) {
988     print "novaboot: Reseting the test box... ";
989     &$target_reset();
990     print "done\n";
991 }
992
993 ### U-boot conversation
994 if (defined $uboot) {
995     my $uboot_prompt = $uboot || '=> ';
996     print "novaboot: Waiting for U-Boot prompt...\n";
997     $exp->log_stdout(1);
998     #$exp->exp_internal(1);
999     $exp->expect(20,
1000                  [qr/Hit any key to stop autoboot:/, sub { $exp->send("\n"); exp_continue; }],
1001                  $uboot_prompt) || die "No U-Boot prompt deteceted";
1002     foreach my $cmd (@uboot_init) {
1003         if ($cmd =~ /\$NB_MYIP/) {
1004             my $ip = (grep /inet /, `ip addr show eth0`)[0] || die "Problem determining our IP address";
1005             $ip =~ s/\s*inet ([0-9.]*).*/$1/;
1006             $cmd =~ s/\$NB_MYIP/$ip/g;
1007         }
1008         chomp($cmd);
1009         $exp->send("$cmd\n");
1010         $exp->expect(10, $uboot_prompt) || die "U-Boot prompt timeout";
1011     }
1012
1013     # Boot the system if there are some load lines in the script
1014     if (scalar(@$modules) > 0) {
1015         my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);
1016         my $dtb;
1017         @$modules = map { if (/\.dtb$/) { $dtb=$_; (); } else { $_ } } @$modules;
1018         my $initrd = shift @$modules;
1019
1020         my ($ramdisk_addr, $fdt_addr) = ('-', '');
1021
1022         die "No '--uboot-addr kernel' given" unless $uboot_addr{kernel};
1023         $exp->send("tftpboot $uboot_addr{kernel} $prefix$kbin\n");
1024         $exp->expect(10,
1025                      [qr/##/, sub { exp_continue; }],
1026                      $uboot_prompt) || die "Kernel load timeout";
1027         if (defined $dtb) {
1028             die "No '--uboot-addr fdt' given" unless $uboot_addr{fdt};
1029             $fdt_addr = $uboot_addr{fdt};
1030             $exp->send("tftpboot $fdt_addr $prefix$dtb\n");
1031             $exp->expect(10,
1032                          [qr/##/, sub { exp_continue; }],
1033                          $uboot_prompt) || die "Device tree load timeout";
1034         }
1035         if (defined $initrd) {
1036             die "No '--uboot-addr ramdisk' given" unless $uboot_addr{ramdisk};
1037             $ramdisk_addr = $uboot_addr{ramdisk};
1038             $exp->send("tftpboot $ramdisk_addr $prefix$initrd\n");
1039             $exp->expect(10,
1040                          [qr/#/, sub { exp_continue; }],
1041                          $uboot_prompt) || die "Initrd load timeout";
1042         }
1043         $exp->send("echo $kcmd\n");
1044         $exp->expect(1, '-re', qr{echo .*\n(.*)\n$uboot_prompt})  || die "Command line test timeout";
1045         my $args = ($exp->matchlist)[0];
1046         if ($args =~ /^setenv\s+bootargs/) {
1047             $exp->send("$args\n");
1048         } else {
1049             $exp->send("setenv bootargs $kcmd\n");
1050         }
1051         $exp->expect(5, $uboot_prompt)  || die "U-Boot prompt timeout";
1052         $exp->send("bootm $uboot_addr{kernel} $ramdisk_addr $fdt_addr\n");
1053         $exp->expect(5, "\n")  || die "U-Boot command timeout";
1054     }
1055 }
1056
1057 ### Serial line interaction
1058 if (defined $exp) {
1059     # Serial line of the target is available
1060     my $interrupt = 'Ctrl-C';
1061     if ($interactive && !@exiton) {
1062         $interrupt = '"~~."';
1063     }
1064     my $note = (-t STDIN) ? '' : '- only target->host ';
1065     print "novaboot: Serial line interaction $note(press $interrupt to interrupt)...\n";
1066     $exp->log_stdout(1);
1067     if (@exiton) {
1068         $exp->expect(undef, @expect_raw, @exiton);
1069     } else {
1070         my @inputs = ($exp);
1071         if (-t STDIN) { # Set up bi-directional communication if we run on terminal
1072             my $infile = new IO::File;
1073             $infile->IO::File::fdopen(*STDIN,'r');
1074             my $in_object = Expect->exp_init($infile);
1075             $in_object->set_group($exp);
1076
1077             if ($interactive) {
1078                 $in_object->set_seq('~~\.', sub { print "novaboot: Escape sequence detected\r\n"; undef; });
1079                 $in_object->manual_stty(0);       # Use raw terminal mode
1080             } else {
1081                 $in_object->manual_stty(1);       # Do not modify terminal settings
1082             }
1083             push(@inputs, $in_object);
1084         }
1085         #use Data::Dumper;
1086         #print Dumper(\@expect_raw);
1087         $exp->expect(undef, @expect_raw) if @expect_raw;
1088         Expect::interconnect(@inputs) unless defined($exp->exitstatus);
1089     }
1090 }
1091
1092 ## Kill dhcpc or tftpd
1093 if (defined $dhcp_tftp || defined $tftp) {
1094     die("novaboot: This should kill servers on background\n");
1095 }
1096
1097 ## Documentation
1098
1099 =head1 NAME
1100
1101 novaboot - A tool for booting various operating systems on various hardware or in qemu
1102
1103 =head1 SYNOPSIS
1104
1105 B<novaboot> --help
1106
1107 B<novaboot> [option]... [--] script...
1108
1109 B<./script> [option]...
1110
1111 =head1 DESCRIPTION
1112
1113 This program makes booting of an operating system (e.g. NOVA or Linux)
1114 as simple as running a local program. It facilitates booting on local
1115 or remote hosts or in emulators such as qemu. Novaboot operation is
1116 controlled by command line options and by a so called novaboot script,
1117 which can be thought as a generalization of bootloader configuration
1118 files (see L</"NOVABOOT SCRIPT SYNTAX">). Based on this input,
1119 novaboot setups everything for the target host to boot the desired
1120 configuration, i.e. it generates the bootloader configuration file in
1121 the proper format, deploy the binaries and other needed files to
1122 required locations, perhaps on a remote boot server and reset the
1123 target host. Finally, target host's serial output is redirected to
1124 standard output if that is possible.
1125
1126 Typical way of using novaboot is to make the novaboot script
1127 executable and set its first line to I<#!/usr/bin/env novaboot>. Then,
1128 booting a particular OS configuration becomes the same as executing a
1129 local program - the novaboot script.
1130
1131 For example, with C<novaboot> you can:
1132
1133 =over 3
1134
1135 =item 1.
1136
1137 Run an OS in Qemu. This is the default action when no other action is
1138 specified by command line switches. Thus running C<novaboot ./script>
1139 (or C<./script> as described above) will run Qemu and make it boot the
1140 configuration specified in the F<script>.
1141
1142 =item 2.
1143
1144 Create a bootloader configuration file (currently supported
1145 bootloaders are GRUB, GRUB2, ISOLINUX, Pulsar and U-Boot) and copy it
1146 with all other files needed for booting to a remote boot server.
1147
1148  ./script --server=192.168.1.1:/tftp --iprelay=192.168.1.2
1149
1150 This command copies files to the TFTP server and uses
1151 TCP/IP-controlled relay to reset the target host and receive its
1152 serial output.
1153
1154 =item 3.
1155
1156 Run DHCP and TFTP server on developer's machine to PXE-boot the target
1157 machine from it. E.g.
1158
1159  ./script --dhcp-tftp
1160
1161 When a PXE-bootable machine is connected via Ethernet to developer's
1162 machine, it will boot the configuration described in the I<script>.
1163
1164 =item 4.
1165
1166 Create bootable ISO images. E.g.
1167
1168  novaboot --iso -- script1 script2
1169
1170 The created ISO image will use ISOLINUX bootloader installed on it and
1171 the boot menu will allow selecting between I<script1> and I<script2>
1172 configurations.
1173
1174 =back
1175
1176 Note that the options needed for a specific target can be stored in a
1177 L</"CONFIGURATION FILE">. Then it is sufficient to use only the B<-t>
1178 option to specify the name of the target.
1179
1180 =head1 PHASES AND OPTIONS
1181
1182 Novaboot performs its work in several phases. Each phase can be
1183 influenced by several command line options, certain phases can be
1184 skipped. The list of phases (in the execution order) and the
1185 corresponding options follow.
1186
1187 =head2 Configuration reading phase
1188
1189 After starting, novaboot reads configuration files. Their content is
1190 described in section L</"CONFIGURATION FILE">. By default,
1191 configuration is read from two locations. First from the configuration
1192 directory and second from F<.novaboot> files along the path to the
1193 current directory. The later read files override settings from the
1194 former ones.
1195
1196 Configuration directory is determined by the content of
1197 NOVABOOT_CONFIG_DIR environment variable defaulting to
1198 F</etc/novaboot.d>. Files in this directory with names consisting
1199 solely from English letters, numbers, dashes '-' and underscores '_'
1200 (note that dot '.' is not included) are read in alphabetical order.
1201
1202 Then novaboot searches for files named F<.novaboot> starting from the
1203 directory of the novaboot script (or working directory, see bellow)
1204 and continuing upwards up to the root directory. The found
1205 configuration files are then read in the opposite order (i.e. from the
1206 root directory downwards). This allows to have, for example, user
1207 specific configuration in F<~/.novaboot> and project specific one in
1208 F<~/project/.novaboot>.
1209
1210 In certain cases, the location of the novaboot script cannot be
1211 determined in this early phase. This happens either when the script is
1212 read from the standard input or when novaboot is invoked explicitly as
1213 in the example L</"4."> above. In this case the current working
1214 directory is used as a starting point for configuration file search
1215 instead of the novaboot script directory.
1216
1217 =over 8
1218
1219 =item -c, --config=I<filename>
1220
1221 Use the specified configuration file instead of the default one(s).
1222
1223 =back
1224
1225 =head2 Command line processing phase
1226
1227 =over 8
1228
1229 =item --dump-config
1230
1231 Dump the current configuration to stdout end exits. Useful as an
1232 initial template for a configuration file.
1233
1234 =item -h, --help
1235
1236 Print short (B<-h>) or long (B<--help>) help.
1237
1238 =item -t, --target=I<target>
1239
1240 This option serves as a user configurable shortcut for other novaboot
1241 options. The effect of this option is the same as the options stored
1242 in the C<%targets> configuration variable under key I<target>. See
1243 also L</"CONFIGURATION FILE">.
1244
1245 =back
1246
1247 =head2 Script preprocessing phase
1248
1249 This phases allows to modify the parsed novaboot script before it is
1250 used in the later phases.
1251
1252 =over 8
1253
1254 =item -a, --append=I<parameters>
1255
1256 Append a string to the first C<load> line in the novaboot script. This
1257 can be used to append parameters to the kernel's or root task's
1258 command line. This option can appear multiple times.
1259
1260 =item -b, --bender
1261
1262 Use F<bender> chainloader. Bender scans the PCI bus for PCI serial
1263 ports and stores the information about them in the BIOS data area for
1264 use by the kernel.
1265
1266 =item --chainloader=I<chainloader>
1267
1268 Specifies a chainloader that is loaded before the kernel and other
1269 files specified in the novaboot script. E.g. 'bin/boot/bender
1270 promisc'.
1271
1272 =item --dump
1273
1274 Print the modules to boot and their parameters after this phase
1275 finishes. Then exit. This is useful for seeing the effect of other
1276 options in this section.
1277
1278 =item -k, --kernel=F<file>
1279
1280 Replace the first word on the first C<load> line in the novaboot
1281 script with F<file>.
1282
1283 =item --scriptmod=I<perl expression>
1284
1285 When novaboot script is read, I<perl expression> is executed for every
1286 line (in $_ variable). For example, C<novaboot
1287 --scriptmod=s/sigma0/omega6/g> replaces every occurrence of I<sigma0>
1288 in the script with I<omega6>.
1289
1290 When this option is present, it overrides I<$script_modifier> variable
1291 from the configuration file, which has the same effect. If this option
1292 is given multiple times all expressions are evaluated in the command
1293 line order.
1294
1295 =back
1296
1297 =head2 File generation phase
1298
1299 In this phase, files needed for booting are generated in a so called
1300 I<build directory> (see L</--build-dir>). In most cases configuration
1301 for a bootloader is generated automatically by novaboot. It is also
1302 possible to generate other files using I<heredoc> or I<"<"> syntax in
1303 novaboot scripts. Finally, binaries can be generated in this phases by
1304 running C<scons> or C<make>.
1305
1306 =over 8
1307
1308 =item --build-dir=I<directory>
1309
1310 Overrides the default build directory location.
1311
1312 The default build directory location is determined as follows: If the
1313 configuration file defines the C<$builddir> variable, its value is
1314 used. Otherwise, it is the directory that contains the first processed
1315 novaboot script.
1316
1317 See also L</BUILDDIR> variable.
1318
1319 =item -g, --grub[=I<filename>]
1320
1321 Generates grub bootloader menu file. If the I<filename> is not
1322 specified, F<menu.lst> is used. The I<filename> is relative to the
1323 build directory (see B<--build-dir>).
1324
1325 =item --grub-preamble=I<prefix>
1326
1327 Specifies the I<preable> that is at the beginning of the generated
1328 GRUB or GRUB2 config files. This is useful for specifying GRUB's
1329 timeout.
1330
1331 =item --prefix=I<prefix>
1332
1333 Specifies I<prefix> (e.g. F</srv/tftp>) that is put in front of every
1334 file name in generated bootloader configuration files (or in U-Boot
1335 commands).
1336
1337 If the I<prefix> contains string $NAME, it will be replaced with the
1338 name of the novaboot script (see also B<--name>).
1339
1340 If the I<prefix> contains string $BUILDDIR, it will be replaced with
1341 the build directory (see also B<--build-dir>).
1342
1343 =item --grub-prefix
1344
1345 Alias for B<--prefix>.
1346
1347 =item --grub2[=I<filename>]
1348
1349 Generate GRUB2 menu entry in I<filename>. If I<filename> is not
1350 specified F<grub.cfg> is used. The content of the menu entry can be
1351 customized with B<--grub-preamble>, B<--grub2-prolog> or
1352 B<--grub_prefix> options.
1353
1354 In order to use the the generated menu entry on your development
1355 machine that uses GRUB2, append the following snippet to
1356 F</etc/grub.d/40_custom> file and regenerate your grub configuration,
1357 i.e. run update-grub on Debian/Ubuntu.
1358
1359   if [ -f /path/to/nul/build/grub.cfg ]; then
1360     source /path/to/nul/build/grub.cfg
1361   fi
1362
1363 =item --grub2-prolog=I<prolog>
1364
1365 Specifies text that is put at the beginning of the GRUB2 menu entry.
1366
1367 =item -m, --make[=make command]
1368
1369 Runs C<make> to build files that are not generated by novaboot itself.
1370
1371 =item --name=I<string>
1372
1373 Use the name I<string> instead of the name of the novaboot script.
1374 This name is used for things like a title of grub menu or for the
1375 server directory where the boot files are copied to.
1376
1377 =item --no-file-gen
1378
1379 Do not run external commands to generate files (i.e. "<" syntax and
1380 C<run> keyword). This switch does not influence generation of files
1381 specified with "<<WORD" syntax.
1382
1383 =item -p, --pulsar[=mac]
1384
1385 Generates pulsar bootloader configuration file named F<config-I<mac>>
1386 The I<mac> string is typically a MAC address and defaults to
1387 I<novaboot>.
1388
1389 =item --scons[=scons command]
1390
1391 Runs C<scons> to build files that are not generated by novaboot
1392 itself.
1393
1394 =item --strip-rom
1395
1396 Strip I<rom://> prefix from command lines and generated config files.
1397 The I<rom://> prefix is used by NUL. For NRE, it has to be stripped.
1398
1399 =item --gen-only
1400
1401 Exit novaboot after file generation phase.
1402
1403 =back
1404
1405 =head2 Target connection check
1406
1407 If supported by the target, the connection to it is made and it is
1408 checked whether the target is not occupied by another novaboot
1409 user/instance.
1410
1411 =over 8
1412
1413 =item --amt=I<"[user[:password]@]host[:port]>
1414
1415 Use Intel AMT technology to control the target machine. WS management
1416 is used to powercycle it and Serial-Over-Lan (SOL) for input/output.
1417 The hostname or (IP address) is given by the I<host> parameter. If
1418 I<password> is not specified, environment variable AMT_PASSWORD is
1419 used. The I<port> specifies a TCP port for SOL. If not specified, the
1420 default is 16992. Default I<user> is admin.
1421
1422 =item --iprelay=I<addr[:port]>
1423
1424 Use TCP/IP relay and serial port to access the target's serial port
1425 and powercycle it. The IP address of the relay is given by I<addr>
1426 parameter. If I<port> is not specified, it default to 23.
1427
1428 Note: This option is supposed to work with HWG-ER02a IP relays.
1429
1430 =item -s, --serial[=device]
1431
1432 Target's serial line is connected to host's serial line (device). The
1433 default value for device is F</dev/ttyUSB0>.
1434
1435 The value of this option is exported in NB_NOVABOOT environment
1436 variable to all subprocesses run by C<novaboot>.
1437
1438 =item --stty=I<settings>
1439
1440 Specifies settings passed to C<stty> invoked on the serial line
1441 specified with B<--serial> option. If this option is not given,
1442 C<stty> is called with C<raw -crtscts -onlcr 115200> settings.
1443
1444 =item --remote-cmd=I<cmd>
1445
1446 Command that mediates connection to the target's serial line. For
1447 example C<ssh server 'cu -l /dev/ttyS0'>.
1448
1449 =item --remote-expect=I<string>
1450
1451 Wait for reception of I<string> after establishing the the remote
1452 connection before continuing.
1453
1454
1455 =back
1456
1457 =head2 File deployment phase
1458
1459 In some setups, it is necessary to copy the files needed for booting
1460 to a particular location, e.g. to a TFTP boot server or to the
1461 F</boot> partition.
1462
1463 =over 8
1464
1465 =item -d, --dhcp-tftp
1466
1467 Turns your workstation into a DHCP and TFTP server so that the OS can
1468 be booted via PXE BIOS (or similar mechanism) on the test machine
1469 directly connected by a plain Ethernet cable to your workstation.
1470
1471 The DHCP and TFTP servers requires root privileges and C<novaboot>
1472 uses C<sudo> command to obtain those. You can put the following to
1473 I</etc/sudoers> to allow running the necessary commands without asking
1474 for password.
1475
1476  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 --listen --secure -v -v -v --pidfile tftpd.pid *, /usr/bin/touch dhcpd.leases, /usr/bin/pkill --pidfile=dhcpd.pid, /usr/bin/pkill --pidfile=tftpd.pid
1477  your_login ALL=NOPASSWD: NOVABOOT
1478
1479 =item --tftp
1480
1481 Starts a TFTP server on your workstation. This is similar to
1482 B<--dhcp-tftp> except that DHCP server is not started.
1483
1484 The TFTP server require root privileges and C<novaboot> uses C<sudo>
1485 command to obtain those. You can put the following to I</etc/sudoers>
1486 to allow running the necessary commands without asking for password.
1487
1488  Cmnd_Alias NOVABOOT =  /usr/sbin/in.tftpd --listen --secure -v -v -v --pidfile tftpd.pid *, /usr/bin/pkill --pidfile=tftpd.pid
1489  your_login ALL=NOPASSWD: NOVABOOT
1490
1491 =item --tftp-port=I<port>
1492
1493 Port to run the TFTP server on. Implies B<--tftp>.
1494
1495 =item --iso[=filename]
1496
1497 Generates the ISO image that boots NOVA system via GRUB. If no filename
1498 is given, the image is stored under I<NAME>.iso, where I<NAME> is the name
1499 of the novaboot script (see also B<--name>).
1500
1501 =item --server[=[[user@]server:]path]
1502
1503 Copy all files needed for booting to another location. The files will
1504 be copied (by B<rsync> tool) to the directory I<path>. If the I<path>
1505 contains string $NAME, it will be replaced with the name of the
1506 novaboot script (see also B<--name>).
1507
1508 =item --rsync-flags=I<flags>
1509
1510 Specifies which I<flags> are appended to F<rsync> command line when
1511 copying files as a result of I<--server> option.
1512
1513 =item --concat
1514
1515 If B<--server> is used and its value ends with $NAME, then after
1516 copying the files, a new bootloader configuration file (e.g. menu.lst)
1517 is created at I<path-wo-name>, i.e. the path specified by B<--server>
1518 with $NAME part removed. The content of the file is created by
1519 concatenating all files of the same name from all subdirectories of
1520 I<path-wo-name> found on the "server".
1521
1522 =item --ider
1523
1524 Use Intel AMT technology for IDE redirection. This allows the target
1525 machine to boot from novaboot created ISO image. Implies B<--iso>.
1526
1527 The experimental C<amtider> utility needed by this option can be
1528 obtained from https://github.com/wentasah/amtterm.
1529
1530 =back
1531
1532 =head2 Target power-on and reset phase
1533
1534 At this point, the target is reset (or switched on/off). There is
1535 several ways how this can be accomplished. Resetting a physical target
1536 can currently be accomplished by the following options: B<--amt>,
1537 B<--iprelay>, B<--reset-cmd>.
1538
1539 =over 8
1540
1541 =item --on, --off
1542
1543 Switch on/off the target machine and exit. The script (if any) is
1544 completely ignored. Currently it works only with B<--iprelay> or
1545 B<--amt>.
1546
1547 =item -Q, --qemu[=I<qemu-binary>]
1548
1549 Boot the configuration in qemu. Optionally, the name of qemu binary
1550 can be specified as a parameter.
1551
1552 =item --qemu-append=I<flags>
1553
1554 Append I<flags> to the default qemu flags (QEMU_FLAGS variable or
1555 C<-cpu coreduo -smp 2>).
1556
1557 =item -q, --qemu-flags=I<flags>
1558
1559 Replace the default qemu flags (QEMU_FLAGS variable or C<-cpu coreduo
1560 -smp 2>) with I<flags> specified here.
1561
1562 =item --reset-cmd=I<cmd>
1563
1564 Command that resets the target.
1565
1566 =item --no-reset, --reset
1567
1568 Disable/enable reseting of the target.
1569
1570 =back
1571
1572 =head2 Interaction with the bootloader on the target
1573
1574 =over 8
1575
1576 =item --uboot[=I<prompt>]
1577
1578 Interact with U-Boot bootloader to boot the thing described in the
1579 novaboot script. I<prompt> specifies the U-Boot's prompt (default is
1580 "=> ", other common prompts are "U-Boot> " or "U-Boot# ").
1581 Implementation of this option is currently tied to a particular board
1582 that we use. It may be subject to changes in the future!
1583
1584 =item --uboot-init
1585
1586 Command(s) to send the U-Boot bootloader before loading the images and
1587 booting them. This option can be given multiple times. After sending
1588 commands from each option novaboot waits for U-Boot I<prompt>.
1589
1590 If the command contains string I<$NB_MYIP> then this string is
1591 replaced by IPv4 address of eth0 interface.
1592
1593 See also C<uboot> keyword in L</"NOVABOOT SCRIPT SYNTAX">).
1594
1595 =item --uboot-addr I<name>=I<address>
1596
1597 Load address of U-Boot's C<tftpboot> command for loading I<name>,
1598 where name is one of I<kernel>, I<ramdisk> or I<fdt> (flattened device
1599 tree).
1600
1601 =back
1602
1603 =head2 Target interaction phase
1604
1605 In this phase, target's serial output is redirected to stdout and if
1606 stdin is a TTY, it is redirected to the target's serial input allowing
1607 interactive work with the target.
1608
1609 =over 8
1610
1611 =item --exiton=I<string>
1612
1613 When I<string> is sent by the target, novaboot exits. This option can
1614 be specified multiple times.
1615
1616 If I<string> is C<-re>, then the next B<--exiton>'s I<string> is
1617 treated as regular expression. For example:
1618
1619     --exiton -re --exiton 'error:.*failed'
1620
1621 =item -i, --interactive
1622
1623 Setup things for interactive use of target. Your terminal will be
1624 switched to raw mode. In raw mode, your system does not process input
1625 in any way (no echoing of entered characters, no interpretation
1626 special characters). This, among others, means that Ctrl-C is passed
1627 to the target and does no longer interrupt novaboot. Use "~~."
1628 sequence to exit novaboot.
1629
1630 =item --expect=I<string>
1631
1632 When I<string> is received from the target, send the string specified
1633 with the subsequent B<--send*> option to the target.
1634
1635 =item --expect-re=I<regex>
1636
1637 When target's output matches regular expression I<regex>, send the
1638 string specified with the subsequent B<--send*> option to the target.
1639
1640 =item --expect-raw=I<perl-code>
1641
1642 Provides direct control over Perl's Expect module.
1643
1644 =item --send=I<string>
1645
1646 Send I<string> to the target after the previously specified
1647 B<--expect*> was matched in the target's output. The I<string> may
1648 contain escape sequences such as "\n".
1649
1650 Note that I<string> is actually interpreted by Perl, so it can contain
1651 much more that escape sequences. This behavior may change in the
1652 future.
1653
1654 Example: C<--expect='login: ' --send='root\n'>
1655
1656 =item --sendcont=I<string>
1657
1658 Similar to B<--send> but continue expecting more input.
1659
1660 Example: C<--expect='Continue?' --sendcont='yes\n'>
1661
1662 =back
1663
1664 =head1 NOVABOOT SCRIPT SYNTAX
1665
1666 The syntax tries to mimic POSIX shell syntax. The syntax is defined
1667 with the following rules.
1668
1669 Lines starting with "#" and empty lines are ignored.
1670
1671 Lines that end with "\" are concatenated with the following line after
1672 removal of the final "\" and leading whitespace of the following line.
1673
1674 Lines of the form I<VARIABLE=...> (i.e. matching '^[A-Z_]+=' regular
1675 expression) assign values to internal variables. See L</VARIABLES>
1676 section.
1677
1678 Lines starting with C<load> keyword represent modules to boot. The
1679 word after C<load> is a file name (relative to the build directory
1680 (see B<--build-dir>) of the module to load and the remaining words are
1681 passed to it as the command line parameters.
1682
1683 When the C<load> line ends with "<<WORD" then the subsequent lines
1684 until the line containing solely WORD are copied literally to the file
1685 named on that line. This is similar to shell's heredoc feature.
1686
1687 When the C<load> line ends with "< CMD" then command CMD is executed
1688 with F</bin/sh> and its standard output is stored in the file named on
1689 that line. The SRCDIR variable in CMD's environment is set to the
1690 absolute path of the directory containing the interpreted novaboot
1691 script.
1692
1693 Lines starting with C<run> keyword contain shell commands that are run
1694 during file generation phase. This is the same as the "< CMD" syntax
1695 for C<load> keyboard except that the command's output is not
1696 redirected to a file. The ordering of commands is the same as they
1697 appear in the novaboot script.
1698
1699 Lines starting with C<uboot> represent U-Boot commands that are sent
1700 to the target if B<--uboot> option is given. Having a U-Boot line in
1701 the novaboot script is the same as passing an equivalent
1702 B<--uboot-init> option to novaboot.
1703
1704 Example (Linux):
1705
1706   #!/usr/bin/env novaboot
1707   load bzImage console=ttyS0,115200
1708   run  make -C buildroot
1709   load rootfs.cpio < gen_cpio buildroot/images/rootfs.cpio "myapp->/etc/init.d/S99myapp"
1710
1711 Example (NOVA User Land - NUL):
1712
1713   #!/usr/bin/env novaboot
1714   WVDESC=Example program
1715   load bin/apps/sigma0.nul S0_DEFAULT script_start:1,1 \
1716                            verbose hostkeyb:0,0x60,1,12,2
1717   load bin/apps/hello.nul
1718   load hello.nulconfig <<EOF
1719   sigma0::mem:16 name::/s0/log name::/s0/timer name::/s0/fs/rom ||
1720   rom://bin/apps/hello.nul
1721   EOF
1722
1723 This example will load three modules: F<sigma0.nul>, F<hello.nul> and
1724 F<hello.nulconfig>. sigma0 receives some command line parameters and
1725 F<hello.nulconfig> file is generated on the fly from the lines between
1726 C<<<EOF> and C<EOF>.
1727
1728 =head2 VARIABLES
1729
1730 The following variables are interpreted in the novaboot script:
1731
1732 =over 8
1733
1734 =item BUILDDIR
1735
1736 Novaboot chdir()s to this directory before file generation phase. The
1737 directory name specified here is relative to the build directory
1738 specified by other means (see L</--build-dir>).
1739
1740 =item EXITON
1741
1742 Assigning this variable has the same effect as specifying L</--exiton>
1743 option.
1744
1745 =item HYPERVISOR_PARAMS
1746
1747 Parameters passed to hypervisor. The default value is "serial", unless
1748 overridden in configuration file.
1749
1750 =item KERNEL
1751
1752 The kernel to use instead of the hypervisor specified in the
1753 configuration file with the C<$hypervisor> variable. The value should
1754 contain the name of the kernel image as well as its command line
1755 parameters. If this variable is defined and non-empty, the variable
1756 HYPERVISOR_PARAMS is not used.
1757
1758 =item QEMU
1759
1760 Use a specific qemu binary (can be overridden with B<-Q>) and flags
1761 when booting this script under qemu. If QEMU_FLAGS variable is also
1762 specified flags specified in QEMU variable are replaced by those in
1763 QEMU_FLAGS.
1764
1765 =item QEMU_FLAGS
1766
1767 Use specific qemu flags (can be overridden with B<-q>).
1768
1769 =item WVDESC
1770
1771 Description of the WvTest-compliant program.
1772
1773 =item WVTEST_TIMEOUT
1774
1775 The timeout in seconds for WvTest harness. If no complete line appears
1776 in the test output within the time specified here, the test fails. It
1777 is necessary to specify this for long running tests that produce no
1778 intermediate output.
1779
1780 =back
1781
1782 =head1 CONFIGURATION FILE
1783
1784 Novaboot can read its configuration from one or more files. By
1785 default, novaboot looks for files named F<.novaboot> as described in
1786 L</Configuration reading phase>. Alternatively, configuration file
1787 location can be specified with the B<-c> switch or with the
1788 NOVABOOT_CONFIG environment variable. The configuration file has Perl
1789 syntax and should set values of certain Perl variables. The current
1790 configuration can be dumped with the B<--dump-config> switch. Some
1791 configuration variables can be overridden by environment variables
1792 (see below) or by command line switches.
1793
1794 Supported configuration variables include:
1795
1796 =over 8
1797
1798 =item $builddir
1799
1800 Build directory location relative to the location of the configuration
1801 file.
1802
1803 =item $default_target
1804
1805 Default target (see below) to use when no target is explicitly
1806 specified on command line with the B<--target> option.
1807
1808 =item %targets
1809
1810 Hash of target definitions to be used with the B<--target> option. The
1811 key is the identifier of the target, the value is the string with
1812 command line options. For instance, if the configuration file contains:
1813
1814  $targets{'mybox'} = '--server=boot:/tftproot --serial=/dev/ttyUSB0 --grub',
1815
1816 then the following two commands are equivalent:
1817
1818  ./script --server=boot:/tftproot --serial=/dev/ttyUSB0 --grub
1819  ./script -t mybox
1820
1821 =back
1822
1823 =head1 ENVIRONMENT VARIABLES
1824
1825 Some options can be specified not only via config file or command line
1826 but also through environment variables. Environment variables override
1827 the values from configuration file and command line parameters
1828 override the environment variables.
1829
1830 =over 8
1831
1832 =item NOVABOOT_CONFIG
1833
1834 Name of the novaboot configuration file to use instead of the default
1835 one(s).
1836
1837 =item NOVABOOT_CONFIG_DIR
1838
1839 Name of the novaboot configuration directory. When not specified
1840 F</etc/novaboot.d> is used.
1841
1842 =item NOVABOOT_BENDER
1843
1844 Defining this variable has the same meaning as B<--bender> option.
1845
1846 =back
1847
1848 =head1 AUTHORS
1849
1850 Michal Sojka <sojka@os.inf.tu-dresden.de>
1851
1852 =cut
1853
1854 # LocalWords:  novaboot Novaboot NOVABOOT TFTP PXE DHCP filename stty
1855 # LocalWords:  chainloader stdout Qemu qemu preprocessing ISOLINUX bootable
1856 # LocalWords:  config subprocesses sudo sudoers tftp dhcp IDE stdin
1857 # LocalWords:  subdirectories TTY whitespace heredoc POSIX WvTest