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