]> rtime.felk.cvut.cz Git - novaboot.git/blob - novaboot
Fix example sudoers file
[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 = 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::targets = (
45     "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',
46     "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',
47     "localhost" => '--scriptmod=s/console=tty[A-Z0-9,]+// --server=/boot/novaboot/$NAME --grub2 --grub-prefix=/boot/novaboot/$NAME --grub2-prolog="  set root=\'(hd0,msdos1)\'"',
48     );
49 $CFG::scons = "scons -j2";
50
51 my $builddir;
52
53 sub read_config($) {
54     my ($cfg) = @_;
55     {
56         package CFG; # Put config data into a separate namespace
57         my $rc = do($cfg);
58
59         # Check for errors
60         if ($@) {
61             die("ERROR: Failure compiling '$cfg' - $@");
62         } elsif (! defined($rc)) {
63             die("ERROR: Failure reading '$cfg' - $!");
64         } elsif (! $rc) {
65             die("ERROR: Failure processing '$cfg'");
66         }
67     }
68     $builddir = File::Spec->rel2abs($CFG::builddir, dirname($cfg)) if defined $CFG::builddir;
69     print STDERR "novaboot: Read $cfg\n";
70 }
71
72 my @cfgs;
73 {
74     # We don't use $0 here, because it points to the novaboot itself and
75     # not to the novaboot script. The problem with this approach is that
76     # when a script is run as "novaboot <options> <script>" then $ARGV[0]
77     # contains the first option. Hence the -f check.
78     my $dir = File::Spec->rel2abs($ARGV[0] && -f $ARGV[0] ? dirname($ARGV[0]) : '', $invocation_dir);
79     while (-d $dir && $dir ne "/") {
80         push @cfgs, "$dir/.novaboot" if -r "$dir/.novaboot";
81         $dir = abs_path($dir."/..");
82     }
83 }
84 my $cfg = $ENV{'NOVABOOT_CONFIG'};
85 Getopt::Long::Configure(qw/no_ignore_case pass_through/);
86 GetOptions ("config|c=s" => \$cfg);
87 read_config($_) foreach $cfg or reverse @cfgs;
88
89 ## Command line handling
90
91 my ($append, $bender, @chainloaders, $concat, $config_name_opt, $dhcp_tftp, $dump_opt, $dump_config, $gen_only, $grub_config, $grub_prefix, $grub_preamble, $grub2_prolog, $grub2_config, $help, $iprelay, $iso_image, $man, $no_file_gen, $off_opt, $on_opt, $pulsar, $pulsar_root, $qemu, $qemu_append, $qemu_flags_cmd, $rom_prefix, $rsync_flags, @scriptmod, $scons, $serial, $server, $stty, $uboot);
92
93 $rsync_flags = '';
94 $rom_prefix = 'rom://';
95 $stty = 'raw -crtscts -onlcr 115200';
96
97 Getopt::Long::Configure(qw/no_ignore_case no_pass_through/);
98 my %opt_spec;
99 %opt_spec = (
100     "append|a=s"     => \$append,
101     "bender|b"       => \$bender,
102     "build-dir=s"    => sub { my ($n, $v) = @_; $builddir = File::Spec->rel2abs($v); },
103     "concat"         => \$concat,
104     "chainloader=s"  => \@chainloaders,
105     "dhcp-tftp|d"    => \$dhcp_tftp,
106     "dump"           => \$dump_opt,
107     "dump-config"    => \$dump_config,
108     "gen-only"       => \$gen_only,
109     "grub|g:s"       => \$grub_config,
110     "grub-preamble=s"=> \$grub_preamble,
111     "grub-prefix=s"  => \$grub_prefix,
112     "grub2:s"        => \$grub2_config,
113     "grub2-prolog=s" => \$grub2_prolog,
114     "iprelay=s"      => \$iprelay,
115     "iso|i:s"        => \$iso_image,
116     "name=s"         => \$config_name_opt,
117     "no-file-gen"    => \$no_file_gen,
118     "off"            => \$off_opt,
119     "on"             => \$on_opt,
120     "pulsar|p:s"     => \$pulsar,
121     "pulsar-root=s"  => \$pulsar_root,
122     "qemu|Q=s"       => \$qemu,
123     "qemu-append=s"  => \$qemu_append,
124     "qemu-flags|q=s" => \$qemu_flags_cmd,
125     "rsync-flags=s"  => \$rsync_flags,
126     "scons:s"        => \$scons,
127     "scriptmod=s"    => \@scriptmod,
128     "serial|s:s"     => \$serial,
129     "server:s"       => \$server,
130     "strip-rom"      => sub { $rom_prefix = ''; },
131     "stty=s"         => \$stty,
132     "target|t=s"     => sub { my ($opt_name, $opt_value) = @_;
133                               exists $CFG::targets{$opt_value} or die("Unknown target '$opt_value' (valid targets are: ".join(", ", sort keys(%CFG::targets)).")");
134                               GetOptionsFromString($CFG::targets{$opt_value}, %opt_spec); },
135     "uboot"          => \$uboot,
136     "h"              => \$help,
137     "help"           => \$man,
138     );
139 GetOptions %opt_spec or pod2usage(2);
140 pod2usage(1) if $help;
141 pod2usage(-exitstatus => 0, -verbose => 2) if $man;
142
143 ### Dump sanitized configuration (if requested)
144
145 if ($dump_config) {
146     use Data::Dumper;
147     $Data::Dumper::Indent=1;
148     print "# This file is in perl syntax.\n";
149     foreach my $key(sort(keys(%CFG::))) { # See "Symbol Tables" in perlmod(1)
150         if (defined ${$CFG::{$key}}) { print Data::Dumper->Dump([${$CFG::{$key}}], ["*$key"]); }
151         if (        @{$CFG::{$key}}) { print Data::Dumper->Dump([\@{$CFG::{$key}}], ["*$key"]); }
152         if (        %{$CFG::{$key}}) { print Data::Dumper->Dump([\%{$CFG::{$key}}], ["*$key"]); }
153     }
154     print "1;\n";
155     exit;
156 }
157
158 ### Sanitize configuration
159
160 if (defined $config_name_opt && scalar(@ARGV) > 1) { die "You cannot use --name with multiple scripts"; }
161
162 # Default options
163 if (defined $serial) { $serial ||= "/dev/ttyUSB0"; }
164 if (defined $grub_config) { $grub_config ||= "menu.lst"; }
165 if (defined $grub2_config) { $grub2_config ||= "grub.cfg"; }
166
167 ## Parse the novaboot script(s)
168 my @scripts;
169 my $file;
170 my $line;
171 my $EOF;
172 my $last_fn = '';
173 my ($modules, $variables, $generated, $continuation);
174 while (<>) {
175     if ($ARGV ne $last_fn) { # New script
176         die "Missing EOF in $last_fn" if $file;
177         die "Unfinished line in $last_fn" if $line;
178         $last_fn = $ARGV;
179         push @scripts, { 'filename' => $ARGV,
180                          'modules' => $modules = [],
181                          'variables' => $variables = {},
182                          'generated' => $generated = []};
183
184     }
185     chomp();
186     next if /^#/ || /^\s*$/;    # Skip comments and empty lines
187
188     foreach my $mod(@scriptmod) { eval $mod; }
189
190     print "$_\n" if $dump_opt;
191
192     if (/^([A-Z_]+)=(.*)$/) {   # Internal variable
193         $$variables{$1} = $2;
194         next;
195     }
196     if (/^([^ ]*)(.*?)[[:space:]]*<<([^ ]*)$/) { # Heredoc start
197         push @$modules, "$1$2";
198         $file = [];
199         push @$generated, {filename => $1, content => $file};
200         $EOF = $3;
201         next;
202     }
203     if ($file && $_ eq $EOF) {  # Heredoc end
204         undef $file;
205         next;
206     }
207     if ($file) {                # Heredoc content
208         push @{$file}, "$_\n";
209         next;
210     }
211     $_ =~ s/^[[:space:]]*// if ($continuation);
212     if (/\\$/) {                # Line continuation
213         $line .= substr($_, 0, length($_)-1);
214         $continuation = 1;
215         next;
216     }
217     $continuation = 0;
218     $line .= $_;
219     $line .= " $append" if ($append && scalar(@$modules) == 0);
220
221     if ($line =~ /^([^ ]*)(.*?)[[:space:]]*< ?(.*)$/) { # Command substitution
222         push @$modules, "$1$2";
223         push @$generated, {filename => $1, command => $3};
224         $line = '';
225         next;
226     }
227     push @$modules, $line;
228     $line = '';
229 }
230 #use Data::Dumper;
231 #print Dumper(\@scripts);
232
233 exit if $dump_opt;
234
235 ## Helper functions
236
237 sub generate_configs($$$) {
238     my ($base, $generated, $filename) = @_;
239     if ($base) { $base = "$base/"; };
240     foreach my $g(@$generated) {
241       if (exists $$g{content}) {
242         my $config = $$g{content};
243         my $fn = $$g{filename};
244         open(my $f, '>', $fn) || die("$fn: $!");
245         map { s|\brom://([^ ]*)|$rom_prefix$base$1|g; print $f "$_"; } @{$config};
246         close($f);
247         print "novaboot: Created $fn\n";
248       } elsif (exists $$g{command} && ! $no_file_gen) {
249         $ENV{SRCDIR} = dirname(File::Spec->rel2abs( $filename, $invocation_dir ));
250         system_verbose("( $$g{command} ) > $$g{filename}");
251       }
252     }
253 }
254
255 sub generate_grub_config($$$$;$)
256 {
257     my ($filename, $title, $base, $modules_ref, $preamble) = @_;
258     if ($base) { $base = "$base/"; };
259     open(my $fg, '>', $filename) or die "$filename: $!";
260     print $fg "$preamble\n" if $preamble;
261     print $fg "title $title\n" if $title;
262     #print $fg "root $base\n"; # root doesn't really work for (nd)
263     my $first = 1;
264     foreach (@$modules_ref) {
265         if ($first) {
266             $first = 0;
267             my ($kbin, $kcmd) = split(' ', $_, 2);
268             $kcmd = '' if !defined $kcmd;
269             print $fg "kernel ${base}$kbin $kcmd\n";
270         } else {
271             s|\brom://([^ ]*)|$rom_prefix$base$1|g; # Translate rom:// files - needed for vdisk parameter of sigma0
272             print $fg "module $base$_\n";
273         }
274     }
275     close($fg);
276     print("novaboot: Created $builddir/$filename\n");
277     return $filename;
278 }
279
280 sub generate_grub2_config($$$$;$$)
281 {
282     my ($filename, $title, $base, $modules_ref, $preamble, $prolog) = @_;
283     if ($base && substr($base,-1,1) ne '/') { $base = "$base/"; };
284     open(my $fg, '>', $filename) or die "$filename: $!";
285     print $fg "$preamble\n" if $preamble;
286     $title ||= 'novaboot';
287     print $fg "menuentry $title {\n";
288     print $fg "$prolog\n" if $prolog;
289     my $first = 1;
290     foreach (@$modules_ref) {
291         if ($first) {
292             $first = 0;
293             my ($kbin, $kcmd) = split(' ', $_, 2);
294             $kcmd = '' if !defined $kcmd;
295             print $fg "  multiboot ${base}$kbin $kcmd\n";
296         } else {
297             my @args = split;
298             # GRUB2 doesn't pass filename in multiboot info so we have to duplicate it here
299             $_ = join(' ', ($args[0], @args));
300             s|\brom://|$rom_prefix|g; # We do not need to translate path for GRUB2
301             print $fg "  module $base$_\n";
302         }
303     }
304     print $fg "}\n";
305     close($fg);
306     print("novaboot: Created $builddir/$filename\n");
307     return $filename;
308 }
309
310 sub generate_pulsar_config($$)
311 {
312     my ($filename, $modules_ref) = @_;
313     open(my $fg, '>', $filename) or die "$filename: $!";
314     print $fg "root $pulsar_root\n" if defined $pulsar_root;
315     my $first = 1;
316     my ($kbin, $kcmd);
317     foreach (@$modules_ref) {
318         if ($first) {
319             $first = 0;
320             ($kbin, $kcmd) = split(' ', $_, 2);
321             $kcmd = '' if !defined $kcmd;
322         } else {
323             my @args = split;
324             s|\brom://|$rom_prefix|g;
325             print $fg "load $_\n";
326         }
327     }
328     # Put kernel as last - this is needed for booting Linux and has no influence on non-Linux OSes
329     print $fg "exec $kbin $kcmd\n";
330     close($fg);
331     print("novaboot: Created $builddir/$filename\n");
332     return $filename;
333 }
334
335 sub shell_cmd_string(@)
336 {
337     return join(' ', map((/^[-_=a-zA-Z0-9\/\.\+]+$/ ? "$_" : "'$_'"), @_));
338 }
339
340 sub exec_verbose(@)
341 {
342     print "novaboot: Running: ".shell_cmd_string(@_)."\n";
343     exec(@_);
344 }
345
346 sub system_verbose($)
347 {
348     my $cmd = shift;
349     print "novaboot: Running: $cmd\n";
350     my $ret = system($cmd);
351     if ($ret & 0x007f) { die("Command terminated by a signal"); }
352     if ($ret & 0xff00) {die("Command exit with non-zero exit code"); }
353     if ($ret) { die("Command failure $ret"); }
354 }
355
356 ## WvTest handline
357
358 if (exists $variables->{WVDESC}) {
359     print "Testing \"$variables->{WVDESC}\" in $last_fn:\n";
360 } elsif ($last_fn =~ /\.wv$/) {
361     print "Testing \"all\" in $last_fn:\n";
362 }
363
364 ## Connect to the target and check whether is not occupied
365
366 # We have to do this before file generation phase, because file
367 # generation is intermixed with file deployment phase and we want to
368 # check whether the target is not used by somebody else before
369 # deploying files. Otherwise, we may rewrite other user's files on a
370 # boot server.
371
372 my $exp; # Expect object to communicate with the target over serial line
373
374 my ($target_reset, $target_power_on, $target_power_off);
375
376 if (defined $iprelay) {
377     my $IPRELAY;
378     $iprelay =~ /([.0-9]+)(:([0-9]+))?/;
379     my $addr = $1;
380     my $port = $3 || 23;
381     my $paddr   = sockaddr_in($port, inet_aton($addr));
382     my $proto   = getprotobyname('tcp');
383     socket($IPRELAY, PF_INET, SOCK_STREAM, $proto)  || die "socket: $!";
384     print "novaboot: Connecting to IP relay... ";
385     connect($IPRELAY, $paddr)    || die "connect: $!";
386     print "done\n";
387     $exp = Expect->init(\*$IPRELAY);
388     $exp->log_stdout(1);
389
390     while (1) {
391         print $exp "\xFF\xF6";  # AYT
392         my $connected = $exp->expect(20, # Timeout in seconds
393                                      '<iprelayd: connected>',
394                                      '-re', '<WEB51 HW[^>]*>');
395         last if $connected;
396     }
397
398     sub relaycmd($$) {
399         my ($relay, $onoff) = @_;
400         die unless ($relay == 1 || $relay == 2);
401
402         my $cmd = ($relay == 1 ? 0x5 : 0x6) | ($onoff ? 0x20 : 0x10);
403         return "\xFF\xFA\x2C\x32".chr($cmd)."\xFF\xF0";
404     }
405
406     sub relayconf($$) {
407         my ($relay, $onoff) = @_;
408         die unless ($relay == 1 || $relay == 2);
409         my $cmd = ($relay == 1 ? 0xdf : 0xbf) | ($onoff ? 0x00 : 0xff);
410         return "\xFF\xFA\x2C\x97".chr($cmd)."\xFF\xF0";
411     }
412
413     sub relay($$;$) {
414         my ($relay, $onoff, $can_giveup) = @_;
415         my $confirmation = '';
416         $exp->log_stdout(0);
417         print $exp relaycmd($relay, $onoff);
418         my $confirmed = $exp->expect(20, # Timeout in seconds
419                                      relayconf($relay, $onoff));
420         if (!$confirmed) {
421             if ($can_giveup) {
422                 print("Relay confirmation timeout - ignoring\n");
423             } else {
424                 die "Relay confirmation timeout";
425             }
426         }
427         $exp->log_stdout(1);
428     }
429
430     $target_reset = sub {
431         relay(2, 1, 1); # Reset the machine
432         usleep(100000);
433         relay(2, 0);
434     };
435
436     $target_power_off = sub {
437         relay(1, 1);            # Press power button
438         usleep(6000000);        # Long press to switch off
439         relay(1, 0);
440     };
441
442     $target_power_on = sub {
443         relay(1, 1);            # Press power button
444         usleep(100000);         # Short press
445         relay(1, 0);
446     };
447 }
448 elsif ($serial) {
449     my $CONN;
450     system_verbose("stty -F $serial $stty");
451     open($CONN, "+<", $serial) || die "open $serial: $!";
452     $exp = Expect->init(\*$CONN);
453 }
454 else {
455     $exp = new Expect(); # Make $exp ready for calling $exp->spawn() later
456 }
457
458 if (defined $on_opt && defined $target_power_on) {
459     &$target_power_on();
460     exit;
461 }
462 if (defined $off_opt && defined $target_power_off) {
463     print "novaboot: Switching the target off...\n";
464     &$target_power_off();
465     exit;
466 }
467
468 $builddir ||= dirname(File::Spec->rel2abs( ${$scripts[0]}{filename})) if scalar @scripts;
469 if (defined $builddir) {
470     chdir($builddir) or die "Can't change directory to $builddir: $!";
471     print "novaboot: Entering directory `$builddir'\n";
472 }
473
474 ## File generation phase
475 my (%files_iso, $menu_iso, $filename);
476 my $config_name = '';
477
478 foreach my $script (@scripts) {
479     $filename = $$script{filename};
480     $modules = $$script{modules};
481     $generated = $$script{generated};
482     $variables = $$script{variables};
483
484     ($config_name = $filename) =~ s#.*/##;
485     $config_name = $config_name_opt if (defined $config_name_opt);
486
487     if (exists $variables->{BUILDDIR}) {
488         $builddir = File::Spec->rel2abs($variables->{BUILDDIR});
489         chdir($builddir) or die "Can't change directory to $builddir: $!";
490         print "novaboot: Entering directory `$builddir'\n";
491     }
492
493     my $kernel;
494     if (exists $variables->{KERNEL}) {
495         $kernel = $variables->{KERNEL};
496     } else {
497         if ($CFG::hypervisor) {
498             $kernel = $CFG::hypervisor . " ";
499             if (exists $variables->{HYPERVISOR_PARAMS}) {
500                 $kernel .= $variables->{HYPERVISOR_PARAMS};
501             } else {
502                 $kernel .= $CFG::hypervisor_params;
503             }
504         }
505     }
506     @$modules = ($kernel, @$modules) if $kernel;
507     @$modules = (@chainloaders, @$modules);
508     @$modules = ("bin/boot/bender", @$modules) if ($bender || defined $ENV{'NOVABOOT_BENDER'});
509
510     my $prefix;
511     ($prefix = $grub_prefix) =~ s/\$NAME/$config_name/ if defined $grub_prefix;
512     $prefix ||= $builddir;
513     # TODO: use $grub_prefix as first parameter if some switch is given
514     generate_configs('', $generated, $filename);
515
516 ### Generate bootloader configuration files
517     my @bootloader_configs;
518     push @bootloader_configs, generate_grub_config($grub_config, $config_name, $prefix, $modules, $grub_preamble) if (defined $grub_config);
519     push @bootloader_configs, generate_grub2_config($grub2_config, $config_name, $prefix, $modules, $grub_preamble, $grub2_prolog) if (defined $grub2_config);
520     push @bootloader_configs, generate_pulsar_config('config-'.($pulsar||'novaboot'), $modules) if (defined $pulsar);
521
522 ### Run scons
523     if (defined $scons) {
524         my @files = map({ ($file) = m/([^ ]*)/; $file; } @$modules);
525         # Filter-out generated files
526         my @to_build = grep({ my $file = $_; !scalar(grep($file eq $$_{filename}, @$generated)) } @files);
527         system_verbose($scons || $CFG::scons." ".join(" ", @to_build));
528     }
529
530 ### Copy files (using rsync)
531     if (defined $server && !defined($gen_only)) {
532         (my $real_server = $server) =~ s/\$NAME/$config_name/;
533
534         my ($hostname, $path) = split(":", $real_server, 2);
535         if (! defined $path) {
536             $path = $hostname;
537             $hostname = "";
538         }
539         my $files = join(" ", map({ ($file) = m/([^ ]*)/; $file; } ( @$modules, @bootloader_configs)));
540         map({ my $file = (split)[0]; die "$file: $!" if ! -f $file; } @$modules);
541         my $istty = -t STDOUT && ($ENV{'TERM'} || 'dumb') ne 'dumb';
542         my $progress = $istty ? "--progress" : "";
543         system_verbose("rsync $progress -RLp $rsync_flags $files $real_server");
544         if ($server =~ m|/\$NAME$| && $concat) {
545             my $cmd = join("; ", map { "( cd $path/.. && cat */$_ > $_ )" } @bootloader_configs);
546             system_verbose($hostname ? "ssh $hostname '$cmd'" : $cmd);
547         }
548     }
549
550 ### Prepare ISO image generation
551     if (defined $iso_image) {
552         generate_configs("(cd)", $generated, $filename);
553         my $menu;
554         generate_grub_config(\$menu, $config_name, "(cd)", $modules);
555         $menu_iso .= "$menu\n";
556         map { ($file,undef) = split; $files_iso{$file} = 1; } @$modules;
557     }
558 }
559
560 ## Generate ISO image
561 if (defined $iso_image) {
562     open(my $fh, ">menu-iso.lst");
563     print $fh "timeout 5\n\n$menu_iso";
564     close($fh);
565     my $files = "boot/grub/menu.lst=menu-iso.lst " . join(" ", map("$_=$_", keys(%files_iso)));
566     $iso_image ||= "$config_name.iso";
567     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");
568     print("ISO image created: $builddir/$iso_image\n");
569 }
570
571 exit(0) if defined $gen_only;
572
573 ## Boot the system using various methods and send serial output to stdout
574
575 if (scalar(@scripts) > 1 && ( defined $dhcp_tftp || defined $serial || defined $iprelay)) {
576     die "You cannot do this with multiple scripts simultaneously";
577 }
578
579 if ($variables->{WVTEST_TIMEOUT}) {
580     print "wvtest: timeout ", $variables->{WVTEST_TIMEOUT}, "\n";
581 }
582
583 sub trim($) {
584     my ($str) = @_;
585     $str =~ s/^\s+|\s+$//g;
586     return $str
587 }
588
589 ### Qemu
590
591 if (!(defined $dhcp_tftp || defined $serial || defined $iprelay || defined $server || defined $iso_image)) {
592     # Qemu
593     $qemu ||= $variables->{QEMU} || $CFG::qemu;
594     my @qemu_flags = split(" ", $qemu);
595     $qemu = shift(@qemu_flags);
596
597     @qemu_flags = split(/ +/, trim($variables->{QEMU_FLAGS})) if exists $variables->{QEMU_FLAGS};
598     @qemu_flags = split(/ +/, trim($qemu_flags_cmd)) if $qemu_flags_cmd;
599     push(@qemu_flags, split(/ +/, trim($qemu_append || '')));
600
601     if (defined $iso_image) {
602         # Boot NOVA with grub (and test the iso image)
603         push(@qemu_flags, ('-cdrom', "$config_name.iso"));
604     } else {
605         # Boot NOVA without GRUB
606
607         # Non-patched qemu doesn't like commas, but NUL can live with pluses instead of commans
608         foreach (@$modules) {s/,/+/g;}
609         generate_configs("", $generated, $filename);
610
611         if (scalar @$modules) {
612             my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);
613             $kcmd = '' if !defined $kcmd;
614             my $dtb;
615             @$modules = map { if (/\.dtb$/) { $dtb=$_; (); } else { $_ } } @$modules;
616             my $initrd = join ",", @$modules;
617
618             push(@qemu_flags, ('-kernel', $kbin, '-append', $kcmd));
619             push(@qemu_flags, ('-initrd', $initrd)) if $initrd;
620             push(@qemu_flags, ('-dtb', $dtb)) if $dtb;
621         }
622     }
623     push(@qemu_flags,  qw(-serial stdio)); # Redirect serial output (for collecting test restuls)
624     unshift(@qemu_flags, ('-name', $config_name));
625     print "novaboot: Running: ".shell_cmd_string($qemu, @qemu_flags)."\n";
626     $exp->spawn(($qemu, @qemu_flags)) || die("exec() failed: $!");
627 }
628
629 ### Local DHCPD and TFTPD
630
631 my ($dhcpd_pid, $tftpd_pid);
632
633 if (defined $dhcp_tftp)
634 {
635     generate_configs("(nd)", $generated, $filename);
636     system_verbose('mkdir -p tftpboot');
637     generate_grub_config("tftpboot/os-menu.lst", $config_name, "(nd)", \@$modules, "timeout 0");
638     open(my $fh, '>', 'dhcpd.conf');
639     my $mac = `cat /sys/class/net/eth0/address`;
640     chomp $mac;
641     print $fh "subnet 10.23.23.0 netmask 255.255.255.0 {
642                       range 10.23.23.10 10.23.23.100;
643                       filename \"bin/boot/grub/pxegrub.pxe\";
644                       next-server 10.23.23.1;
645 }
646 host server {
647         hardware ethernet $mac;
648         fixed-address 10.23.23.1;
649 }";
650     close($fh);
651     system_verbose("sudo ip a add 10.23.23.1/24 dev eth0;
652             sudo ip l set dev eth0 up;
653             sudo touch dhcpd.leases");
654
655     # We run servers by forking ourselves, because the servers end up
656     # in our process group and get killed by signals sent to the
657     # process group (e.g. Ctrl-C on terminal).
658     $dhcpd_pid = fork();
659     exec_verbose("sudo dhcpd -d -cf dhcpd.conf -lf dhcpd.leases -pf dhcpd.pid") if ($dhcpd_pid == 0);
660     $tftpd_pid = fork();
661     exec_verbose("sudo in.tftpd --foreground --secure -v -v -v --pidfile tftpd.pid $builddir") if ($tftpd_pid == 0);
662
663     # Kill server when we die
664     $SIG{__DIE__} = sub { system_verbose('sudo pkill --pidfile=dhcpd.pid');
665                           system_verbose('sudo pkill --pidfile=tftpd.pid'); };
666 }
667
668 ### Serial line or IP relay
669
670 if (defined $target_reset) {
671     print "novaboot: Reseting the test box... ";
672     &$target_reset();
673     print "done\n";
674 }
675
676 if (defined $uboot) {
677     print "novaboot: Waiting for uBoot prompt...\n";
678     $exp->log_stdout(1);
679     $exp->expect(20,
680                  [qr/Hit any key to stop autoboot:/, sub { $exp->send('.'); exp_continue; }],
681                  '=> ') || die "No uBoot prompt deteceted";
682
683     $exp->send("dhcp\n");
684     $exp->expect(1, '=> ');
685
686     my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);
687     my $dtb;
688     @$modules = map { print "DTBBBB $_\n"; if (/\.dtb$/) { $dtb=$_; (); } else { $_ } } @$modules;
689     my $initrd = shift @$modules;
690
691     my $kern_addr = '800000';
692     my $initrd_addr = '-';
693     my $dtb_addr = '';
694
695     sleep(1);
696     $exp->send("tftp $kern_addr $kbin\n");
697     $exp->expect(1,
698                  [qr/#/, sub { exp_continue; }],
699                  '=> ') || die "Kernel load failed";
700     if (defined $dtb) {
701         $dtb_addr = '7f0000';
702         $exp->send("tftp $dtb_addr $dtb\n");
703         $exp->expect(1,
704                      [qr/#/, sub { exp_continue; }],
705                      '=> ') || die "Device tree load failed";
706     }
707     if (defined $initrd) {
708         $initrd_addr = 'b00000';
709         $exp->send("tftp $initrd_addr $initrd\n");
710         $exp->expect(1,
711                      [qr/#/, sub { exp_continue; }],
712                      '=> ') || die "Initrd load failed";
713     }
714     $exp->send("mw f0000b00 \${psc_cfg}\n");
715     $exp->send("set bootargs $kcmd\n");
716     $exp->expect(1, '=> ');
717     $exp->send("bootm $kern_addr $initrd_addr $dtb_addr\n");
718 }
719
720 if (defined $exp) {
721     # Serial line of the target is available
722     print "novaboot: Serial line interaction (press Ctrl-C to interrupt)...\n";
723     $exp->log_stdout(1);
724     my @inputs = ($exp);
725     if (-t STDIN) { # Set up bi-directional communication if we run on terminal
726         my $infile = new IO::File;
727         $infile->IO::File::fdopen(*STDIN,'r');
728         my $in_object = Expect->exp_init($infile);
729         $in_object->set_group($exp);
730         #$in_object->set_seq("\cC",undef);
731
732         # I'm not sure when to use raw mode and when not. With
733         # --dhcp-tftp, I want the output of daemons to be normally
734         # formated (no raw mode). On the other hand, I don't want
735         # input for qemu to be echoed. Need to think more about this.
736         $in_object->manual_stty(1);
737         push(@inputs, $in_object);
738     }
739     Expect::interconnect(@inputs);
740 }
741
742 ## Kill dhcpc or tftpd
743 if (defined $dhcp_tftp) {
744     die("novaboot: This should kill servers on background\n");
745 }
746
747 ## Documentation
748
749 =head1 NAME
750
751 novaboot - A tool for booting various operating systems on various hardware or in qemu
752
753 =head1 SYNOPSIS
754
755 B<novaboot> [ options ] [--] script...
756
757 B<./script> [ options ]
758
759 B<novaboot> --help
760
761 =head1 DESCRIPTION
762
763 This program makes it easier to boot NOVA or other operating system
764 (OS) in different environments. It reads a so called novaboot script
765 and uses it either to boot the OS in an emulator (e.g. in qemu) or to
766 generate the configuration for a specific bootloader and optionally to
767 copy the necessary binaries and other needed files to proper
768 locations, perhaps on a remote server. In case the system is actually
769 booted, its serial output is redirected to standard output if that is
770 possible.
771
772 A typical way of using novaboot is to make the novaboot script
773 executable and set its first line to I<#!/usr/bin/env novaboot>. Then,
774 booting a particular OS configuration becomes the same as executing a
775 local program - the novaboot script.
776
777 With C<novaboot> you can:
778
779 =over 3
780
781 =item 1.
782
783 Run an OS in Qemu. This is the default action when no other action is
784 specified by command line switches. Thus running C<novaboot ./script>
785 (or C<./script> as described above) will run Qemu and make it boot the
786 configuration specified in the I<script>.
787
788 =item 2.
789
790 Create a bootloader configuration file (currently supported
791 bootloaders are GRUB, GRUB2, Pulsar and uBoot) and copy it with all
792 other files needed for booting to another, perhaps remote, location.
793
794  ./script --server --iprelay=192.168.1.2
795
796 This command copies files to a TFTP server specified in the
797 configuration file and uses TCP/IP-controlled relay to reset the test
798 box and receive its serial output.
799
800 =item 3.
801
802 Run DHCP and TFTP server on developer's machine to PXE-boot NOVA from
803 it. E.g.
804
805  ./script --dhcp-tftp
806
807 When a PXE-bootable machine is connected via Ethernet to developer's
808 machine, it will boot the configuration described in I<script>.
809
810 =item 4.
811
812 Create bootable ISO images. E.g.
813
814  novaboot --iso -- script1 script2
815
816 The created ISO image will have GRUB bootloader installed on it and
817 the boot menu will allow selecting between I<script1> and I<script2>
818 configurations.
819
820 =back
821
822 =head1 PHASES AND OPTIONS
823
824 Novaboot performs its work in several phases. Each phase can be
825 influenced by several options, certain phases can be skipped. The list
826 of phases (in the execution order) and the corresponding options
827 follows.
828
829 =head2 Configuration reading phase
830
831 After starting, novaboot reads configuration files. By default, it
832 searches for files named F<.novaboot> starting from the directory of
833 the novaboot script (or working directory, see bellow) and continuing
834 upwards up to the root directory. The configuration files are read in
835 order from the root directory downwards with latter files overriding
836 settings from the former ones.
837
838 In certain cases, the location of the novaboot script cannot be
839 determined in this early phase. This happens either when the script is
840 read from the standard input or when novaboot is invoked explicitly
841 and options precede the script name, as in the example L</"4."> above.
842 In this case the current working directory is used as a starting point
843 for configuration file search.
844
845 =over 8
846
847 =item -c, --config=<filename>
848
849 Use the specified configuration file instead of the default one(s).
850
851 =back
852
853 =head2 Command line processing phase
854
855 =over 8
856
857 =item --dump-config
858
859 Dump the current configuration to stdout end exits. Useful as an
860 initial template for a configuration file.
861
862 =item -h, --help
863
864 Print short (B<-h>) or long (B<--help>) help.
865
866 =item -t, --target=<target>
867
868 This option serves as a user configurable shortcut for other novaboot
869 options. The effect of this option is the same as the options stored
870 in the C<%targets> configuration variable under key I<target>. See
871 also L</"CONFIGURATION FILE">.
872
873 =back
874
875 =head2 Script preprocessing phase
876
877 This phases allows to modify the parsed novaboot script before it is
878 used in the later phases.
879
880 =over 8
881
882 =item -a, --append=<parameters>
883
884 Appends a string to the first "filename" line in the novaboot script.
885 This can be used to append parameters to the kernel's or root task's
886 command line.
887
888 =item -b, --bender
889
890 Use F<bender> chainloader. Bender scans the PCI bus for PCI serial
891 ports and stores the information about them in the BIOS data area for
892 use by the kernel.
893
894 =item --chainloader=<chainloader>
895
896 Chainloader that is loaded before the kernel and other files specified
897 in the novaboot script. E.g. 'bin/boot/bender promisc'.
898
899 =item --dump
900
901 Prints the content of the novaboot script after removing comments and
902 evaluating all I<--scriptmod> expressions. Exit after reading (and
903 dumping) the script.
904
905 =item --scriptmod=I<perl expression>
906
907 When novaboot script is read, I<perl expression> is executed for every
908 line (in $_ variable). For example, C<novaboot
909 --scriptmod=s/sigma0/omega6/g> replaces every occurrence of I<sigma0>
910 in the script with I<omega6>.
911
912 When this option is present, it overrides I<$script_modifier> variable
913 from the configuration file, which has the same effect. If this option
914 is given multiple times all expressions are evaluated in the command
915 line order.
916
917 =item --strip-rom
918
919 Strip I<rom://> prefix from command lines and generated config files.
920 The I<rom://> prefix is used by NUL. For NRE, it has to be stripped.
921
922 =back
923
924 =head2 File generation phase
925
926 In this phase, files needed for booting are generated in a so called
927 I<build directory> (see TODO). In most cases configuration for a
928 bootloader is generated automatically by novaboot. It is also possible
929 to generate other files using I<heredoc> or I<"<"> syntax in novaboot
930 scripts. Finally, binaries can be generated in this phases by running
931 C<scons> or C<make>.
932
933 =over 8
934
935 =item --build-dir=<directory>
936
937 Overrides the default build directory location.
938
939 The default build directory location is determined as follows: If the
940 configuration file defines the C<$builddir> variable, its value is
941 used. Otherwise, it is the directory that contains the first processed
942 novaboot script.
943
944 =item -g, --grub[=I<filename>]
945
946 Generates grub bootloader menu file. If the I<filename> is not
947 specified, F<menu.lst> is used. The I<filename> is relative to the
948 build directory (see B<--build-dir>).
949
950 =item --grub-preamble=I<prefix>
951
952 Specifies the I<preable> that is at the beginning of the generated
953 GRUB or GRUB2 config files. This is useful for specifying GRUB's
954 timeout.
955
956 =item --grub-prefix=I<prefix>
957
958 Specifies I<prefix> that is put in front of every file name in GRUB's
959 F<menu.lst>. The default value is the absolute path to the build directory.
960
961 If the I<prefix> contains string $NAME, it will be replaced with the
962 name of the novaboot script (see also B<--name>).
963
964 =item --grub2[=I<filename>]
965
966 Generate GRUB2 menuentry in I<filename>. If I<filename> is not
967 specified F<grub.cfg> is used. The content of the menuentry can be
968 customized with B<--grub-preable>, B<--grub2-prolog> or
969 B<--grub_prefix> options.
970
971 In order to use the the generated menuentry on your development
972 machine that uses GRUB2, append the following snippet to
973 F</etc/grub.d/40_custom> file and regenerate your grub configuration,
974 i.e. run update-grub on Debian/Ubuntu.
975
976   if [ -f /path/to/nul/build/grub.cfg ]; then
977     source /path/to/nul/build/grub.cfg
978   fi
979
980 =item --grub2-prolog=I<prolog>
981
982 Specifies text I<preable> that is put at the begiging of the entry
983 GRUB2 entry.
984
985 =item --name=I<string>
986
987 Use the name I<string> instead of the name of the novaboot script.
988 This name is used for things like a title of grub menu or for the
989 server directory where the boot files are copied to.
990
991 =item --no-file-gen
992
993 Do not generate files on the fly (i.e. "<" syntax) except for the
994 files generated via "<<WORD" syntax.
995
996 =item -p, --pulsar[=mac]
997
998 Generates pulsar bootloader configuration file named F<config-I<mac>>
999 The I<mac> string is typically a MAC address and defaults to
1000 I<novaboot>.
1001
1002 =item --scons[=scons command]
1003
1004 Runs I<scons> to build files that are not generated by novaboot
1005 itself.
1006
1007 =item --gen-only
1008
1009 Exit novaboot after file generation phase.
1010
1011 =back
1012
1013 =head2 Target connection check
1014
1015 If supported by the target, the connection to it is made and it is
1016 checked whether the target is not occupied by another novaboot
1017 user/instance.
1018
1019 =head2 File deployment phase
1020
1021 In some setups, it is necessary to copy the files needed for booting
1022 to a particular location, e.g. to a TFTP boot server or to the
1023 F</boot> partition.
1024
1025 =over 8
1026
1027 =item -d, --dhcp-tftp
1028
1029 Turns your workstation into a DHCP and TFTP server so that NOVA
1030 can be booted via PXE BIOS on a test machine directly connected by
1031 a plain Ethernet cable to your workstation.
1032
1033 The DHCP and TFTP servers require root privileges and C<novaboot>
1034 uses C<sudo> command to obtain those. You can put the following to
1035 I</etc/sudoers> to allow running the necessary commands without
1036 asking for password.
1037
1038  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
1039  your_login ALL=NOPASSWD: NOVABOOT
1040
1041 =item -i, --iso[=filename]
1042
1043 Generates the ISO image that boots NOVA system via GRUB. If no filename
1044 is given, the image is stored under I<NAME>.iso, where I<NAME> is the name
1045 of the novaboot script (see also B<--name>).
1046
1047 =item --server[=[[user@]server:]path]
1048
1049 Copy all files needed for booting to another location (implies B<-g>
1050 unless B<--grub2> is given). The files will be copied (by B<rsync>
1051 tool) to the directory I<path>. If the I<path> contains string $NAME,
1052 it will be replaced with the name of the novaboot script (see also
1053 B<--name>).
1054
1055 =item --concat
1056
1057 If B<--server> is used and its value ends with $NAME, then after
1058 copying the files, a new bootloader configuration file (e.g. menu.lst)
1059 is created at I<path-wo-name>, i.e. the path specified by B<--server>
1060 with $NAME part removed. The content of the file is created by
1061 concatenating all files of the same name from all subdirectories of
1062 I<path-wo-name> found on the "server".
1063
1064 =item --rsync-flags=I<flags>
1065
1066 Specifies which I<flags> are appended to F<rsync> command line when
1067 copying files as a result of I<--server> option.
1068
1069 =back
1070
1071 =head2 Target power-on and reset phase
1072
1073 =over 8
1074
1075 =item --iprelay=I<addr[:port]>
1076
1077 Use IP relay to reset the machine and to get the serial output. The IP
1078 address of the relay is given by I<addr> parameter.
1079
1080 Note: This option is expected to work with HWG-ER02a IP relays.
1081
1082 =item --on, --off
1083
1084 Switch on/off the target machine. Currently works only with
1085 B<--iprelay>.
1086
1087 =item -Q, --qemu=I<qemu-binary>
1088
1089 The name of qemu binary to use. The default is 'qemu'.
1090
1091 =item --qemu-append=I<flags>
1092
1093 Append I<flags> to the default qemu flags (QEMU_FLAGS variable or
1094 C<-cpu coreduo -smp 2>).
1095
1096 =item -q, --qemu-flags=I<flags>
1097
1098 Replace the default qemu flags (QEMU_FLAGS variable or C<-cpu coreduo
1099 -smp 2>) with I<flags> specified here.
1100
1101 =back
1102
1103 =head2 Interaction with the bootloader on the target
1104
1105 See B<--serial>. There will be new options soon.
1106
1107 =over 8
1108
1109 =item --uboot
1110
1111 Interact with uBoot bootloader to boot the thing described in the
1112 novaboot script. Implementation of this option is currently tied to a
1113 particular board that we use. It may be subject to changes in the
1114 future!
1115
1116 =back
1117
1118 =head2 Target's output reception phase
1119
1120 =over 8
1121
1122 =item -s, --serial[=device]
1123
1124 Use serial line to control GRUB bootloader and to see the output
1125 serial output of the machine. The default value is F</dev/ttyUSB0>.
1126
1127 =item --stty=<settings>
1128
1129 Specifies settings passed to C<stty> invoked on the serial line
1130 specified with B<--serial>. If this option is not given, C<stty> is
1131 called with C<raw -crtscts -onlcr 115200> settings.
1132
1133 =back
1134
1135 See also B<--iprelay>.
1136
1137 =head2 Termination phase
1138
1139 Daemons that were spwned (F<dhcpd> and F<tftpd>) are killed here.
1140
1141 =head1 NOVABOOT SCRIPT SYNTAX
1142
1143 The syntax tries to mimic POSIX shell syntax. The syntax is defined with the following rules.
1144
1145 Lines starting with "#" are ignored.
1146
1147 Lines that end with "\" are concatenated with the following line after
1148 removal of the final "\" and leading whitespace of the following line.
1149
1150 Lines in the form I<VARIABLE=...> (i.e. matching '^[A-Z_]+=' regular
1151 expression) assign values to internal variables. See VARIABLES
1152 section.
1153
1154 Otherwise, the first word on the line represents the filename
1155 (relative to the build directory (see B<--build-dir>) of the module to
1156 load and the remaining words are passed as the command line
1157 parameters.
1158
1159 When the line ends with "<<WORD" then the subsequent lines until the
1160 line containing only WORD are copied literally to the file named on
1161 that line.
1162
1163 When the line ends with "< CMD" the command CMD is executed with
1164 C</bin/sh> and its standard output is stored in the file named on that
1165 line. The SRCDIR variable in CMD's environment is set to the absolute
1166 path of the directory containing the interpreted novaboot script.
1167
1168 Example:
1169   #!/usr/bin/env novaboot
1170   WVDESC=Example program
1171   bin/apps/sigma0.nul S0_DEFAULT script_start:1,1 \
1172     verbose hostkeyb:0,0x60,1,12,2
1173   bin/apps/hello.nul
1174   hello.nulconfig <<EOF
1175   sigma0::mem:16 name::/s0/log name::/s0/timer name::/s0/fs/rom ||
1176   rom://bin/apps/hello.nul
1177   EOF
1178
1179 This example will load three modules: sigma0.nul, hello.nul and
1180 hello.nulconfig. sigma0 gets some command line parameters and
1181 hello.nulconfig file is generated on the fly from the lines between
1182 <<EOF and EOF.
1183
1184 =head2 VARIABLES
1185
1186 The following variables are interpreted in the novaboot script:
1187
1188 =over 8
1189
1190 =item BUILDDIR
1191
1192 Novaboot chdir()s to this directory before file generation phase. The
1193 directory name specified here is relative to the build directory
1194 specified by other means (see L</--build-dir>).
1195
1196 =item WVDESC
1197
1198 Description of the wvtest-compliant program.
1199
1200 =item WVTEST_TIMEOUT
1201
1202 The timeout in seconds for WvTest harness. If no complete line appears
1203 in the test output within the time specified here, the test fails. It
1204 is necessary to specify this for long running tests that produce no
1205 intermediate output.
1206
1207 =item QEMU
1208
1209 Use a specific qemu binary (can be overriden with B<-Q>) and flags
1210 when booting this script under qemu. If QEMU_FLAGS variable is also
1211 specified flags specified in QEMU variable are replaced by those in
1212 QEMU_FLAGS.
1213
1214 =item QEMU_FLAGS
1215
1216 Use specific qemu flags (can be overriden with B<-q>).
1217
1218 =item HYPERVISOR_PARAMS
1219
1220 Parameters passed to hypervisor. The default value is "serial", unless
1221 overriden in configuration file.
1222
1223 =item KERNEL
1224
1225 The kernel to use instead of NOVA hypervisor specified in the
1226 configuration file. The value should contain the name of the kernel
1227 image as well as its command line parameters. If this variable is
1228 defined and non-empty, the variable HYPERVISOR_PARAMS is not used.
1229
1230 =back
1231
1232 =head1 CONFIGURATION FILE
1233
1234 Novaboot can read its configuration from a file. Configuration file
1235 was necessary in early days of novaboot. Nowadays, an attempt is made
1236 to not use the configuration file because it makes certain novaboot
1237 scripts unusable on systems without (or with different) configuration
1238 file. The only recommended use of the configuration file is to specify
1239 custom_options (see bellow).
1240
1241 If you decide to use the configuration file, it is looked up, by
1242 default, in files named F<.novaboot> as described in L</Configuration
1243 reading phase>. Alternatively, its location can be specified with the
1244 B<-c> switch or with the NOVABOOT_CONFIG environment variable. The
1245 configuration file has perl syntax and should set values of certain
1246 Perl variables. The current configuration can be dumped with the
1247 B<--dump-config> switch. Some configuration variables can be overriden
1248 by environment variables (see below) or by command line switches.
1249
1250 Documentation of some configuration variables follows:
1251
1252 =over 8
1253
1254 =item $builddir
1255
1256 Build directory location relative to the location of the configuration
1257 file.
1258
1259 =item %targets
1260
1261 Hash of shortcuts to be used with the B<--target> option. If the hash
1262 contains, for instance, the following pair of values
1263
1264  'mybox' => '--server=boot:/tftproot --serial=/dev/ttyUSB0 --grub',
1265
1266 then the following two commands are equivalent:
1267
1268  ./script --server=boot:/tftproot --serial=/dev/ttyUSB0 --grub
1269  ./script -t mybox
1270
1271 =back
1272
1273 =head1 ENVIRONMENT VARIABLES
1274
1275 Some options can be specified not only via config file or command line
1276 but also through environment variables. Environment variables override
1277 the values from configuration file and command line parameters
1278 override the environment variables.
1279
1280 =over 8
1281
1282 =item NOVABOOT_CONFIG
1283
1284 Name of the novaboot configuration file to use instead of the default
1285 one(s).
1286
1287 =item NOVABOOT_BENDER
1288
1289 Defining this variable has the same meaning as B<--bender> option.
1290
1291 =back
1292
1293 =head1 AUTHORS
1294
1295 Michal Sojka <sojka@os.inf.tu-dresden.de>