]> rtime.felk.cvut.cz Git - novaboot.git/blob - novaboot
Whitespace and indentation fixes
[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     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, "reset");
634         if ($result != 0) {
635             print STDERR "Warning: Cannot reset $host, trying power on. ";
636             $result = powerChange($host,$user,$amt_password, "on");
637         }
638         die "AMT reset failed (code $result)" if $result;
639         sleep(2); # Without pause, connection to AMT machine is initiated before reset and fails after restart
640         $exp = Expect->spawn("amtterm -u $user -p $amt_password $host $port");
641         #expect must be here because AMT doesnt allow to reset/on/off computer when somebody is connected
642     };
643 }
644
645 if ($remote_expect) {
646     $exp->expect(10, $remote_expect) || die "Expect for '$remote_expect' timed out";
647 }
648
649 if (defined $reset_cmd) {
650     $target_reset = sub {
651         system_verbose($reset_cmd);
652     };
653 }
654
655 if (defined $on_opt && defined $target_power_on) {
656     &$target_power_on();
657     exit;
658 }
659 if (defined $off_opt && defined $target_power_off) {
660     print "novaboot: Switching the target off...\n";
661     &$target_power_off();
662     exit;
663 }
664
665 $builddir ||= dirname(File::Spec->rel2abs( ${$scripts[0]}{filename})) if scalar @scripts;
666 if (defined $builddir) {
667     chdir($builddir) or die "Can't change directory to $builddir: $!";
668     print "novaboot: Entering directory `$builddir'\n";
669 }
670
671 ## File generation phase
672 my (%files_iso, $menu_iso, $filename);
673 my $config_name = '';
674
675 foreach my $script (@scripts) {
676     $filename = $$script{filename};
677     $modules = $$script{modules};
678     $generated = $$script{generated};
679     $variables = $$script{variables};
680
681     ($config_name = $filename) =~ s#.*/##;
682     $config_name = $config_name_opt if (defined $config_name_opt);
683
684     if (exists $variables->{BUILDDIR}) {
685         $builddir = File::Spec->rel2abs($variables->{BUILDDIR});
686         chdir($builddir) or die "Can't change directory to $builddir: $!";
687         print "novaboot: Entering directory `$builddir'\n";
688     }
689
690     my $prefix;
691     ($prefix = $grub_prefix) =~ s/\$NAME/$config_name/ if defined $grub_prefix;
692     $prefix ||= $builddir;
693     # TODO: use $grub_prefix as first parameter if some switch is given
694     generate_configs('', $generated, $filename);
695
696 ### Generate bootloader configuration files
697     my @bootloader_configs;
698     push @bootloader_configs, generate_grub_config($grub_config, $config_name, $prefix, $modules, $grub_preamble) if (defined $grub_config);
699     push @bootloader_configs, generate_grub2_config($grub2_config, $config_name, $prefix, $modules, $grub_preamble, $grub2_prolog) if (defined $grub2_config);
700     push @bootloader_configs, generate_pulsar_config('config-'.($pulsar||'novaboot'), $modules) if (defined $pulsar);
701
702 ### Run scons or make
703     {
704         my @files = map({ ($file) = m/([^ ]*)/; $file; } @$modules);
705         # Filter-out generated files
706         my @to_build = grep({ my $file = $_; !scalar(grep($file eq $$_{filename}, @$generated)) } @files);
707
708         system_verbose($scons || $CFG::scons." ".join(" ", @to_build)) if (defined $scons);
709         system_verbose($make  || $CFG::make ." ".join(" ", @to_build)) if (defined $make);
710     }
711
712 ### Copy files (using rsync)
713     if (defined $server && !defined($gen_only)) {
714         (my $real_server = $server) =~ s/\$NAME/$config_name/;
715
716         my ($hostname, $path) = split(":", $real_server, 2);
717         if (! defined $path) {
718             $path = $hostname;
719             $hostname = "";
720         }
721         my $files = join(" ", map({ ($file) = m/([^ ]*)/; $file; } ( @$modules, @bootloader_configs)));
722         map({ my $file = (split)[0]; die "$file: $!" if ! -f $file; } @$modules);
723         my $istty = -t STDOUT && ($ENV{'TERM'} || 'dumb') ne 'dumb';
724         my $progress = $istty ? "--progress" : "";
725         system_verbose("rsync $progress -RLp $rsync_flags $files $real_server");
726         if ($server =~ m|/\$NAME$| && $concat) {
727             my $cmd = join("; ", map { "( cd $path/.. && cat */$_ > $_ )" } @bootloader_configs);
728             system_verbose($hostname ? "ssh $hostname '$cmd'" : $cmd);
729         }
730     }
731
732 ### Prepare ISO image generation
733     if (defined $iso_image) {
734         generate_configs("(cd)", $generated, $filename);
735         my $menu;
736         generate_grub_config(\$menu, $config_name, "(cd)", $modules);
737         $menu_iso .= "$menu\n";
738         map { ($file,undef) = split; $files_iso{$file} = 1; } @$modules;
739     }
740 }
741
742 ## Generate ISO image
743 if (defined $iso_image) {
744     open(my $fh, ">menu-iso.lst");
745     print $fh "timeout 5\n\n$menu_iso";
746     close($fh);
747     my $files = "boot/grub/menu.lst=menu-iso.lst " . join(" ", map("$_=$_", keys(%files_iso)));
748     $iso_image ||= "$config_name.iso";
749     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");
750     print("ISO image created: $builddir/$iso_image\n");
751 }
752
753 exit(0) if defined $gen_only;
754
755 ## Boot the system using various methods and send serial output to stdout
756
757 if (scalar(@scripts) > 1 && ( defined $dhcp_tftp || defined $serial || defined $iprelay)) {
758     die "You cannot do this with multiple scripts simultaneously";
759 }
760
761 if ($variables->{WVTEST_TIMEOUT}) {
762     print "wvtest: timeout ", $variables->{WVTEST_TIMEOUT}, "\n";
763 }
764
765 sub trim($) {
766     my ($str) = @_;
767     $str =~ s/^\s+|\s+$//g;
768     return $str
769 }
770
771 ### Qemu
772
773 if (defined $qemu) {
774     # Qemu
775     $qemu ||= $variables->{QEMU} || $CFG::qemu;
776     my @qemu_flags = split(" ", $qemu);
777     $qemu = shift(@qemu_flags);
778
779     @qemu_flags = split(/ +/, trim($variables->{QEMU_FLAGS})) if exists $variables->{QEMU_FLAGS};
780     @qemu_flags = split(/ +/, trim($qemu_flags_cmd)) if $qemu_flags_cmd;
781     push(@qemu_flags, split(/ +/, trim($qemu_append || '')));
782
783     if (defined $iso_image) {
784         # Boot NOVA with grub (and test the iso image)
785         push(@qemu_flags, ('-cdrom', "$config_name.iso"));
786     } else {
787         # Boot NOVA without GRUB
788
789         # Non-patched qemu doesn't like commas, but NUL can live with pluses instead of commans
790         foreach (@$modules) {s/,/+/g;}
791         generate_configs("", $generated, $filename);
792
793         if (scalar @$modules) {
794             my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);
795             $kcmd = '' if !defined $kcmd;
796             my $dtb;
797             @$modules = map { if (/\.dtb$/) { $dtb=$_; (); } else { $_ } } @$modules;
798             my $initrd = join ",", @$modules;
799
800             push(@qemu_flags, ('-kernel', $kbin, '-append', $kcmd));
801             push(@qemu_flags, ('-initrd', $initrd)) if $initrd;
802             push(@qemu_flags, ('-dtb', $dtb)) if $dtb;
803         }
804     }
805     push(@qemu_flags,  qw(-serial stdio)); # Redirect serial output (for collecting test restuls)
806     unshift(@qemu_flags, ('-name', $config_name));
807     print "novaboot: Running: ".shell_cmd_string($qemu, @qemu_flags)."\n";
808     $exp = Expect->spawn(($qemu, @qemu_flags)) || die("exec() failed: $!");
809 }
810
811 ### Local DHCPD and TFTPD
812
813 my ($dhcpd_pid, $tftpd_pid);
814
815 if (defined $dhcp_tftp)
816 {
817     generate_configs("(nd)", $generated, $filename);
818     system_verbose('mkdir -p tftpboot');
819     generate_grub_config("tftpboot/os-menu.lst", $config_name, "(nd)", \@$modules, "timeout 0");
820     open(my $fh, '>', 'dhcpd.conf');
821     my $mac = `cat /sys/class/net/eth0/address`;
822     chomp $mac;
823     print $fh "subnet 10.23.23.0 netmask 255.255.255.0 {
824                       range 10.23.23.10 10.23.23.100;
825                       filename \"bin/boot/grub/pxegrub.pxe\";
826                       next-server 10.23.23.1;
827 }
828 host server {
829         hardware ethernet $mac;
830         fixed-address 10.23.23.1;
831 }";
832     close($fh);
833     system_verbose("sudo ip a add 10.23.23.1/24 dev eth0;
834             sudo ip l set dev eth0 up;
835             sudo touch dhcpd.leases");
836
837     # We run servers by forking ourselves, because the servers end up
838     # in our process group and get killed by signals sent to the
839     # process group (e.g. Ctrl-C on terminal).
840     $dhcpd_pid = fork();
841     exec_verbose("sudo dhcpd -d -cf dhcpd.conf -lf dhcpd.leases -pf dhcpd.pid") if ($dhcpd_pid == 0);
842     $tftpd_pid = fork();
843     exec_verbose("sudo in.tftpd --foreground --secure -v -v -v --pidfile tftpd.pid $builddir") if ($tftpd_pid == 0);
844
845     # Kill server when we die
846     $SIG{__DIE__} = sub { system_verbose('sudo pkill --pidfile=dhcpd.pid');
847                           system_verbose('sudo pkill --pidfile=tftpd.pid'); };
848 }
849
850 ### Serial line or IP relay
851
852 if (defined $target_reset) {
853     print "novaboot: Reseting the test box... ";
854     &$target_reset();
855     print "done\n";
856 }
857
858 if (defined $uboot) {
859     print "novaboot: Waiting for uBoot prompt...\n";
860     $exp->log_stdout(1);
861     #$exp->exp_internal(1);
862     $exp->expect(20,
863                  [qr/Hit any key to stop autoboot:/, sub { $exp->send("\n"); exp_continue; }],
864                  '=> ') || die "No uBoot prompt deteceted";
865     $exp->send("$uboot_init\n") if $uboot_init;
866     $exp->expect(10, '=> ') || die "uBoot prompt timeout";
867
868     my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);
869     my $dtb;
870     @$modules = map { if (/\.dtb$/) { $dtb=$_; (); } else { $_ } } @$modules;
871     my $initrd = shift @$modules;
872
873     my $kern_addr = '800000';
874     my $initrd_addr = '-';
875     my $dtb_addr = '';
876
877     $exp->send("tftp $kern_addr $kbin\n");
878     $exp->expect(10,
879                  [qr/#/, sub { exp_continue; }],
880                  '=> ') || die "Kernel load failed";
881     if (defined $dtb) {
882         $dtb_addr = '7f0000';
883         $exp->send("tftp $dtb_addr $dtb\n");
884         $exp->expect(10,
885                      [qr/#/, sub { exp_continue; }],
886                      '=> ') || die "Device tree load failed";
887     }
888     if (defined $initrd) {
889         $initrd_addr = 'b00000';
890         $exp->send("tftp $initrd_addr $initrd\n");
891         $exp->expect(10,
892                      [qr/#/, sub { exp_continue; }],
893                      '=> ') || die "Initrd load failed";
894     }
895     $exp->send("set bootargs '$kcmd'\n");
896     $exp->expect(5, '=> ')  || die "uBoot prompt timeout";
897     $exp->send("bootm $kern_addr $initrd_addr $dtb_addr\n");
898     $exp->expect(5, "\n")  || die "uBoot command timeout";
899 }
900
901 if (defined $exp) {
902     # Serial line of the target is available
903     my $interrupt = 'Ctrl-C';
904     if ($interactive && !@exiton) {
905         $interrupt = '"~~."';
906     }
907     print "novaboot: Serial line interaction (press $interrupt to interrupt)...\n";
908     $exp->log_stdout(1);
909     if (@exiton) {
910         $exp->expect(undef, @expect_raw, @exiton);
911     } else {
912         my @inputs = ($exp);
913         if (-t STDIN) { # Set up bi-directional communication if we run on terminal
914             my $infile = new IO::File;
915             $infile->IO::File::fdopen(*STDIN,'r');
916             my $in_object = Expect->exp_init($infile);
917             $in_object->set_group($exp);
918
919             if ($interactive) {
920                 $in_object->set_seq('~~\.', sub { print "novaboot: Escape sequence detected\r\n"; undef; });
921                 $in_object->manual_stty(0);       # Use raw terminal mode
922             } else {
923                 $in_object->manual_stty(1);       # Do not modify terminal settings
924             }
925             push(@inputs, $in_object);
926         }
927         #use Data::Dumper;
928         #print Dumper(\@expect_raw);
929         $exp->expect(undef, @expect_raw) if @expect_raw;
930         Expect::interconnect(@inputs) unless defined($exp->exitstatus);
931     }
932 }
933
934 ## Kill dhcpc or tftpd
935 if (defined $dhcp_tftp) {
936     die("novaboot: This should kill servers on background\n");
937 }
938
939 ## Documentation
940
941 =head1 NAME
942
943 novaboot - A tool for booting various operating systems on various hardware or in qemu
944
945 =head1 SYNOPSIS
946
947 B<novaboot> --help
948
949 B<novaboot> [option]... [--] script...
950
951 B<./script> [option]...
952
953 =head1 DESCRIPTION
954
955 This program makes booting of an operating system (e.g. NOVA or Linux)
956 as simple as running a local program. It facilitates booting on local
957 or remote hosts or in emulators such as qemu. Novaboot operation is
958 controlled by command line options and by a so called novaboot script,
959 which can be thought as a generalization of bootloader configuration
960 files (see L</"NOVABOOT SCRIPT SYNTAX">). Based on this input,
961 novaboot setups everything for the target host to boot the desired
962 configuration, i.e. it generates the bootloader configuration file in
963 the proper format, deploy the binaries and other needed files to
964 required locations, perhaps on a remote boot server and reset the
965 target host. Finally, target host's serial output is redirected to
966 standard output if that is possible.
967
968 Typical way of using novaboot is to make the novaboot script
969 executable and set its first line to I<#!/usr/bin/env novaboot>. Then,
970 booting a particular OS configuration becomes the same as executing a
971 local program - the novaboot script.
972
973 For example, with C<novaboot> you can:
974
975 =over 3
976
977 =item 1.
978
979 Run an OS in Qemu. This is the default action when no other action is
980 specified by command line switches. Thus running C<novaboot ./script>
981 (or C<./script> as described above) will run Qemu and make it boot the
982 configuration specified in the F<script>.
983
984 =item 2.
985
986 Create a bootloader configuration file (currently supported
987 bootloaders are GRUB, GRUB2, Pulsar and U-Boot) and copy it with all
988 other files needed for booting to a remote boot server.
989
990  ./script --server=192.168.1.1:/tftp --iprelay=192.168.1.2
991
992 This command copies files to the TFTP server and uses
993 TCP/IP-controlled relay to reset the target host and receive its
994 serial output.
995
996 =item 3.
997
998 Run DHCP and TFTP server on developer's machine to PXE-boot the target
999 host from it. E.g.
1000
1001  ./script --dhcp-tftp
1002
1003 When a PXE-bootable machine is connected via Ethernet to developer's
1004 machine, it will boot the configuration described in I<script>.
1005
1006 =item 4.
1007
1008 Create bootable ISO images. E.g.
1009
1010  novaboot --iso -- script1 script2
1011
1012 The created ISO image will have GRUB bootloader installed on it and
1013 the boot menu will allow selecting between I<script1> and I<script2>
1014 configurations.
1015
1016 =back
1017
1018 Note that the options needed for a specific target can be stored in a
1019 L</"CONFIGURATION FILE">. Then it is sufficient to use only the B<-t>
1020 option to specify the name of the target.
1021
1022 =head1 PHASES AND OPTIONS
1023
1024 Novaboot performs its work in several phases. Each phase can be
1025 influenced by several options, certain phases can be skipped. The list
1026 of phases (in the execution order) and the corresponding options
1027 follow.
1028
1029 =head2 Configuration reading phase
1030
1031 After starting, novaboot reads configuration files. By default, it
1032 searches for files named F<.novaboot> starting from the directory of
1033 the novaboot script (or working directory, see bellow) and continuing
1034 upwards up to the root directory. The configuration files are read in
1035 order from the root directory downwards with latter files overriding
1036 settings from the former ones.
1037
1038 In certain cases, the location of the novaboot script cannot be
1039 determined in this early phase. This happens either when the script is
1040 read from the standard input or when novaboot is invoked explicitly
1041 and options precede the script name, as in the example L</"4."> above.
1042 In this case the current working directory is used as a starting point
1043 for configuration file search.
1044
1045 =over 8
1046
1047 =item -c, --config=I<filename>
1048
1049 Use the specified configuration file instead of the default one(s).
1050
1051 =back
1052
1053 =head2 Command line processing phase
1054
1055 =over 8
1056
1057 =item --dump-config
1058
1059 Dump the current configuration to stdout end exits. Useful as an
1060 initial template for a configuration file.
1061
1062 =item -h, --help
1063
1064 Print short (B<-h>) or long (B<--help>) help.
1065
1066 =item -t, --target=I<target>
1067
1068 This option serves as a user configurable shortcut for other novaboot
1069 options. The effect of this option is the same as the options stored
1070 in the C<%targets> configuration variable under key I<target>. See
1071 also L</"CONFIGURATION FILE">.
1072
1073 =back
1074
1075 =head2 Script preprocessing phase
1076
1077 This phases allows to modify the parsed novaboot script before it is
1078 used in the later phases.
1079
1080 =over 8
1081
1082 =item -a, --append=I<parameters>
1083
1084 Append a string to the first C<load> line in the novaboot script. This
1085 can be used to append parameters to the kernel's or root task's
1086 command line. Can appear multiple times.
1087
1088 =item -b, --bender
1089
1090 Use F<bender> chainloader. Bender scans the PCI bus for PCI serial
1091 ports and stores the information about them in the BIOS data area for
1092 use by the kernel.
1093
1094 =item --chainloader=I<chainloader>
1095
1096 Chainloader that is loaded before the kernel and other files specified
1097 in the novaboot script. E.g. 'bin/boot/bender promisc'.
1098
1099 =item --dump
1100
1101 Print the modules to boot and their parameters after this phase
1102 finishes. Then exit. This is useful for seeing the effect of other
1103 options in this section.
1104
1105 =item -k, --kernel=F<file>
1106
1107 Replace the first word on the first C<load> line in the novaboot
1108 script with F<file>.
1109
1110 =item --scriptmod=I<perl expression>
1111
1112 When novaboot script is read, I<perl expression> is executed for every
1113 line (in $_ variable). For example, C<novaboot
1114 --scriptmod=s/sigma0/omega6/g> replaces every occurrence of I<sigma0>
1115 in the script with I<omega6>.
1116
1117 When this option is present, it overrides I<$script_modifier> variable
1118 from the configuration file, which has the same effect. If this option
1119 is given multiple times all expressions are evaluated in the command
1120 line order.
1121
1122 =back
1123
1124 =head2 File generation phase
1125
1126 In this phase, files needed for booting are generated in a so called
1127 I<build directory> (see L</--build-dir>). In most cases configuration
1128 for a bootloader is generated automatically by novaboot. It is also
1129 possible to generate other files using I<heredoc> or I<"<"> syntax in
1130 novaboot scripts. Finally, binaries can be generated in this phases by
1131 running C<scons> or C<make>.
1132
1133 =over 8
1134
1135 =item --build-dir=I<directory>
1136
1137 Overrides the default build directory location.
1138
1139 The default build directory location is determined as follows: If the
1140 configuration file defines the C<$builddir> variable, its value is
1141 used. Otherwise, it is the directory that contains the first processed
1142 novaboot script.
1143
1144 See also L</BUILDDIR> variable.
1145
1146 =item -g, --grub[=I<filename>]
1147
1148 Generates grub bootloader menu file. If the I<filename> is not
1149 specified, F<menu.lst> is used. The I<filename> is relative to the
1150 build directory (see B<--build-dir>).
1151
1152 =item --grub-preamble=I<prefix>
1153
1154 Specifies the I<preable> that is at the beginning of the generated
1155 GRUB or GRUB2 config files. This is useful for specifying GRUB's
1156 timeout.
1157
1158 =item --grub-prefix=I<prefix>
1159
1160 Specifies I<prefix> that is put in front of every file name in GRUB's
1161 F<menu.lst>. The default value is the absolute path to the build directory.
1162
1163 If the I<prefix> contains string $NAME, it will be replaced with the
1164 name of the novaboot script (see also B<--name>).
1165
1166 =item --grub2[=I<filename>]
1167
1168 Generate GRUB2 menuentry in I<filename>. If I<filename> is not
1169 specified F<grub.cfg> is used. The content of the menuentry can be
1170 customized with B<--grub-preable>, B<--grub2-prolog> or
1171 B<--grub_prefix> options.
1172
1173 In order to use the the generated menuentry on your development
1174 machine that uses GRUB2, append the following snippet to
1175 F</etc/grub.d/40_custom> file and regenerate your grub configuration,
1176 i.e. run update-grub on Debian/Ubuntu.
1177
1178   if [ -f /path/to/nul/build/grub.cfg ]; then
1179     source /path/to/nul/build/grub.cfg
1180   fi
1181
1182 =item --grub2-prolog=I<prolog>
1183
1184 Specifies text I<preable> that is put at the beginning of the entry
1185 GRUB2 entry.
1186
1187 =item -m, --make[=make command]
1188
1189 Runs C<make> to build files that are not generated by novaboot itself.
1190
1191 =item --name=I<string>
1192
1193 Use the name I<string> instead of the name of the novaboot script.
1194 This name is used for things like a title of grub menu or for the
1195 server directory where the boot files are copied to.
1196
1197 =item --no-file-gen
1198
1199 Do not generate files on the fly (i.e. "<" syntax) except for the
1200 files generated via "<<WORD" syntax.
1201
1202 =item -p, --pulsar[=mac]
1203
1204 Generates pulsar bootloader configuration file named F<config-I<mac>>
1205 The I<mac> string is typically a MAC address and defaults to
1206 I<novaboot>.
1207
1208 =item --scons[=scons command]
1209
1210 Runs C<scons> to build files that are not generated by novaboot
1211 itself.
1212
1213 =item --strip-rom
1214
1215 Strip I<rom://> prefix from command lines and generated config files.
1216 The I<rom://> prefix is used by NUL. For NRE, it has to be stripped.
1217
1218 =item --gen-only
1219
1220 Exit novaboot after file generation phase.
1221
1222 =back
1223
1224 =head2 Target connection check
1225
1226 If supported by the target, the connection to it is made and it is
1227 checked whether the target is not occupied by another novaboot
1228 user/instance.
1229
1230 =over 8
1231
1232 =item --amt=I<"[user[:password]@]host[:port]>
1233
1234 Use Intel AMT to connect to target. Using SOL redirection and WS management
1235 to powercycle it. The IP address or FQDN of the PC is given by I<host>
1236 parameter. If I<password> is not specified, environment variable
1237 AMT_PASSWORD is used. I<port> is defining port number for SOL,
1238 if not specified, default is 16992. Default I<user> is admin.
1239
1240 =item --iprelay=I<addr[:port]>
1241
1242 Use TCP/IP relay and serial port to access the target's serial port
1243 and powercycle it. The IP address of the relay is given by I<addr>
1244 parameter. If I<port> is not specified, it default to 23.
1245
1246 Note: This option is supposed to work with HWG-ER02a IP relays.
1247
1248 =item -s, --serial[=device]
1249
1250 Target's serial line is connected to host's serial line (device). The
1251 default value for device is F</dev/ttyUSB0>.
1252
1253 The value of this option is exported in NB_NOVABOOT environment
1254 variable to all subprocesses run by C<novaboot>.
1255
1256 =item --stty=I<settings>
1257
1258 Specifies settings passed to C<stty> invoked on the serial line
1259 specified with B<--serial> option. If this option is not given,
1260 C<stty> is called with C<raw -crtscts -onlcr 115200> settings.
1261
1262 =item --remote-cmd=I<cmd>
1263
1264 Command that mediates connection to the target's serial line. For
1265 example C<ssh server 'cu -l /dev/ttyS0'>.
1266
1267 =item --remote-expect=I<string>
1268
1269 Wait for reception of I<string> after establishing the the remote
1270 connection before continuing.
1271
1272
1273 =back
1274
1275 =head2 File deployment phase
1276
1277 In some setups, it is necessary to copy the files needed for booting
1278 to a particular location, e.g. to a TFTP boot server or to the
1279 F</boot> partition.
1280
1281 =over 8
1282
1283 =item -d, --dhcp-tftp
1284
1285 Turns your workstation into a DHCP and TFTP server so that the OS can
1286 be booted via PXE BIOS (or similar mechanism) on the test machine
1287 directly connected by a plain Ethernet cable to your workstation.
1288
1289 The DHCP and TFTP servers require root privileges and C<novaboot>
1290 uses C<sudo> command to obtain those. You can put the following to
1291 I</etc/sudoers> to allow running the necessary commands without
1292 asking for password.
1293
1294  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
1295  your_login ALL=NOPASSWD: NOVABOOT
1296
1297 =item --iso[=filename]
1298
1299 Generates the ISO image that boots NOVA system via GRUB. If no filename
1300 is given, the image is stored under I<NAME>.iso, where I<NAME> is the name
1301 of the novaboot script (see also B<--name>).
1302
1303 =item --server[=[[user@]server:]path]
1304
1305 Copy all files needed for booting to another location (implies B<-g>
1306 unless B<--grub2> is given). The files will be copied (by B<rsync>
1307 tool) to the directory I<path>. If the I<path> contains string $NAME,
1308 it will be replaced with the name of the novaboot script (see also
1309 B<--name>).
1310
1311 =item --concat
1312
1313 If B<--server> is used and its value ends with $NAME, then after
1314 copying the files, a new bootloader configuration file (e.g. menu.lst)
1315 is created at I<path-wo-name>, i.e. the path specified by B<--server>
1316 with $NAME part removed. The content of the file is created by
1317 concatenating all files of the same name from all subdirectories of
1318 I<path-wo-name> found on the "server".
1319
1320 =item --rsync-flags=I<flags>
1321
1322 Specifies which I<flags> are appended to F<rsync> command line when
1323 copying files as a result of I<--server> option.
1324
1325 =back
1326
1327 =head2 Target power-on and reset phase
1328
1329 =over 8
1330
1331 =item --on, --off
1332
1333 Switch on/off the target machine. Currently works only with
1334 B<--iprelay>.
1335
1336 =item -Q, --qemu[=I<qemu-binary>]
1337
1338 Boot the configuration in qemu. Optionally, the name of qemu binary
1339 can be specified as a parameter.
1340
1341 =item --qemu-append=I<flags>
1342
1343 Append I<flags> to the default qemu flags (QEMU_FLAGS variable or
1344 C<-cpu coreduo -smp 2>).
1345
1346 =item -q, --qemu-flags=I<flags>
1347
1348 Replace the default qemu flags (QEMU_FLAGS variable or C<-cpu coreduo
1349 -smp 2>) with I<flags> specified here.
1350
1351 =item --reset-cmd=I<cmd>
1352
1353 Command that resets the target.
1354
1355 =back
1356
1357 =head2 Interaction with the bootloader on the target
1358
1359 =over 8
1360
1361 =item --uboot
1362
1363 Interact with uBoot bootloader to boot the thing described in the
1364 novaboot script. Implementation of this option is currently tied to a
1365 particular board that we use. It may be subject to changes in the
1366 future!
1367
1368 =item --uboot-init
1369
1370 Command(s) to send the U-Boot bootloader before loading the images and
1371 booting them.
1372
1373 =back
1374
1375 =head2 Target interaction phase
1376
1377 In this phase, target's serial output is redirected to stdout and if
1378 stdin is a TTY, it is redirected to the target's serial input allowing
1379 interactive work with the target.
1380
1381 =over 8
1382
1383 =item --exiton=I<string>
1384
1385 When I<string> is sent by the target, novaboot exits. This option can
1386 be specified multiple times.
1387
1388 If I<string> is C<-re>, then the next B<--exiton>'s I<string> is
1389 treated as regular expression. For example:
1390
1391     --exiton -re --exiton 'error:.*failed'
1392
1393 =item -i, --interactive
1394
1395 Setup things for interactive use of target. Your terminal will be
1396 switched to raw mode. In raw mode, your system does not process input
1397 in any way (no echoing of entered characters, no interpretation
1398 special characters). This, among others, means that Ctrl-C is passed
1399 to the target and does no longer interrupt novaboot. Use "~~."
1400 sequence to exit novaboot.
1401
1402 =item --expect=I<string>
1403
1404 When I<string> is received from the target, send the string specified
1405 with the subsequent B<--send*> option to the target.
1406
1407 =item --expect-re=I<regex>
1408
1409 When target's output matches regular expression I<regex>, send the
1410 string specified with the subsequent B<--send*> option to the target.
1411
1412 =item --expect-raw=I<perl-code>
1413
1414 Provides direct control over Perl's Expect module.
1415
1416 =item --send=I<string>
1417
1418 Send I<string> to the target after the previously specified
1419 B<--expect*> was matched in the target's output. The I<string> may
1420 contain escape sequences such as "\n".
1421
1422 Note that I<string> is actually interpreted by Perl, so it can contain
1423 much more that escape sequences. This behavior may change in the
1424 future.
1425
1426 Example: C<--expect='login: ' --send='root\n'>
1427
1428 =item --sendcont=I<string>
1429
1430 Similar to B<--send> but continue expecting more input.
1431
1432 Example: C<--expect='Continue?' --sendcont='yes\n'>
1433
1434 =back
1435
1436 =head1 NOVABOOT SCRIPT SYNTAX
1437
1438 The syntax tries to mimic POSIX shell syntax. The syntax is defined
1439 with the following rules.
1440
1441 Lines starting with "#" and empty lines are ignored.
1442
1443 Lines that end with "\" are concatenated with the following line after
1444 removal of the final "\" and leading whitespace of the following line.
1445
1446 Lines of the form I<VARIABLE=...> (i.e. matching '^[A-Z_]+=' regular
1447 expression) assign values to internal variables. See L</VARIABLES>
1448 section.
1449
1450 Lines starting with C<load> keyword represent modules to boot. The
1451 word after C<load> is a file name (relative to the build directory
1452 (see B<--build-dir>) of the module to load and the remaining words are
1453 passed to it as the command line parameters.
1454
1455 When the C<load> line ends with "<<WORD" then the subsequent lines
1456 until the line containing solely WORD are copied literally to the file
1457 named on that line. This is similar to shell's heredoc feature.
1458
1459 When the C<load> line ends with "< CMD" then command CMD is executed
1460 with F</bin/sh> and its standard output is stored in the file named on
1461 that line. The SRCDIR variable in CMD's environment is set to the
1462 absolute path of the directory containing the interpreted novaboot
1463 script.
1464
1465 Example (Linux):
1466
1467   #!/usr/bin/env novaboot
1468   load bzImage console=ttyS0,115200
1469   load rootfs.cpio < gen_cpio buildroot/images/rootfs.cpio "myapp->/etc/init.d/S99myapp"
1470
1471 Example (NOVA User Land - NUL):
1472
1473   #!/usr/bin/env novaboot
1474   WVDESC=Example program
1475   load bin/apps/sigma0.nul S0_DEFAULT script_start:1,1 \
1476                            verbose hostkeyb:0,0x60,1,12,2
1477   load bin/apps/hello.nul
1478   load hello.nulconfig <<EOF
1479   sigma0::mem:16 name::/s0/log name::/s0/timer name::/s0/fs/rom ||
1480   rom://bin/apps/hello.nul
1481   EOF
1482
1483 This example will load three modules: F<sigma0.nul>, F<hello.nul> and
1484 F<hello.nulconfig>. sigma0 receives some command line parameters and
1485 F<hello.nulconfig> file is generated on the fly from the lines between
1486 C<<<EOF> and C<EOF>.
1487
1488 =head2 VARIABLES
1489
1490 The following variables are interpreted in the novaboot script:
1491
1492 =over 8
1493
1494 =item BUILDDIR
1495
1496 Novaboot chdir()s to this directory before file generation phase. The
1497 directory name specified here is relative to the build directory
1498 specified by other means (see L</--build-dir>).
1499
1500 =item EXITON
1501
1502 Assigning this variable has the same effect as specifying L</--exiton>
1503 option.
1504
1505 =item HYPERVISOR_PARAMS
1506
1507 Parameters passed to hypervisor. The default value is "serial", unless
1508 overridden in configuration file.
1509
1510 =item KERNEL
1511
1512 The kernel to use instead of the hypervisor specified in the
1513 configuration file with the C<$hypervisor> variable. The value should
1514 contain the name of the kernel image as well as its command line
1515 parameters. If this variable is defined and non-empty, the variable
1516 HYPERVISOR_PARAMS is not used.
1517
1518 =item QEMU
1519
1520 Use a specific qemu binary (can be overridden with B<-Q>) and flags
1521 when booting this script under qemu. If QEMU_FLAGS variable is also
1522 specified flags specified in QEMU variable are replaced by those in
1523 QEMU_FLAGS.
1524
1525 =item QEMU_FLAGS
1526
1527 Use specific qemu flags (can be overridden with B<-q>).
1528
1529 =item WVDESC
1530
1531 Description of the wvtest-compliant program.
1532
1533 =item WVTEST_TIMEOUT
1534
1535 The timeout in seconds for WvTest harness. If no complete line appears
1536 in the test output within the time specified here, the test fails. It
1537 is necessary to specify this for long running tests that produce no
1538 intermediate output.
1539
1540 =back
1541
1542 =head1 CONFIGURATION FILE
1543
1544 Novaboot can read its configuration from one or more files. By
1545 default, novaboot looks for files named F<.novaboot> as described in
1546 L</Configuration reading phase>. Alternatively, its location can be
1547 specified with the B<-c> switch or with the NOVABOOT_CONFIG
1548 environment variable. The configuration file has perl syntax and
1549 should set values of certain Perl variables. The current configuration
1550 can be dumped with the B<--dump-config> switch. Some configuration
1551 variables can be overridden by environment variables (see below) or by
1552 command line switches.
1553
1554 Supported configuration variables include:
1555
1556 =over 8
1557
1558 =item $builddir
1559
1560 Build directory location relative to the location of the configuration
1561 file.
1562
1563 =item $default_target
1564
1565 Default target (see below) to use when no target is explicitly
1566 specified on command line with the B<--target> option.
1567
1568 =item %targets
1569
1570 Hash of shortcuts to be used with the B<--target> option. If the hash
1571 contains, for instance, the following pair of values
1572
1573  'mybox' => '--server=boot:/tftproot --serial=/dev/ttyUSB0 --grub',
1574
1575 then the following two commands are equivalent:
1576
1577  ./script --server=boot:/tftproot --serial=/dev/ttyUSB0 --grub
1578  ./script -t mybox
1579
1580 =back
1581
1582 =head1 ENVIRONMENT VARIABLES
1583
1584 Some options can be specified not only via config file or command line
1585 but also through environment variables. Environment variables override
1586 the values from configuration file and command line parameters
1587 override the environment variables.
1588
1589 =over 8
1590
1591 =item NOVABOOT_CONFIG
1592
1593 Name of the novaboot configuration file to use instead of the default
1594 one(s).
1595
1596 =item NOVABOOT_BENDER
1597
1598 Defining this variable has the same meaning as B<--bender> option.
1599
1600 =back
1601
1602 =head1 AUTHORS
1603
1604 Michal Sojka <sojka@os.inf.tu-dresden.de>