]> rtime.felk.cvut.cz Git - can-eth-gw.git/blob - bench/novaboot
Fix device name
[can-eth-gw.git] / bench / novaboot
1 #!/usr/bin/perl -w\r
2 \r
3 use strict;\r
4 use warnings;\r
5 use Getopt::Long qw(GetOptionsFromString);\r
6 use Pod::Usage;\r
7 use File::Basename;\r
8 use File::Spec;\r
9 use IO::Handle;\r
10 use Time::HiRes("usleep");\r
11 use Socket;\r
12 use FileHandle;\r
13 use IPC::Open2;\r
14 use POSIX qw(:errno_h);\r
15 use Cwd;\r
16 \r
17 # always flush\r
18 $| = 1;\r
19 \r
20 my $invocation_dir = getcwd();\r
21 \r
22 chomp(my $gittop = `git rev-parse --show-toplevel 2>/dev/null`);\r
23 \r
24 # Default configuration\r
25 $CFG::hypervisor = "bin/apps/hypervisor";\r
26 $CFG::hypervisor_params = "serial";\r
27 $CFG::server = "erwin.inf.tu-dresden.de:boot/novaboot/\$NAME";\r
28 $CFG::server_grub_prefix = "(nd)/tftpboot/sojka/novaboot/\$NAME";\r
29 $CFG::grub_keys = ''; #"/novaboot\n\n/\$NAME\n\n";\r
30 $CFG::grub2_prolog = "  set root='(hd0,msdos1)'";\r
31 $CFG::genisoimage = "genisoimage";\r
32 $CFG::iprelay_addr = '141.76.48.80:2324'; #'141.76.48.252';\r
33 $CFG::qemu = 'qemu';\r
34 $CFG::script_modifier = ''; # Depricated, use --scriptmod commandline option or custom_options.\r
35 @CFG::chainloaders = (); #('bin/boot/bender promisc');\r
36 $CFG::pulsar_root = '';\r
37 $CFG::pulsar_mac = '52-54-00-12-34-56';\r
38 %CFG::custom_options = ('I' => '--server=erwin.inf.tu-dresden.de:~passive/boot --rsync-flags="--chmod=Dg+s,ug+w,o-w,+rX --rsync-path=\"umask 002 && rsync\"" --grub-prefix=(nd)/tftpboot/passive/ --iprelay=141.76.48.80:2324 --scriptmod=s/\\\\bhostserial\\\\b/hostserialpci/g',\r
39                         'J' => '--server=rtime.felk.cvut.cz:/srv/tftp/novaboot --rsync-flags="--chmod=Dg+s,ug+w,o-w,+rX --rsync-path=\"umask 002 && rsync\"" --pulsar=novaboot --iprelay=147.32.86.92:2324');\r
40 $CFG::scons = "scons -j2";\r
41 \r
42 my @qemu_flags = qw(-cpu coreduo -smp 2);\r
43 sub read_config($) {\r
44     my ($cfg) = @_;\r
45     {\r
46         package CFG; # Put config data into a separate namespace\r
47         my $rc = do($cfg);\r
48 \r
49         # Check for errors\r
50         if ($@) {\r
51             die("ERROR: Failure compiling '$cfg' - $@");\r
52         } elsif (! defined($rc)) {\r
53             die("ERROR: Failure reading '$cfg' - $!");\r
54         } elsif (! $rc) {\r
55             die("ERROR: Failure processing '$cfg'");\r
56         }\r
57     }\r
58 }\r
59 \r
60 my $cfg = $ENV{'NOVABOOT_CONFIG'} || $ENV{'HOME'}."/.novaboot";\r
61 Getopt::Long::Configure(qw/no_ignore_case pass_through/);\r
62 GetOptions ("config|c=s" => \$cfg);\r
63 if (-s $cfg) { read_config($cfg); }\r
64 \r
65 # Command line\r
66 my ($append, $bender, $builddir, $config_name_opt, $dhcp_tftp, $dump_opt, $dump_config, $grub_config, $grub_prefix, $grub2_config, $help, $iprelay, $iso_image, $man, $no_file_gen, $off_opt, $on_opt, $pulsar, $qemu, $qemu_append, $qemu_flags_cmd, $rsync_flags, @scriptmod, $scons, $serial, $server);\r
67 \r
68 $rsync_flags = '';\r
69 \r
70 Getopt::Long::Configure(qw/no_ignore_case no_pass_through/);\r
71 my %opt_spec = (\r
72     "append|a=s"     => \$append,\r
73     "bender|b"       => \$bender,\r
74     "build-dir=s"    => \$builddir,\r
75     "dhcp-tftp|d"    => \$dhcp_tftp,\r
76     "dump"           => \$dump_opt,\r
77     "dump-config"    => \$dump_config,\r
78     "grub|g:s"       => \$grub_config,\r
79     "grub-prefix=s"  => \$grub_prefix,\r
80     "grub2:s"        => \$grub2_config,\r
81     "iprelay:s"      => \$iprelay,\r
82     "iso|i:s"        => \$iso_image,\r
83     "name=s"         => \$config_name_opt,\r
84     "no-file-gen"    => \$no_file_gen,\r
85     "off"            => \$off_opt,\r
86     "on"             => \$on_opt,\r
87     "pulsar|p:s"     => \$pulsar,\r
88     "qemu|Q=s"       => \$qemu,\r
89     "qemu-append=s"  => \$qemu_append,\r
90     "qemu-flags|q=s" => \$qemu_flags_cmd,\r
91     "rsync-flags=s"  => \$rsync_flags,\r
92     "scons:s"        => \$scons,\r
93     "scriptmod=s"    => \@scriptmod,\r
94     "serial|s:s"     => \$serial,\r
95     "server:s"       => \$server,\r
96     "h"              => \$help,\r
97     "help"           => \$man,\r
98     );\r
99 foreach my $opt(keys(%CFG::custom_options)) {\r
100     $opt_spec{$opt} = sub { GetOptionsFromString($CFG::custom_options{$opt}, %opt_spec); };\r
101 }\r
102 GetOptions %opt_spec or pod2usage(2);\r
103 pod2usage(1) if $help;\r
104 pod2usage(-exitstatus => 0, -verbose => 2) if $man;\r
105 \r
106 $CFG::iprelay_addr = $ENV{'NOVABOOT_IPRELAY'} if $ENV{'NOVABOOT_IPRELAY'};\r
107 if ($iprelay && $iprelay ne "on" && $iprelay ne "off") { $CFG::iprelay_addr = $iprelay; }\r
108 \r
109 if (defined $config_name_opt && scalar(@ARGV) > 1) { die "You cannot use --name with multiple scripts"; }\r
110 \r
111 if ($server) { $CFG::server = $server; }\r
112 if ($qemu) { $CFG::qemu = $qemu; }\r
113 $qemu_append ||= '';\r
114 \r
115 if (defined $pulsar) {\r
116     $CFG::pulsar_mac = $pulsar;\r
117 }\r
118 \r
119 if ($scons) { $CFG::scons = $scons; }\r
120 if (!@scriptmod && $CFG::script_modifier) { @scriptmod = ( $CFG::script_modifier ); }\r
121 if (defined $grub_prefix) { $CFG::server_grub_prefix = $grub_prefix; }\r
122 \r
123 if ($dump_config) {\r
124     use Data::Dumper;\r
125     $Data::Dumper::Indent=0;\r
126     print "# This file is in perl syntax.\n";\r
127     foreach my $key(sort(keys(%CFG::))) { # See "Symbol Tables" in perlmod(1)\r
128         if (defined ${$CFG::{$key}}) { print Data::Dumper->Dump([${$CFG::{$key}}], ["*$key"]); }\r
129         if (defined @{$CFG::{$key}}) { print Data::Dumper->Dump([\@{$CFG::{$key}}], ["*$key"]); }\r
130         if (        %{$CFG::{$key}}) { print Data::Dumper->Dump([\%{$CFG::{$key}}], ["*$key"]); }\r
131         print "\n";\r
132     }\r
133     print "1;\n";\r
134     exit;\r
135 }\r
136 \r
137 if (defined $serial) {\r
138     $serial ||= "/dev/ttyUSB0";\r
139 }\r
140 \r
141 if (defined $grub_config) {\r
142     $grub_config ||= "menu.lst";\r
143 }\r
144 \r
145 if (defined $grub2_config) {\r
146     $grub2_config ||= "grub.cfg";\r
147 }\r
148 \r
149 if ($on_opt) { $iprelay="on"; }\r
150 if ($off_opt) { $iprelay="off"; }\r
151 \r
152 # Parse the config(s)\r
153 my @scripts;\r
154 my $file;\r
155 my $line;\r
156 my $EOF;\r
157 my $last_fn = '';\r
158 my ($modules, $variables, $generated, $continuation);\r
159 while (<>) {\r
160     if ($ARGV ne $last_fn) { # New script\r
161         die "Missing EOF in $last_fn" if $file;\r
162         die "Unfinished line in $last_fn" if $line;\r
163         $last_fn = $ARGV;\r
164         push @scripts, { 'filename' => $ARGV,\r
165                          'modules' => $modules = [],\r
166                          'variables' => $variables = {},\r
167                          'generated' => $generated = []};\r
168 \r
169     }\r
170     chomp();\r
171     next if /^#/ || /^\s*$/;    # Skip comments and empty lines\r
172 \r
173     foreach my $mod(@scriptmod) { eval $mod; }\r
174 \r
175     print "$_\n" if $dump_opt;\r
176 \r
177     if (/^([A-Z_]+)=(.*)$/) {   # Internal variable\r
178         $$variables{$1} = $2;\r
179         next;\r
180     }\r
181     if (/^([^ ]*)(.*?)[[:space:]]*<<([^ ]*)$/) { # Heredoc start\r
182         push @$modules, "$1$2";\r
183         $file = [];\r
184         push @$generated, {filename => $1, content => $file};\r
185         $EOF = $3;\r
186         next;\r
187     }\r
188     if ($file && $_ eq $EOF) {  # Heredoc end\r
189         undef $file;\r
190         next;\r
191     }\r
192     if ($file) {                # Heredoc content\r
193         push @{$file}, "$_\n";\r
194         next;\r
195     }\r
196     $_ =~ s/^[[:space:]]*// if ($continuation);\r
197     if (/\\$/) {                # Line continuation\r
198         $line .= substr($_, 0, length($_)-1);\r
199         $continuation = 1;\r
200         next;\r
201     }\r
202     $continuation = 0;\r
203     $line .= $_;\r
204     $line .= " $append" if ($append && scalar(@$modules) == 0);\r
205 \r
206     if ($line =~ /^([^ ]*)(.*?)[[:space:]]*< ?(.*)$/) { # Command substitution\r
207         push @$modules, "$1$2";\r
208         push @$generated, {filename => $1, command => $3};\r
209         $line = '';\r
210         next;\r
211     }\r
212     push @$modules, $line;\r
213     $line = '';\r
214 }\r
215 #use Data::Dumper;\r
216 #print Dumper(\@scripts);\r
217 \r
218 exit if $dump_opt;\r
219 \r
220 sub generate_configs($$$) {\r
221     my ($base, $generated, $filename) = @_;\r
222     if ($base) { $base = "$base/"; };\r
223     foreach my $g(@$generated) {\r
224       if (exists $$g{content}) {\r
225         my $config = $$g{content};\r
226         my $fn = $$g{filename};\r
227         open(my $f, '>', $fn) || die("$fn: $!");\r
228         map { s|\brom://([^ ]*)|rom://$base$1|g; print $f "$_"; } @{$config};\r
229         close($f);\r
230         print "novaboot: Created $fn\n";\r
231       } elsif (exists $$g{command} && ! $no_file_gen) {\r
232         $ENV{SRCDIR} = dirname(File::Spec->rel2abs( $filename, $invocation_dir ));\r
233         system_verbose("( $$g{command} ) > $$g{filename}");\r
234       }\r
235     }\r
236 }\r
237 \r
238 sub generate_grub_config($$$$;$)\r
239 {\r
240     my ($filename, $title, $base, $modules_ref, $prepend) = @_;\r
241     if ($base) { $base = "$base/"; };\r
242     open(my $fg, '>', $filename) or die "$filename: $!";\r
243     print $fg "$prepend\n" if $prepend;\r
244     my $endmark = ($serial || defined $iprelay) ? ';' : '';\r
245     print $fg "title $title$endmark\n" if $title;\r
246     #print $fg "root $base\n"; # root doesn't really work for (nd)\r
247     my $first = 1;\r
248     foreach (@$modules_ref) {\r
249         if ($first) {\r
250             $first = 0;\r
251             my ($kbin, $kcmd) = split(' ', $_, 2);\r
252             $kcmd = '' if !defined $kcmd;\r
253             print $fg "kernel ${base}$kbin $kcmd\n";\r
254         } else {\r
255             s|\brom://([^ ]*)|rom://$base$1|g; # Translate rom:// files - needed for vdisk parameter of sigma0\r
256             print $fg "module $base$_\n";\r
257         }\r
258     }\r
259     close($fg);\r
260 }\r
261 \r
262 sub generate_grub2_config($$$$;$)\r
263 {\r
264     my ($filename, $title, $base, $modules_ref, $prepend) = @_;\r
265     if ($base && substr($base,-1,1) ne '/') { $base = "$base/"; };\r
266     open(my $fg, '>', $filename) or die "$filename: $!";\r
267     print $fg "$prepend\n" if $prepend;\r
268     my $endmark = ($serial || defined $iprelay) ? ';' : '';\r
269     $title ||= 'novaboot';\r
270     print $fg "menuentry $title$endmark {\n";\r
271     print $fg "$CFG::grub2_prolog\n";\r
272     my $first = 1;\r
273     foreach (@$modules_ref) {\r
274         if ($first) {\r
275             $first = 0;\r
276             my ($kbin, $kcmd) = split(' ', $_, 2);\r
277             $kcmd = '' if !defined $kcmd;\r
278             print $fg "  multiboot ${base}$kbin $kcmd\n";\r
279         } else {\r
280             my @args = split;\r
281             # GRUB2 doesn't pass filename in multiboot info so we have to duplicate it here\r
282             $_ = join(' ', ($args[0], @args));\r
283             #s|\brom://([^ ]*)|rom://$base$1|g; # Translate rom:// files - needed for vdisk parameter of sigma0\r
284             print $fg "  module $base$_\n";\r
285         }\r
286     }\r
287     print $fg "}\n";\r
288     close($fg);\r
289 }\r
290 \r
291 sub generate_pulsar_config($$)\r
292 {\r
293     my ($filename, $modules_ref) = @_;\r
294     open(my $fg, '>', $filename) or die "$filename: $!";\r
295     print $fg "root $CFG::pulsar_root\n" if $CFG::pulsar_root;\r
296     my $first = 1;\r
297     my ($kbin, $kcmd);\r
298     foreach (@$modules_ref) {\r
299         if ($first) {\r
300             $first = 0;\r
301             ($kbin, $kcmd) = split(' ', $_, 2);\r
302             $kcmd = '' if !defined $kcmd;\r
303         } else {\r
304             my @args = split;\r
305             print $fg "load $_\n";\r
306         }\r
307     }\r
308     # Put kernel as last - this is needed for booting Linux and has no influence on non-Linux OSes\r
309     print $fg "exec $kbin $kcmd\n";\r
310     close($fg);\r
311 }\r
312 \r
313 sub exec_verbose(@)\r
314 {\r
315     print "novaboot: Running: ".join(' ', map("'$_'", @_))."\n";\r
316     exec(@_);\r
317 }\r
318 \r
319 sub system_verbose($)\r
320 {\r
321     my $cmd = shift;\r
322     print "novaboot: Running: $cmd\n";\r
323     my $ret = system($cmd);\r
324     if ($ret & 0x007f) { die("Command terminated by a signal"); }\r
325     if ($ret & 0xff00) {die("Command exit with non-zero exit code"); }\r
326     if ($ret) { die("Command failure $ret"); }\r
327 }\r
328 \r
329 if (exists $variables->{WVDESC}) {\r
330     print "Testing \"$variables->{WVDESC}\" in $last_fn:\n";\r
331 } elsif ($last_fn =~ /\.wv$/) {\r
332     print "Testing \"all\" in $last_fn:\n";\r
333 }\r
334 \r
335 my $IPRELAY;\r
336 if (defined $iprelay) {\r
337     $CFG::iprelay_addr =~ /([.0-9]+)(:([0-9]+))?/;\r
338     my $addr = $1;\r
339     my $port = $3 || 23;\r
340     my $paddr   = sockaddr_in($port, inet_aton($addr));\r
341     my $proto   = getprotobyname('tcp');\r
342     socket($IPRELAY, PF_INET, SOCK_STREAM, $proto)  || die "socket: $!";\r
343     print "novaboot: Connecting to IP relay... ";\r
344     connect($IPRELAY, $paddr)    || die "connect: $!";\r
345     print "done\n";\r
346     $IPRELAY->autoflush(1);\r
347 \r
348     while (1) {\r
349         print $IPRELAY "\xFF\xF6";\r
350         alarm(20);\r
351         local $SIG{ALRM} = sub { die "Relay AYT timeout"; };\r
352         my $ayt_reponse = "";\r
353         my $read = sysread($IPRELAY, $ayt_reponse, 100);\r
354         alarm(0);\r
355 \r
356         chomp($ayt_reponse);\r
357         print "$ayt_reponse\n";\r
358         if ($ayt_reponse =~ /<iprelayd: not connected/) {\r
359             sleep(10);\r
360             next;\r
361         }\r
362         last;\r
363     }\r
364 \r
365     sub relaycmd($$) {\r
366         my ($relay, $onoff) = @_;\r
367         die unless ($relay == 1 || $relay == 2);\r
368 \r
369         my $cmd = ($relay == 1 ? 0x5 : 0x6) | ($onoff ? 0x20 : 0x10);\r
370         return "\xFF\xFA\x2C\x32".chr($cmd)."\xFF\xF0";\r
371     }\r
372 \r
373     sub relayconf($$) {\r
374         my ($relay, $onoff) = @_;\r
375         die unless ($relay == 1 || $relay == 2);\r
376         my $cmd = ($relay == 1 ? 0xdf : 0xbf) | ($onoff ? 0x00 : 0xff);\r
377         return "\xFF\xFA\x2C\x97".chr($cmd)."\xFF\xF0";\r
378     }\r
379 \r
380     sub relay($$;$) {\r
381         my ($relay, $onoff, $can_giveup) = @_;\r
382         my $confirmation = '';\r
383         print $IPRELAY relaycmd($relay, $onoff);\r
384 \r
385         # We use non-blocking I/O and polling here because for some\r
386         # reason read() on blocking FD returns only after all\r
387         # requested data is available. If we get during the first\r
388         # read() only a part of confirmation, we may get the rest\r
389         # after the system boots and print someting, which may be too\r
390         # long.\r
391         $IPRELAY->blocking(0);\r
392 \r
393         alarm(20); # Timeout in seconds\r
394         my $giveup = 0;\r
395         local $SIG{ALRM} = sub {\r
396             if ($can_giveup) { print("Relay confirmation timeout - ignoring\n"); $giveup = 1;}\r
397             else {die "Relay confirmation timeout";}\r
398         };\r
399         my $index;\r
400         while (($index=index($confirmation, relayconf($relay, $onoff))) < 0 && !$giveup) {\r
401             my $read = read($IPRELAY, $confirmation, 70, length($confirmation));\r
402             if (!defined($read)) {\r
403                 die("IP relay: $!") unless $! == EAGAIN;\r
404                 usleep(10000);\r
405                 next;\r
406             }\r
407             #use MIME::QuotedPrint;\r
408             #print "confirmation = ".encode_qp($confirmation)."\n";\r
409         }\r
410         alarm(0);\r
411         $IPRELAY->blocking(1);\r
412     }\r
413 }\r
414 \r
415 if ($iprelay && ($iprelay eq "on" || $iprelay eq "off")) {\r
416      relay(1, 1); # Press power button\r
417     if ($iprelay eq "on") {\r
418         usleep(100000);         # Short press\r
419     } else {\r
420         usleep(6000000);        # Long press to switch off\r
421     }\r
422     print $IPRELAY relay(1, 0);\r
423     exit;\r
424 }\r
425 \r
426 if ($builddir) {\r
427     $CFG::builddir = $builddir;\r
428 } else {\r
429     if (! defined $CFG::builddir) {\r
430         $CFG::builddir = ( $gittop || $ENV{'HOME'}."/nul" ) . "/build";\r
431         if (! -d $CFG::builddir) {\r
432             $CFG::builddir = $ENV{SRCDIR} = dirname(File::Spec->rel2abs( ${$scripts[0]}{filename}, $invocation_dir ));\r
433         }\r
434     }\r
435 }\r
436 \r
437 chdir($CFG::builddir) or die "Can't change directory to $CFG::builddir: $!";\r
438 print "novaboot: Entering directory `$CFG::builddir'\n";\r
439 \r
440 my (%files_iso, $menu_iso, $config_name, $filename);\r
441 \r
442 foreach my $script (@scripts) {\r
443     $filename = $$script{filename};\r
444     $modules = $$script{modules};\r
445     $generated = $$script{generated};\r
446     $variables = $$script{variables};\r
447     my ($server_grub_prefix);\r
448 \r
449     if (defined $config_name_opt) {\r
450         $config_name = $config_name_opt;\r
451     } else {\r
452         ($config_name = $filename) =~ s#.*/##;\r
453     }\r
454 \r
455     my $kernel;\r
456     if (exists $variables->{KERNEL}) {\r
457         $kernel = $variables->{KERNEL};\r
458     } else {\r
459         $kernel = $CFG::hypervisor . " ";\r
460         if (exists $variables->{HYPERVISOR_PARAMS}) {\r
461             $kernel .= $variables->{HYPERVISOR_PARAMS};\r
462         } else {\r
463             $kernel .= $CFG::hypervisor_params;\r
464         }\r
465     }\r
466     @$modules = ($kernel, @$modules);\r
467     @$modules = (@CFG::chainloaders, @$modules);\r
468     @$modules = ("bin/boot/bender", @$modules) if ($bender || defined $ENV{'NOVABOOT_BENDER'});\r
469 \r
470     if (defined $grub_config) {\r
471         generate_configs("", $generated, $filename);\r
472         generate_grub_config($grub_config, $config_name, "", $modules);\r
473         print("GRUB menu created: $CFG::builddir/$grub_config\n");\r
474         exit;\r
475     }\r
476 \r
477     if (defined $grub2_config && !defined $server) {\r
478         generate_configs('', $generated, $filename);\r
479         generate_grub2_config($grub2_config, $config_name, $CFG::builddir, $modules);\r
480         print("GRUB2 configuration created: $CFG::builddir/$grub2_config\n");\r
481         exit;\r
482     }\r
483 \r
484     my $pulsar_config;\r
485     if (defined $pulsar) {\r
486         $pulsar_config = "config-$CFG::pulsar_mac";\r
487         generate_configs('', $generated, $filename);\r
488         generate_pulsar_config($pulsar_config, $modules);\r
489         if (!defined $server) {\r
490             print("Pulsar configuration created: $CFG::builddir/$pulsar_config\n");\r
491             exit;\r
492         }\r
493     }\r
494 \r
495     if (defined $scons) {\r
496         my @files = map({ ($file) = m/([^ ]*)/; $file; } @$modules);\r
497         # Filter-out generated files\r
498         my @to_build = grep({ my $file = $_; !scalar(grep($file eq $$_{filename}, @$generated)) } @files);\r
499         system_verbose($CFG::scons." ".join(" ", @to_build));\r
500     }\r
501 \r
502     if (defined $server) {\r
503         ($server_grub_prefix = $CFG::server_grub_prefix) =~ s/\$NAME/$config_name/;\r
504         ($server = $CFG::server)                         =~ s/\$NAME/$config_name/;\r
505         my $bootloader_config;\r
506         if ($grub2_config) {\r
507             generate_configs('', $generated, $filename);\r
508             $bootloader_config ||= "grub.cfg";\r
509             generate_grub2_config($grub2_config, $config_name, $server_grub_prefix, $modules);\r
510         } elsif (defined $pulsar) {\r
511             $bootloader_config = $pulsar_config;\r
512         } else {\r
513             generate_configs($server_grub_prefix, $generated, $filename);\r
514             $bootloader_config ||= "menu.lst";\r
515             if (!grep { $_ eq $bootloader_config } @$modules) {\r
516                 generate_grub_config($bootloader_config, $config_name, $server_grub_prefix, $modules,\r
517                                      $server_grub_prefix eq $CFG::server_grub_prefix ? "timeout 0" : undef);\r
518             }\r
519         }\r
520         my ($hostname, $path) = split(":", $server, 2);\r
521         if (! defined $path) {\r
522             $path = $hostname;\r
523             $hostname = "";\r
524         }\r
525         my $files = "$bootloader_config " . join(" ", map({ ($file) = m/([^ ]*)/; $file; } @$modules));\r
526         my $combined_menu_lst = ($CFG::server =~ m|/\$NAME$|);\r
527         map({ my $file = (split)[0]; die "$file: $!" if ! -f $file; } @$modules);\r
528         my $istty = -t STDOUT && $ENV{'TERM'} ne "dumb";\r
529         my $progress = $istty ? "--progress" : "";\r
530         system_verbose("rsync $progress -RLp $rsync_flags $files $server");\r
531         my $cmd = "cd $path/.. && cat */menu.lst > menu.lst";\r
532         if ($combined_menu_lst) { system_verbose($hostname ? "ssh $hostname '$cmd'" : $cmd); }\r
533     }\r
534 \r
535     if (defined $iso_image) {\r
536         generate_configs("(cd)", $generated, $filename);\r
537         my $menu;\r
538         generate_grub_config(\$menu, $config_name, "(cd)", $modules);\r
539         $menu_iso .= "$menu\n";\r
540         map { ($file,undef) = split; $files_iso{$file} = 1; } @$modules;\r
541     }\r
542 }\r
543 \r
544 if (defined $iso_image) {\r
545     open(my $fh, ">menu-iso.lst");\r
546     print $fh "timeout 5\n\n$menu_iso";\r
547     close($fh);\r
548     my $files = "boot/grub/menu.lst=menu-iso.lst " . join(" ", map("$_=$_", keys(%files_iso)));\r
549     $iso_image ||= "$config_name.iso";\r
550     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");\r
551     print("ISO image created: $CFG::builddir/$iso_image\n");\r
552 }\r
553 \r
554 ######################################################################\r
555 # Boot NOVA using various methods and send serial output to stdout\r
556 ######################################################################\r
557 \r
558 if (scalar(@scripts) > 1 && ( defined $dhcp_tftp || defined $serial || defined $iprelay)) {\r
559     die "You cannot do this with multiple scripts simultaneously";\r
560 }\r
561 \r
562 if ($variables->{WVTEST_TIMEOUT}) {\r
563     print "wvtest: timeout ", $variables->{WVTEST_TIMEOUT}, "\n";\r
564 }\r
565 \r
566 if (!(defined $dhcp_tftp || defined $serial || defined $iprelay || defined $server || defined $iso_image)) {\r
567     # Qemu\r
568     @qemu_flags = split(/ /, $variables->{QEMU_FLAGS}) if exists $variables->{QEMU_FLAGS};\r
569     @qemu_flags = split(/ /, $qemu_flags_cmd) if $qemu_flags_cmd;\r
570     push(@qemu_flags, split(/ /, $qemu_append));\r
571 \r
572     if (defined $iso_image) {\r
573         # Boot NOVA with grub (and test the iso image)\r
574         push(@qemu_flags, ('-cdrom', "$config_name.iso"));\r
575     } else {\r
576         # Boot NOVA without GRUB\r
577 \r
578         # Non-patched qemu doesn't like commas, but NUL can live with pluses instead of commans\r
579         foreach (@$modules) {s/,/+/g;}\r
580         generate_configs("", $generated, $filename);\r
581 \r
582         my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);\r
583         $kcmd = '' if !defined $kcmd;\r
584         my $initrd = join ",", @$modules;\r
585 \r
586         push(@qemu_flags, ('-kernel', $kbin, '-append', $kcmd));\r
587         push(@qemu_flags, ('-initrd', $initrd)) if $initrd;\r
588     }\r
589     push(@qemu_flags,  qw(-serial stdio)); # Redirect serial output (for collecting test restuls)\r
590     exec_verbose(($CFG::qemu,  '-name', $config_name, @qemu_flags));\r
591 }\r
592 \r
593 my ($dhcpd_pid, $tftpd_pid);\r
594 \r
595 if (defined $dhcp_tftp)\r
596 {\r
597     generate_configs("(nd)", $generated, $filename);\r
598     system_verbose('mkdir -p tftpboot');\r
599     generate_grub_config("tftpboot/os-menu.lst", $config_name, "(nd)", \@$modules, "timeout 0");\r
600     open(my $fh, '>', 'dhcpd.conf');\r
601     my $mac = `cat /sys/class/net/eth0/address`;\r
602     chomp $mac;\r
603     print $fh "subnet 10.23.23.0 netmask 255.255.255.0 {\r
604                       range 10.23.23.10 10.23.23.100;\r
605                       filename \"bin/boot/grub/pxegrub.pxe\";\r
606                       next-server 10.23.23.1;\r
607 }\r
608 host server {\r
609         hardware ethernet $mac;\r
610         fixed-address 10.23.23.1;\r
611 }";\r
612     close($fh);\r
613     system_verbose("sudo ip a add 10.23.23.1/24 dev eth0;\r
614             sudo ip l set dev eth0 up;\r
615             sudo touch dhcpd.leases");\r
616 \r
617     $dhcpd_pid = fork();\r
618     if ($dhcpd_pid == 0) {\r
619         # This way, the spawned server are killed when this script is killed.\r
620         exec_verbose("sudo dhcpd -d -cf dhcpd.conf -lf dhcpd.leases -pf dhcpd.pid");\r
621     }\r
622     $tftpd_pid = fork();\r
623     if ($tftpd_pid == 0) {\r
624         exec_verbose("sudo in.tftpd --foreground --secure -v -v -v $CFG::builddir");\r
625     }\r
626     $SIG{TERM} = sub { print "CHILDS KILLED\n"; kill 15, $dhcpd_pid, $tftpd_pid; };\r
627 }\r
628 \r
629 if ($serial || defined $iprelay) {\r
630     my $CONN;\r
631     if (defined $iprelay) {\r
632         print "novaboot: Reseting the test box... ";\r
633         relay(2, 1, 1); # Reset the machine\r
634         usleep(100000);\r
635         relay(2, 0);\r
636         print "done\n";\r
637 \r
638         $CONN = $IPRELAY;\r
639     } elsif ($serial) {\r
640         system("stty -F $serial raw -crtscts -onlcr 115200");\r
641         open($CONN, "+<", $serial) || die "open $serial: $!";\r
642         $CONN->autoflush(1);\r
643     }\r
644     if (!defined $dhcp_tftp && $CFG::grub_keys) {\r
645         # Control grub via serial line\r
646         print "Waiting for GRUB's serial output... ";\r
647         while (<$CONN>) {\r
648             if (/Press any key to continue/) { print $CONN "\n"; last; }\r
649         }\r
650         $CFG::grub_keys =~ s/\$NAME/$config_name;/;\r
651         my @characters = split(//, $CFG::grub_keys);\r
652         foreach (@characters) {\r
653             print $CONN $_;\r
654             usleep($_ eq "\n" ? 100000 : 10000);\r
655         }\r
656         print $CONN "\n";\r
657         print "done\n";\r
658     }\r
659     # Pass the NOVA output to stdout.\r
660     while (<$CONN>) {\r
661         print;\r
662     }\r
663     kill 15, $dhcpd_pid, $tftpd_pid if ($dhcp_tftp);\r
664     exit;\r
665 }\r
666 \r
667 if (defined $dhcp_tftp) {\r
668     my $pid = wait();\r
669     if ($pid == $dhcpd_pid) { print "dhcpd exited!\n"; }\r
670     elsif ($pid == $tftpd_pid) { print "tftpd exited!\n"; }\r
671     else { print "wait returned: $pid\n"; }\r
672     kill(15, 0); # Kill current process group i.e. all remaining children\r
673 }\r
674 \r
675 =head1 NAME\r
676 \r
677 novaboot - NOVA boot script interpreter\r
678 \r
679 =head1 SYNOPSIS\r
680 \r
681 B<novaboot> [ options ] [--] script...\r
682 \r
683 B<./script> [ options ]\r
684 \r
685 B<novaboot> --help\r
686 \r
687 =head1 DESCRIPTION\r
688 \r
689 This program makes it easier to boot NOVA or other operating system\r
690 (OS) in different environments. It reads a so called novaboot script\r
691 and uses it either to boot the OS in an emulator (e.g. in qemu) or to\r
692 generate the configuration for a specific bootloader and optionally to\r
693 copy the necessary binaries and other needed files to proper\r
694 locations, perhaps on a remote server. In case the system is actually\r
695 booted, its serial output is redirected to standard output if that is\r
696 possible.\r
697 \r
698 A typical way of using novaboot is to make the novaboot script\r
699 executable and set its first line to I<#!/usr/bin/env novaboot>. Then,\r
700 booting a particular OS configuration becomes the same as executing a\r
701 local program - the novaboot script.\r
702 \r
703 With C<novaboot> you can:\r
704 \r
705 =over 3\r
706 \r
707 =item 1.\r
708 \r
709 Run NOVA in Qemu. This is the default action when no other action is\r
710 specified by command line switches. Thus running C<novaboot ./script>\r
711 (or C<./script> as described above) will run Qemu with configuration\r
712 specified in the I<script>.\r
713 \r
714 =item 2.\r
715 \r
716 Create a bootloader configuration file (currently supported\r
717 bootloaders are GRUB, GRUB2 and Pulsar) and copy it with all files\r
718 needed for booting another, perhaps remote, location.\r
719 \r
720  ./script --server --iprelay\r
721 \r
722 This command copies files to a TFTP server specified in the\r
723 configuration file and uses TCP/IP-controlled relay to reset the test\r
724 box and receive its serial output.\r
725 \r
726 =item 3.\r
727 \r
728 Run DHCP and TFTP server on developer's machine to PXE-boot NOVA from\r
729 it. E.g.\r
730 \r
731  ./script --dhcp-tftp\r
732 \r
733 When a PXE-bootable machine is connected via Ethernet to developer's\r
734 machine, it will boot the configuration described in I<script>.\r
735 \r
736 =item 4.\r
737 \r
738 Create bootable ISO images. E.g.\r
739 \r
740  novaboot --iso -- script1 script2\r
741 \r
742 The created ISO image will have GRUB bootloader installed on it and\r
743 the boot menu will allow selecting between I<script1> and I<script2>\r
744 configurations.\r
745 \r
746 =back\r
747 \r
748 =head1 OPTIONS\r
749 \r
750 =over 8\r
751 \r
752 =item -a, --append=<parameters>\r
753 \r
754 Appends a string to the root task's command line.\r
755 \r
756 =item -b, --bender\r
757 \r
758 Boot bender tool before the kernel to find PCI serial ports.\r
759 \r
760 =item --build-dir=<directory>\r
761 \r
762 Overrides the default build directory location.\r
763 \r
764 The default build directory location is determined as follows:\r
765 \r
766 If there is a configuration file, the value specified in I<$builddir>\r
767 variable is used. Otherwise, if the current working directory is\r
768 inside git work tree and there is F<build> directory at the top of\r
769 that tree, it is used. Otherwise, if directory F<~/nul/build> exists,\r
770 it is used. Otherwise, it is the directory that contains the first\r
771 processed novaboot script.\r
772 \r
773 =item -c, --config=<filename>\r
774 \r
775 Use a different configuration file than the default one (i.e.\r
776 F<~/.novaboot>).\r
777 \r
778 =item -d, --dhcp-tftp\r
779 \r
780 Turns your workstation into a DHCP and TFTP server so that NOVA\r
781 can be booted via PXE BIOS on a test machine directly connected by\r
782 a plain Ethernet cable to your workstation.\r
783 \r
784 The DHCP and TFTP servers require root privileges and C<novaboot>\r
785 uses C<sudo> command to obtain those. You can put the following to\r
786 I</etc/sudoers> to allow running the necessary commands without\r
787 asking for password.\r
788 \r
789  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 *, /usr/bin/touch dhcpd.leases\r
790  your_login ALL=NOPASSWD: NOVABOOT\r
791 \r
792 =item --dump\r
793 \r
794 Prints the content of the novaboot script after removing comments and\r
795 evaluating all I<--scriptmod> expressions.\r
796 \r
797 =item --dump-config\r
798 \r
799 Dumps current configuration to stdout end exits. Useful as an initial\r
800 template for a configuration file.\r
801 \r
802 =item -g, --grub[=I<filename>]\r
803 \r
804 Generates grub menu file. If the I<filename> is not specified,\r
805 F<menu.lst> is used. The I<filename> is relative to NUL build\r
806 directory.\r
807 \r
808 =item --grub-prefix=I<prefix>\r
809 \r
810 Specifies I<prefix> that is put before every file in GRUB's menu.lst.\r
811 This overrides the value of I<$server_grub_prefix> from the\r
812 configuration file.\r
813 \r
814 =item --grub2[=I<filename>]\r
815 \r
816 Generate GRUB2 menuentry in I<filename>. If I<filename> is not\r
817 specified grub.cfg is used. The content of the menuentry can be\r
818 customized by I<$grub2_prolog> and I<$server_grub_prefix>\r
819 configuration variables.\r
820 \r
821 In order to use the the generated menuentry on your development\r
822 machine that uses GRUB2, append the following snippet to\r
823 F</etc/grub.d/40_custom> file and regenerate your grub configuration,\r
824 i.e. run update-grub on Debian/Ubuntu.\r
825 \r
826   if [ -f /path/to/nul/build/grub.cfg ]; then\r
827     source /path/to/nul/build/grub.cfg\r
828   fi\r
829 \r
830 \r
831 =item -h, --help\r
832 \r
833 Print short (B<-h>) or long (B<--help>) help.\r
834 \r
835 =item --iprelay[=addr or cmd]\r
836 \r
837 If no I<cmd> is given, use IP relay to reset the machine and to get\r
838 the serial output. The IP address of the relay is given by I<addr>\r
839 parameter if specified or by $iprelay_addr variable in the\r
840 configuration file.\r
841 \r
842 If I<cmd> is one of "on" or "off", the IP relay is used to press power\r
843 button for a short (in case of "on") or long (in case of "off") time.\r
844 Then, novaboot exits.\r
845 \r
846 Note: This option is expected to work with HWG-ER02a IP relays.\r
847 \r
848 =item -i, --iso[=filename]\r
849 \r
850 Generates the ISO image that boots NOVA system via GRUB. If no filename\r
851 is given, the image is stored under I<NAME>.iso, where I<NAME> is the name\r
852 of novaboot script (see also B<--name>).\r
853 \r
854 =item -I\r
855 \r
856 This is an alias (see C<%custom_options> below) defined in the default\r
857 configuration. When used, it causes novaboot to use Michal's remotely\r
858 controllable test bed.\r
859 \r
860 =item -J\r
861 \r
862 This is an alias (see C<%custom_options> below) defined in the default\r
863 configuration. When used, it causes novaboot to use another remotely\r
864 controllable test bed.\r
865 \r
866 =item --on, --off\r
867 \r
868 Synonym for --iprelay=on/off.\r
869 \r
870 =item --name=I<string>\r
871 \r
872 Use the name I<string> instead of the name of the novaboot script.\r
873 This name is used for things like a title of grub menu or for the\r
874 server directory where the boot files are copied to.\r
875 \r
876 =item --no-file-gen\r
877 \r
878 Do not generate files on the fly (i.e. "<" syntax) except for the\r
879 files generated via "<<WORD" syntax.\r
880 \r
881 =item -p, --pulsar[=mac]\r
882 \r
883 Generates pulsar bootloader configuration file whose name is based on\r
884 the MAC address specified either on the command line or taken from\r
885 I<.novaboot> configuration file.\r
886 \r
887 =item -Q, --qemu=I<qemu-binary>\r
888 \r
889 Use specific version of qemu binary. The default is 'qemu'.\r
890 \r
891 =item --qemu-append=I<flags>\r
892 \r
893 Append I<flags> to the default qemu flags (QEMU_FLAGS variable or\r
894 C<-cpu coreduo -smp 2>).\r
895 \r
896 =item -q, --qemu-flags=I<flags>\r
897 \r
898 Replace the default qemu flags (QEMU_FLAGS variable or C<-cpu coreduo\r
899 -smp 2>) with I<flags> specified here.\r
900 \r
901 =item --rsync-flags=I<flags>\r
902 \r
903 Specifies which I<flags> are appended to F<rsync> command line when\r
904 copying files as a result of I<--server> option.\r
905 \r
906 =item --scons[=scons command]\r
907 \r
908 Runs I<scons> to build files that are not generated by novaboot\r
909 itself.\r
910 \r
911 =item --scriptmod=I<perl expression>\r
912 \r
913 When novaboot script is read, I<perl expression> is executed for every\r
914 line (in $_ variable). For example, C<novaboot\r
915 --scriptmod=s/sigma0/omega6/g> replaces every occurrence of I<sigma0>\r
916 in the script with I<omega6>.\r
917 \r
918 When this option is present, it overrides I<$script_modifier> variable\r
919 from the configuration file, which has the same effect. If this option\r
920 is given multiple times all expressions are evaluated in the command\r
921 line order.\r
922 \r
923 =item --server[=[[user@]server:]path]\r
924 \r
925 Copy all files needed for booting to a server (implies B<-g> unless\r
926 B<--grub2> is given). The files will be copied to the directory\r
927 I<path>. If the I<path> contains string $NAME, it will be replaced\r
928 with the name of the novaboot script (see also B<--name>).\r
929 \r
930 Additionally, if $NAME is the last component of the I<path>, a file\r
931 named I<path>/menu.lst (with $NAME removed from the I<path>) will be\r
932 created on the server by concatenating all I<path>/*/menu.lst (with\r
933 $NAME removed from the I<path>) files found on the server.\r
934 \r
935 =item -s, --serial[=device]\r
936 \r
937 Use serial line to control GRUB bootloader and to get the serial\r
938 output of the machine. The default value is /dev/ttyUSB0.\r
939 \r
940 =back\r
941 \r
942 =head1 NOVABOOT SCRIPT SYNTAX\r
943 \r
944 The syntax tries to mimic POSIX shell syntax. The syntax is defined with the following rules.\r
945 \r
946 Lines starting with "#" are ignored.\r
947 \r
948 Lines that end with "\" are concatenated with the following line after\r
949 removal of the final "\" and leading whitespace of the following line.\r
950 \r
951 Lines in the form I<VARIABLE=...> (i.e. matching '^[A-Z_]+=' regular\r
952 expression) assign values to internal variables. See VARIABLES\r
953 section.\r
954 \r
955 Otherwise, the first word on the line represents the filename\r
956 (relative to the build directory (see I<--build-dir>) of the module to\r
957 load and the remaining words are passed as the command line\r
958 parameters.\r
959 \r
960 When the line ends with "<<WORD" then the subsequent lines until the\r
961 line containing only WORD are copied literally to the file named on\r
962 that line.\r
963 \r
964 When the line ends with "< CMD" the command CMD is executed with\r
965 C</bin/sh> and its standard output is stored in the file named on that\r
966 line. The SRCDIR variable in CMD's environment is set to the absolute\r
967 path of the directory containing the interpreted novaboot script.\r
968 \r
969 Example:\r
970   #!/usr/bin/env novaboot\r
971   WVDESC=Example program\r
972   bin/apps/sigma0.nul S0_DEFAULT script_start:1,1 \\r
973     verbose hostkeyb:0,0x60,1,12,2\r
974   bin/apps/hello.nul\r
975   hello.nulconfig <<EOF\r
976   sigma0::mem:16 name::/s0/log name::/s0/timer name::/s0/fs/rom ||\r
977   rom://bin/apps/hello.nul\r
978   EOF\r
979 \r
980 This example will load three modules: sigma0.nul, hello.nul and\r
981 hello.nulconfig. sigma0 gets some command line parameters and\r
982 hello.nulconfig file is generated on the fly from the lines between\r
983 <<EOF and EOF.\r
984 \r
985 =head2 VARIABLES\r
986 \r
987 The following variables are interpreted in the novaboot script:\r
988 \r
989 =over 8\r
990 \r
991 =item WVDESC\r
992 \r
993 Description of the wvtest-compliant program.\r
994 \r
995 =item WVTEST_TIMEOUT\r
996 \r
997 The timeout in seconds for WvTest harness. If no complete line appears\r
998 in the test output within the time specified here, the test fails. It\r
999 is necessary to specify this for long running tests that produce no\r
1000 intermediate output.\r
1001 \r
1002 =item QEMU_FLAGS\r
1003 \r
1004 Use specific qemu flags (can be overriden by B<-q>).\r
1005 \r
1006 =item HYPERVISOR_PARAMS\r
1007 \r
1008 Parameters passed to hypervisor. The default value is "serial", unless\r
1009 overriden in configuration file.\r
1010 \r
1011 =item KERNEL\r
1012 \r
1013 The kernel to use instead of NOVA hypervisor specified in the\r
1014 configuration file. The value should contain the name of the kernel\r
1015 image as well as its command line parameters. If this variable is\r
1016 defined and non-empty, the variable HYPERVISOR_PARAMS is not used.\r
1017 \r
1018 =back\r
1019 \r
1020 =head1 CONFIGURATION FILE\r
1021 \r
1022 novaboot can read its configuration from ~/.novaboot file (or another\r
1023 file specified with B<-c> parameter or NOVABOOT_CONFIG environment\r
1024 variable). It is a file with perl syntax, which sets values of certain\r
1025 variables. The current configuration can be dumped with\r
1026 B<--dump-config> switch. Use\r
1027 \r
1028     novaboot --dump-config > ~/.novaboot\r
1029 \r
1030 to create a default configuration file and modify it to your needs.\r
1031 Some configuration variables can be overriden by environment variables\r
1032 (see below) or by command line switches.\r
1033 \r
1034 Documentation of some configuration variables follows:\r
1035 \r
1036 =over 8\r
1037 \r
1038 =item @chainloaders\r
1039 \r
1040 Custom chainloaders to load before hypervisor and files specified in\r
1041 novaboot script. E.g. ('bin/boot/bender promisc', 'bin/boot/zapp').\r
1042 \r
1043 =item %custom_options\r
1044 \r
1045 Defines custom command line options that can serve as aliases for\r
1046 other options. E.g. 'S' => '--server=boot:/tftproot\r
1047 --serial=/dev/ttyUSB0'.\r
1048 \r
1049 =back\r
1050 \r
1051 =head1 ENVIRONMENT VARIABLES\r
1052 \r
1053 Some options can be specified not only via config file or command line\r
1054 but also through environment variables. Environment variables override\r
1055 the values from configuration file and command line parameters\r
1056 override the environment variables.\r
1057 \r
1058 =over 8\r
1059 \r
1060 =item NOVABOOT_CONFIG\r
1061 \r
1062 A name of default novaboot configuration file.\r
1063 \r
1064 =item NOVABOOT_BENDER\r
1065 \r
1066 Defining this variable has the same meaning as B<--bender> option.\r
1067 \r
1068 =item NOVABOOT_IPRELAY\r
1069 \r
1070 The IP address (and optionally the port) of the IP relay. This\r
1071 overrides $iprelay_addr variable from the configuration file.\r
1072 \r
1073 =back\r
1074 \r
1075 =head1 AUTHORS\r
1076 \r
1077 Michal Sojka <sojka@os.inf.tu-dresden.de>\r