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