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