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