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