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