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