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