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