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