# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
+## Initialization
+
use strict;
use warnings;
use warnings (exists $ENV{NOVABOOT_TEST} ? (FATAL => 'all') : ());
-use Getopt::Long qw(GetOptionsFromString);
+use Getopt::Long qw(GetOptionsFromString GetOptionsFromArray);
use Pod::Usage;
use File::Basename;
use File::Spec;
use Socket;
use FileHandle;
use IPC::Open2;
-use POSIX qw(:errno_h);
+use POSIX qw(:errno_h sysconf);
use Cwd qw(getcwd abs_path);
use Expect;
$CFG::hypervisor = "";
$CFG::hypervisor_params = "serial";
$CFG::genisoimage = "genisoimage";
-$CFG::qemu = 'qemu -cpu coreduo -smp 2';
-$CFG::default_target = 'qemu';
+$CFG::qemu = 'qemu-system-i386 -cpu coreduo -smp 2';
+$CFG::default_target = '';
%CFG::targets = (
'qemu' => '--qemu',
"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',
"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',
"localhost" => '--scriptmod=s/console=tty[A-Z0-9,]+// --server=/boot/novaboot/$NAME --grub2 --grub-prefix=/boot/novaboot/$NAME --grub2-prolog=" set root=\'(hd0,msdos1)\'"',
- "ryuglab" => '--server=pc-sojkam.felk.cvut.cz:/srv/tftp --uboot --uboot-init="mw f0000b00 \${psc_cfg}; sleep 1" --remote-cmd="ssh -t pc-sojkam.felk.cvut.cz \"cu -l /dev/ttyUSB0 -s 115200\"" --remote-expect="Connected." --reset-cmd="ssh -t pc-sojkam.felk.cvut.cz \"dtrrts /dev/ttyUSB0 1 1\""',
- "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"',
-
+ "ryu" => '--uboot --uboot-init="mw f0000b00 \${psc_cfg}; sleep 1" --uboot-addr kernel=800000 --uboot-addr ramdisk=b00000 --uboot-addr fdt=7f0000',
+ "ryuglab" => '--target ryu --server=pc-sojkam.felk.cvut.cz:/srv/tftp --remote-cmd="ssh -tt pc-sojkam.felk.cvut.cz \"sterm -d -s 115200 /dev/ttyUSB0\""',
+ "ryulocal" => '--target ryu --dhcp-tftp --serial --reset-cmd="if which dtrrts; then dtrrts $NB_SERIAL 0 1; sleep 0.1; dtrrts $NB_SERIAL 1 1; fi"',
);
-chomp(my $nproc = `nproc`);
-$CFG::scons = "scons -j$nproc";
-$CFG::make = "make -j$nproc";
+
+{
+ my %const;
+ $const{linux}->{_SC_NPROCESSORS_CONF} = 83;
+ my $nproc = sysconf($const{$^O}->{_SC_NPROCESSORS_CONF});
+
+ $CFG::scons = "scons -j$nproc";
+ $CFG::make = "make -j$nproc";
+}
my $builddir;
my ($cfg) = @_;
{
package CFG; # Put config data into a separate namespace
+
my $rc = do($cfg);
# Check for errors
my @dirs = File::Spec->splitdir($dir);
$dir = File::Spec->catdir(@dirs[0..$#dirs-1]);
}
+ @cfgs = reverse @cfgs;
+
+ my $xdg_config_home = $ENV{'XDG_CONFIG_HOME'} || $ENV{'HOME'}.'/.config';
+ unshift(@cfgs, "$xdg_config_home/novaboot") if -r "$xdg_config_home/novaboot";
+
+ $dir = $ENV{'NOVABOOT_CONFIG_DIR'} || '/etc/novaboot.d';
+ if (opendir(my $dh, $dir)) {
+ my @etccfg = map { "$dir/$_" } grep { /^[-_a-zA-Z0-9]+$/ && -f "$dir/$_" } readdir($dh);
+ closedir $dh;
+ @etccfg = sort(@etccfg);
+ @cfgs = ( @etccfg, @cfgs );
+ }
}
my $cfg = $ENV{'NOVABOOT_CONFIG'};
Getopt::Long::Configure(qw/no_ignore_case pass_through/);
GetOptions ("config|c=s" => \$cfg);
-read_config($_) foreach $cfg or reverse @cfgs;
+read_config($_) foreach $cfg or @cfgs;
## Command line handling
-my $explicit_target;
+my $explicit_target = $ENV{'NOVABOOT_TARGET'};
GetOptions ("target|t=s" => \$explicit_target);
-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);
+my ($amt, @append, $bender, @chainloaders, $concat, $config_name_opt, $dhcp_tftp, $dump_opt, $dump_config, @exiton, $exiton_timeout, @expect_raw, $final_eol, $gen_only, $grub_config, $grub_prefix, $grub_preamble, $grub2_prolog, $grub2_config, $help, $ider, $interaction, $iprelay, $iso_image, $interactive, $kernel_opt, $make, $man, $netif, $no_file_gen, $off_opt, $on_opt, $pulsar, $pulsar_root, $qemu, $qemu_append, $qemu_flags_cmd, $remote_cmd, $remote_expect, $remote_expect_silent, $reset, $reset_cmd, $rom_prefix, $rsync_flags, @scriptmod, $scons, $serial, $server, $stty, $tftp, $tftp_port, $uboot, %uboot_addr, $uboot_cmd, @uboot_init);
+# Default values of certain command line options
+%uboot_addr = (
+ 'kernel' => '${kernel_addr_r}',
+ 'ramdisk' => '${ramdisk_addr_r}',
+ 'fdt' => '${fdt_addr_r}',
+ );
$rsync_flags = '';
$rom_prefix = 'rom://';
$stty = 'raw -crtscts -onlcr 115200';
+$reset = 1; # Reset target by default
+$interaction = 1; # Perform target interaction by default
+$final_eol = 1;
+$netif = 'eth0';
my @expect_seen = ();
sub handle_expect
@expect_seen = ();
}
-Getopt::Long::Configure(qw/no_ignore_case no_pass_through/);
my %opt_spec;
%opt_spec = (
"amt=s" => \$amt,
"dump" => \$dump_opt,
"dump-config" => \$dump_config,
"exiton=s" => \@exiton,
+ "exiton-timeout=i"=> \$exiton_timeout,
+ "exiton-re=s" => sub { my ($n, $v) = @_; push(@exiton, '-re', $v); },
"expect=s" => \&handle_expect,
"expect-re=s" => \&handle_expect,
"expect-raw=s" => sub { my ($n, $v) = @_; unshift(@expect_raw, eval($v)); },
+ "final-eol!" => \$final_eol,
"gen-only" => \$gen_only,
"grub|g:s" => \$grub_config,
"grub-preamble=s"=> \$grub_preamble,
- "grub-prefix=s" => \$grub_prefix,
+ "prefix|grub-prefix=s" => \$grub_prefix,
"grub2:s" => \$grub2_config,
"grub2-prolog=s" => \$grub2_prolog,
+ "ider" => \$ider,
+ "interaction!" => \$interaction,
"iprelay=s" => \$iprelay,
"iso:s" => \$iso_image,
"kernel|k=s" => \$kernel_opt,
"interactive|i" => \$interactive,
"name=s" => \$config_name_opt,
"make|m:s" => \$make,
+ "netif=s" => \$netif,
"no-file-gen" => \$no_file_gen,
"off" => \$off_opt,
"on" => \$on_opt,
"qemu-flags|q=s" => \$qemu_flags_cmd,
"remote-cmd=s" => \$remote_cmd,
"remote-expect=s"=> \$remote_expect,
+ "remote-expect-silent=s"=> sub { $remote_expect=$_[1]; $remote_expect_silent=1; },
+ "reset!" => \$reset,
"reset-cmd=s" => \$reset_cmd,
"rsync-flags=s" => \$rsync_flags,
"scons:s" => \$scons,
"server:s" => \$server,
"strip-rom" => sub { $rom_prefix = ''; },
"stty=s" => \$stty,
- "uboot" => \$uboot,
- "uboot-init=s" => \$uboot_init,
+ "tftp" => \$tftp,
+ "tftp-port=i" => \$tftp_port,
+ "uboot:s" => \$uboot,
+ "no-uboot" => sub { undef $uboot; },
+ "uboot-addr=s" => \%uboot_addr,
+ "uboot-cmd=s" => \$uboot_cmd,
+ "uboot-init=s" => \@uboot_init,
"h" => \$help,
"help" => \$man,
);
# First process target options
{
my $t = defined($explicit_target) ? $explicit_target : $CFG::default_target;
- if ($t) {
+ my @target_expanded;
+ Getopt::Long::Configure(qw/no_ignore_case pass_through/);
+ while ($t) {
exists $CFG::targets{$t} or die("Unknown target '$t' (valid targets are: ".join(", ", sort keys(%CFG::targets)).")");
- GetOptionsFromString($CFG::targets{$t}, %opt_spec);
+
+ undef $explicit_target;
+ my ($ret, $remaining_args) = GetOptionsFromString ($CFG::targets{$t}, ("target|t=s" => \$explicit_target));
+ if (!$ret) { die "Error parsing target $t option"; }
+ push(@target_expanded, @$remaining_args);
+ $t = $explicit_target;
}
+ Getopt::Long::Configure(qw/no_ignore_case no_pass_through/);
+ GetOptionsFromArray(\@target_expanded, %opt_spec) or die ("Error in target definition");
}
# Then process other command line options - some of them may override
if (defined $config_name_opt && scalar(@ARGV) > 1) { die "You cannot use --name with multiple scripts"; }
+if ($ider) {
+ $iso_image //= ''; # IDE-R needs an ISO image
+ if (!defined $amt) { die "Error: --ider requires --amt"; }
+}
+
+{
+ my %input_opts = ('--iprelay' => \$iprelay,
+ '--serial' => \$serial,
+ '--remote-cmd' => \$remote_cmd,
+ '--amt' => \$amt);
+ my @opts = grep(defined(${$input_opts{$_}}) , keys %input_opts);
+
+ die("novaboot: More than one target connection option: ".join(', ', @opts)) if scalar @opts > 1;
+}
+
# Default options
if (defined $serial) {
$serial ||= "/dev/ttyUSB0";
my $file;
my $EOF;
my $last_fn = '';
-my ($modules, $variables, $generated, $continuation);
-while (<>) {
+my ($modules, $variables, $generated, $continuation) = ([], {}, []);
+my $skip_reading = defined($on_opt) || defined($off_opt);
+while (!$skip_reading && ($_ = <>)) {
if ($ARGV ne $last_fn) { # New script
die "Missing EOF in $last_fn" if $file;
die "Unfinished line in $last_fn" if $continuation;
push @$modules, $_;
next;
}
+ if (/^run (.*)/) { # run line
+ push @$generated, {command => $1};
+ next;
+ }
+ if (/^uboot(?::([0-9]+)s)? (.*)/) { # uboot line
+ # TODO: If U-Boot supports some interactive menu, it might
+ # make sense to store uboot lines per novaboot script.
+ if ($1) { # Command with explicit timeout
+ push @uboot_init, { command => $2,
+ timeout => $1 };
+ } else { # Command without explicit timeout
+ push @uboot_init, $2;
+ }
+ next;
+ }
die("novaboot: Cannot parse script '$last_fn' line $.. Didn't you forget 'load' keyword?\n");
}
open(my $f, '>', $fn) || die("$fn: $!");
map { s|\brom://([^ ]*)|$rom_prefix$base$1|g; print $f "$_"; } @{$config};
close($f);
- print "novaboot: Created $fn\n";
+ print STDERR "novaboot: Created $fn\n";
} elsif (exists $$g{command} && ! $no_file_gen) {
$ENV{SRCDIR} = dirname(File::Spec->rel2abs( $filename, $invocation_dir ));
- system_verbose("( $$g{command} ) > $$g{filename}");
+ if (exists $$g{filename}) {
+ system_verbose("( $$g{command} ) > $$g{filename}");
+ } else {
+ system_verbose($$g{command});
+ }
}
}
}
return $filename;
}
+sub generate_syslinux_config($$$$)
+{
+ my ($filename, $title, $base, $modules_ref) = @_;
+ if ($base && $base !~ /\/$/) { $base = "$base/"; };
+ open(my $fg, '>', $filename) or die "$filename: $!";
+ print $fg "LABEL $title\n";
+ #TODO print $fg "MENU LABEL $human_readable_title\n";
+
+ my ($kbin, $kcmd) = split(' ', @$modules_ref[0], 2);
+
+ if (system("file $kbin|grep 'Linux kernel'") == 0) {
+ my $initrd = @$modules_ref[1];
+ die('To many "load" lines for Linux kernel') if (scalar @$modules_ref > 2);
+ print $fg "LINUX $base$kbin\n";
+ print $fg "APPEND $kcmd\n";
+ print $fg "INITRD $base$initrd\n";
+ } else {
+ print $fg "KERNEL mboot.c32\n";
+ my @append;
+ foreach (@$modules_ref) {
+ s|\brom://([^ ]*)|$rom_prefix$base$1|g; # Translate rom:// files - needed for vdisk parameter of sigma0
+ push @append, "$base$_";
+ print $fg "APPEND ".join(' --- ', @append)."\n";
+ }
+ }
+ #TODO print $fg "TEXT HELP\n";
+ #TODO print $fg "some help here\n";
+ #TODO print $fg "ENDTEXT\n";
+ close($fg);
+ print("novaboot: Created $builddir/$filename\n");
+ return $filename;
+}
+
sub generate_grub2_config($$$$;$$)
{
my ($filename, $title, $base, $modules_ref, $preamble, $prolog) = @_;
sub exec_verbose(@)
{
- print "novaboot: Running: ".shell_cmd_string(@_)."\n";
+ print STDERR "novaboot: Running: ".shell_cmd_string(@_)."\n";
exec(@_);
+ exit(1); # should not be reached
}
sub system_verbose($)
{
my $cmd = shift;
- print "novaboot: Running: $cmd\n";
+ print STDERR "novaboot: Running: $cmd\n";
my $ret = system($cmd);
if ($ret & 0x007f) { die("Command terminated by a signal"); }
if ($ret & 0xff00) {die("Command exit with non-zero exit code"); }
if ($ret) { die("Command failure $ret"); }
}
-## WvTest handline
+sub trim($) {
+ my ($str) = @_;
+ $str =~ s/^\s+|\s+$//g;
+ return $str
+}
+
+## WvTest headline
if (exists $variables->{WVDESC}) {
- print "Testing \"$variables->{WVDESC}\" in $last_fn:\n";
+ print STDERR "Testing \"$variables->{WVDESC}\" in $last_fn:\n";
} elsif ($last_fn =~ /\.wv$/) {
- print "Testing \"all\" in $last_fn:\n";
+ print STDERR "Testing \"all\" in $last_fn:\n";
}
-## Connect to the target and check whether is not occupied
+## Connect to the target and check whether it is not occupied
# We have to do this before file generation phase, because file
# generation is intermixed with file deployment phase and we want to
my $exp; # Expect object to communicate with the target over serial line
my ($target_reset, $target_power_on, $target_power_off);
+my ($amt_user, $amt_password, $amt_host, $amt_port);
if (defined $iprelay) {
my $IPRELAY;
my $paddr = sockaddr_in($port, inet_aton($addr));
my $proto = getprotobyname('tcp');
socket($IPRELAY, PF_INET, SOCK_STREAM, $proto) || die "socket: $!";
- print "novaboot: Connecting to IP relay... ";
+ print STDERR "novaboot: Connecting to IP relay... ";
connect($IPRELAY, $paddr) || die "connect: $!";
- print "done\n";
+ print STDERR "done\n";
$exp = Expect->init(\*$IPRELAY);
$exp->log_stdout(1);
system_verbose("stty -F $serial $stty");
open($CONN, "+<", $serial) || die "open $serial: $!";
$exp = Expect->init(\*$CONN);
-} elsif ($remote_cmd) {
- print "novaboot: Running: $remote_cmd\n";
+}
+elsif ($remote_cmd) {
+ print STDERR "novaboot: Running: $remote_cmd\n";
$exp = Expect->spawn($remote_cmd);
-} elsif (defined $amt){
- use LWP::UserAgent;
- use LWP::Authen::Digest;
- sub genXML { #HOST, username, password, schema, className, pstate
+}
+elsif (defined $amt) {
+ require LWP::UserAgent;
+ require LWP::Authen::Digest;
+
+ sub genXML {
my ($host, $username, $password, $schema, $className, $pstate) = @_;
- #AMT numbers for PowerStateChange: 2 on, 4 standby, 7 hibernate, 8 off,
- #10 reset, 11 MNI interupt(on windows->bluescreen;-))
- my %pstates = ("on", 2,
- "standby", 4,
- "hibernate", 7,
- "off", 8,
- "reset", 10,
- "MNI", 11);
- my $text = <<END;
+ #AMT numbers for PowerStateChange (MNI => bluescreen on windows;-)
+ my %pstates = ("on" => 2,
+ "standby" => 4,
+ "hibernate" => 7,
+ "off" => 8,
+ "reset" => 10,
+ "MNI" => 11);
+ return <<END;
<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">
<s:Header><a:To>http://$host:16992/wsman</a:To>
<w:ResourceURI s:mustUnderstand="true">$schema</w:ResourceURI>
</p:RequestPowerStateChange_INPUT>
</s:Body></s:Envelope>
END
- return $text;
}
-
- sub sendPOST{ #ip, username, password, content
- my ($host, $username, $password, $content)=@_;+
- my $req = HTTP::Request->new(POST => "http://$host:16992/wsman");
+
+ sub sendPOST {
+ my ($host, $username, $password, $content) = @_;
+
my $ua = LWP::UserAgent->new();
- my $res = $ua->request($req);
- die ($res->status_line) unless $res->code==401;
- my $header = $res->header("WWW-Authenticate");
- my $digRealm = "Digest realm=\"";
- my $posRealm = index($header,$digRealm)+length($digRealm);
- my $realm = substr($header,$posRealm,index($header,"\"",$posRealm)-$posRealm);
-
- $ua = LWP::UserAgent->new();
$ua->agent("novaboot");
- $ua->credentials("$host:16992",$realm, $username => $password);
+
+ my $req = HTTP::Request->new(POST => "http://$host:16992/wsman");
+ my $res = $ua->request($req);
+ die ("Unexpected AMT response: " . $res->status_line) unless $res->code == 401;
+
+ my ($realm) = $res->header("WWW-Authenticate") =~ /Digest realm="(.*?)"/;
+ $ua->credentials("$host:16992", $realm, $username => $password);
+
# Create a request
$req = HTTP::Request->new(POST => "http://$host:16992/wsman");
$req->content_type('application/x-www-form-urlencoded');
$req->content($content);
$res = $ua->request($req);
- die ($res->status_line) unless $res->is_success;
+ die ("AMT power change request failed: " . $res->status_line) unless $res->is_success;
$res->content() =~ /<g:ReturnValue>(\d+)<\/g:ReturnValue>/;
return $1;
}
- sub powerChange {#IP, username, password, pstate
+ sub powerChange {
my ($host, $username, $password, $pstate)=@_;
my $schema="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_PowerManagementService";
my $className="/RequestPowerStateChange";
my $content = genXML($host, $username, $password ,$schema, $className, $pstate);
return sendPOST($host, $username, $password, $content);
- }
-
- my (@amt_info, $len, $amt_password, $result, $host, $port, $user);
- ($user,$amt_password,$host,$port) = ($amt =~ /(?:(.*?)(?::(.*))?@)?([^:]*)(?::([0-9]*))?/);;
- $len=@amt_info;
- die "AMT host not specified" unless $host;
- $user ||= "admin";
- $port ||= 16994;
+ }
+
+ ($amt_user,$amt_password,$amt_host,$amt_port) = ($amt =~ /(?:(.*?)(?::(.*))?@)?([^:]*)(?::([0-9]*))?/);;
+ $amt_user ||= "admin";
$amt_password ||= $ENV{'AMT_PASSWORD'} || die "AMT password not specified";
- $target_power_off = sub {
- $result = powerChange($host,$user,$amt_password, "off");
- die "Cannot turn off the computer, somebody is connected or computer is already off" unless $result==0;
- };
-
- $target_power_on = sub {
- $result = powerChange($host,$user,$amt_password, "on");
- };
-
- $target_reset = sub {
- $result = powerChange($host,$user,$amt_password, "reset");
- if ($result != 0) {
- print STDERR "Warning: Cannot reset $host, trying power on. ";
- $result = powerChange($host,$user,$amt_password, "on");
- }
- die "AMT reset failed (code $result)" if $result;
- sleep(2); # Without pause, connection to AMT machine is initiated before reset and fails after restart
- $exp = Expect->spawn("amtterm -u $user -p $amt_password $host $port");
- #expect must be here because AMT doesnt allow to reset/on/off computer when somebody is connected
- };
+ $amt_host || die "AMT host not specified";
+ $amt_port ||= 16994;
+
+
+ $target_power_off = sub {
+ $exp->close();
+ my $result = powerChange($amt_host,$amt_user,$amt_password, "off");
+ die "AMT power off failed (ReturnValue $result)" if $result != 0;
+ };
+
+ $target_power_on = sub {
+ my $result = powerChange($amt_host,$amt_user,$amt_password, "on");
+ die "AMT power on failed (ReturnValue $result)" if $result != 0;
+ };
+
+ $target_reset = sub {
+ my $result = powerChange($amt_host,$amt_user,$amt_password, "reset");
+ if ($result != 0) {
+ print STDERR "Warning: Cannot reset $amt_host, trying power on. ";
+ $result = powerChange($amt_host,$amt_user,$amt_password, "on");
+ }
+ die "AMT reset failed (ReturnValue $result)" if $result != 0;
+ };
+
+ my $cmd = "amtterm -u $amt_user -p $amt_password $amt_host $amt_port";
+ print STDERR "novaboot: Running: $cmd\n" =~ s/\Q$amt_password\E/???/r;
+ $exp = Expect->spawn($cmd);
+ $exp->expect(10, "RUN_SOL") || die "Expect for 'RUN_SOL' timed out";
+
}
+
if ($remote_expect) {
- $exp->expect(10, $remote_expect) || die "Expect for '$remote_expect' timed out";
+ $exp || die("No serial line connection");
+ my $log = $exp->log_stdout;
+ if (defined $remote_expect_silent) {
+ $exp->log_stdout(0);
+ }
+ $exp->expect(180, $remote_expect) || die "Expect for '$remote_expect' timed out";
+ if (defined $remote_expect_silent) {
+ $exp->log_stdout($log);
+ print $exp->after() if $log;
+ }
}
if (defined $reset_cmd) {
exit;
}
if (defined $off_opt && defined $target_power_off) {
- print "novaboot: Switching the target off...\n";
+ print STDERR "novaboot: Switching the target off...\n";
&$target_power_off();
exit;
}
$builddir ||= dirname(File::Spec->rel2abs( ${$scripts[0]}{filename})) if scalar @scripts;
if (defined $builddir) {
chdir($builddir) or die "Can't change directory to $builddir: $!";
- print "novaboot: Entering directory `$builddir'\n";
+ print STDERR "novaboot: Entering directory `$builddir'\n";
+} else {
+ $builddir = $invocation_dir;
}
## File generation phase
my (%files_iso, $menu_iso, $filename);
my $config_name = '';
+my $prefix = '';
foreach my $script (@scripts) {
$filename = $$script{filename};
if (exists $variables->{BUILDDIR}) {
$builddir = File::Spec->rel2abs($variables->{BUILDDIR});
chdir($builddir) or die "Can't change directory to $builddir: $!";
- print "novaboot: Entering directory `$builddir'\n";
+ print STDERR "novaboot: Entering directory `$builddir'\n";
}
- my $prefix;
- ($prefix = $grub_prefix) =~ s/\$NAME/$config_name/ if defined $grub_prefix;
- $prefix ||= $builddir;
+ if ($grub_prefix) {
+ $prefix = $grub_prefix;
+ $prefix =~ s/\$NAME/$config_name/;
+ $prefix =~ s/\$BUILDDIR/$builddir/;
+ }
# TODO: use $grub_prefix as first parameter if some switch is given
generate_configs('', $generated, $filename);
{
my @files = map({ ($file) = m/([^ ]*)/; $file; } @$modules);
# Filter-out generated files
- my @to_build = grep({ my $file = $_; !scalar(grep($file eq $$_{filename}, @$generated)) } @files);
+ my @to_build = grep({ my $file = $_; !scalar(grep($file eq ($$_{filename} || ''), @$generated)) } @files);
system_verbose($scons || $CFG::scons." ".join(" ", @to_build)) if (defined $scons);
system_verbose($make || $CFG::make ." ".join(" ", @to_build)) if (defined $make);
map({ my $file = (split)[0]; die "$file: $!" if ! -f $file; } @$modules);
my $istty = -t STDOUT && ($ENV{'TERM'} || 'dumb') ne 'dumb';
my $progress = $istty ? "--progress" : "";
- system_verbose("rsync $progress -RLp $rsync_flags $files $real_server");
- if ($server =~ m|/\$NAME$| && $concat) {
- my $cmd = join("; ", map { "( cd $path/.. && cat */$_ > $_ )" } @bootloader_configs);
- system_verbose($hostname ? "ssh $hostname '$cmd'" : $cmd);
+ if ($files) {
+ system_verbose("rsync $progress -RLp $rsync_flags $files $real_server");
+ if ($server =~ m|/\$NAME$| && $concat) {
+ my $cmd = join("; ", map { "( cd $path/.. && cat */$_ > $_ )" } @bootloader_configs);
+ system_verbose($hostname ? "ssh $hostname '$cmd'" : $cmd);
+ }
}
}
if (defined $iso_image) {
generate_configs("(cd)", $generated, $filename);
my $menu;
- generate_grub_config(\$menu, $config_name, "(cd)", $modules);
+ generate_syslinux_config(\$menu, $config_name, "/", $modules);
$menu_iso .= "$menu\n";
map { ($file,undef) = split; $files_iso{$file} = 1; } @$modules;
}
## Generate ISO image
if (defined $iso_image) {
- open(my $fh, ">menu-iso.lst");
- print $fh "timeout 5\n\n$menu_iso";
+ system_verbose("mkdir -p isolinux");
+
+ my @files;
+ if (-f '/usr/lib/ISOLINUX/isolinux.bin') {
+ # Newer ISOLINUX version
+ @files = qw(/usr/lib/ISOLINUX/isolinux.bin /usr/lib/syslinux/modules/bios/mboot.c32 /usr/lib/syslinux/modules/bios/libcom32.c32 /usr/lib/syslinux/modules/bios/menu.c32 /usr/lib/syslinux/modules/bios/ldlinux.c32);
+ } else {
+ # Older ISOLINUX version
+ @files = qw(/usr/lib/syslinux/isolinux.bin /usr/lib/syslinux/mboot.c32 /usr/lib/syslinux/menu.c32);
+ }
+ system_verbose("cp @files isolinux");
+ open(my $fh, ">isolinux/isolinux.cfg");
+ if ($#scripts) {
+ print $fh "TIMEOUT 50\n";
+ print $fh "DEFAULT menu\n";
+ } else {
+ print $fh "DEFAULT $config_name\n";
+ }
+ print $fh "$menu_iso";
close($fh);
- my $files = "boot/grub/menu.lst=menu-iso.lst " . join(" ", map("$_=$_", keys(%files_iso)));
+
+ my $files = join(" ", map("$_=$_", (keys(%files_iso), 'isolinux/isolinux.cfg', map(s|.*/|isolinux/|r, @files))));
$iso_image ||= "$config_name.iso";
- 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");
+
+ # Note: We use -U flag below to "Allow 'untranslated' filenames,
+ # completely violating the ISO9660 standards". Without this
+ # option, isolinux is not able to read files names for example
+ # bzImage-3.0.
+ system_verbose("$CFG::genisoimage -R -b isolinux/isolinux.bin -c isolinux/boot.cat -no-emul-boot -boot-load-size 4 -boot-info-table -hide-rr-moved -U -o $iso_image -graft-points $files");
print("ISO image created: $builddir/$iso_image\n");
}
}
if ($variables->{WVTEST_TIMEOUT}) {
- print "wvtest: timeout ", $variables->{WVTEST_TIMEOUT}, "\n";
+ print STDERR "wvtest: timeout ", $variables->{WVTEST_TIMEOUT}, "\n";
}
-sub trim($) {
- my ($str) = @_;
- $str =~ s/^\s+|\s+$//g;
- return $str
-}
-
-### Qemu
+### Start in Qemu
if (defined $qemu) {
# Qemu
if (defined $iso_image) {
# Boot NOVA with grub (and test the iso image)
- push(@qemu_flags, ('-cdrom', "$config_name.iso"));
+ push(@qemu_flags, ('-cdrom', $iso_image));
} else {
# Boot NOVA without GRUB
}
push(@qemu_flags, qw(-serial stdio)); # Redirect serial output (for collecting test restuls)
unshift(@qemu_flags, ('-name', $config_name));
- print "novaboot: Running: ".shell_cmd_string($qemu, @qemu_flags)."\n";
+ print STDERR "novaboot: Running: ".shell_cmd_string($qemu, @qemu_flags)."\n";
$exp = Expect->spawn(($qemu, @qemu_flags)) || die("exec() failed: $!");
}
my ($dhcpd_pid, $tftpd_pid);
+$tftp=1 if $tftp_port;
+
if (defined $dhcp_tftp)
{
generate_configs("(nd)", $generated, $filename);
system_verbose('mkdir -p tftpboot');
generate_grub_config("tftpboot/os-menu.lst", $config_name, "(nd)", \@$modules, "timeout 0");
open(my $fh, '>', 'dhcpd.conf');
- my $mac = `cat /sys/class/net/eth0/address`;
+ my $mac = `cat /sys/class/net/$netif/address`;
chomp $mac;
print $fh "subnet 10.23.23.0 netmask 255.255.255.0 {
range 10.23.23.10 10.23.23.100;
fixed-address 10.23.23.1;
}";
close($fh);
- system_verbose("sudo ip a add 10.23.23.1/24 dev eth0;
- sudo ip l set dev eth0 up;
+ system_verbose("sudo ip a add 10.23.23.1/24 dev $netif;
+ sudo ip l set dev $netif up;
sudo touch dhcpd.leases");
# We run servers by forking ourselves, because the servers end up
# process group (e.g. Ctrl-C on terminal).
$dhcpd_pid = fork();
exec_verbose("sudo dhcpd -d -cf dhcpd.conf -lf dhcpd.leases -pf dhcpd.pid") if ($dhcpd_pid == 0);
- $tftpd_pid = fork();
- exec_verbose("sudo in.tftpd --foreground --secure -v -v -v --pidfile tftpd.pid $builddir") if ($tftpd_pid == 0);
+}
+
+if (defined $dhcp_tftp || defined $tftp) {
+ $tftp_port ||= 69;
+ # Unfortunately, tftpd requires root privileges even with
+ # non-privileged (>1023) port due to initgroups().
+ system_verbose("sudo in.tftpd --listen --secure -v -v -v --pidfile tftpd.pid --address :$tftp_port $builddir");
# Kill server when we die
- $SIG{__DIE__} = sub { system_verbose('sudo pkill --pidfile=dhcpd.pid');
+ $SIG{__DIE__} = sub { system_verbose('sudo pkill --pidfile=dhcpd.pid') if (defined $dhcp_tftp);
system_verbose('sudo pkill --pidfile=tftpd.pid'); };
+
+ # We have to kill tftpd explicitely, because it is not in our process group
+ $SIG{INT} = sub { system_verbose('sudo pkill --pidfile=tftpd.pid'); exit(0); };
}
-### Serial line or IP relay
+### AMT IDE-R
+if (defined $ider) {
+ my $ider_cmd= "amtider -c $iso_image -u $amt_user -p $amt_password $amt_host $amt_port" ;
+ print STDERR "novaboot: Running: $ider_cmd\n" =~ s/\Q$amt_password\E/???/r;
+ my $ider_pid = fork();
+ if ($ider_pid == 0) {
+ exec($ider_cmd);
+ die "IDE redirection failed";
+ }
+ # FIXME: This collides with --tftp option. Hopefully, nobody needs
+ # to use both simultaneously.
+ $SIG{__DIE__} = sub { system_verbose('kill $ider_pid'); };
+}
-if (defined $target_reset) {
- print "novaboot: Reseting the test box... ";
+### Reset target (IP relay, AMT, ...)
+
+if (defined $target_reset && $reset) {
+ print STDERR "novaboot: Reseting the test box... ";
&$target_reset();
- print "done\n";
+ print STDERR "done\n";
+ if (defined $exp) {
+ # We don't want to output anything printed by the target
+ # before reset so we clear the buffers now. This is, however,
+ # not ideal because we may loose some data that were sent
+ # after the reset. If this is a problem, one should reset and
+ # connect to serial line in atomic manner. For example, if
+ # supported by hardware, use --remote-cmd 'sterm -d ...' and
+ # do not use separate --reset-cmd.
+ my $log = $exp->log_stdout;
+ $exp->log_stdout(0);
+ $exp->expect(0); # Read data from target
+ $exp->clear_accum(); # Clear the read data
+ $exp->log_stdout($log);
+ }
}
+### U-boot conversation
if (defined $uboot) {
- print "novaboot: Waiting for uBoot prompt...\n";
+ my $uboot_prompt = $uboot || '=> ';
+ print STDERR "novaboot: Waiting for U-Boot prompt...\n";
+ $exp || die("No serial line connection");
$exp->log_stdout(1);
#$exp->exp_internal(1);
$exp->expect(20,
[qr/Hit any key to stop autoboot:/, sub { $exp->send("\n"); exp_continue; }],
- '=> ') || die "No uBoot prompt deteceted";
- $exp->send("$uboot_init\n") if $uboot_init;
- $exp->expect(10, '=> ') || die "uBoot prompt timeout";
-
- my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);
- my $dtb;
- @$modules = map { if (/\.dtb$/) { $dtb=$_; (); } else { $_ } } @$modules;
- my $initrd = shift @$modules;
-
- my $kern_addr = '800000';
- my $initrd_addr = '-';
- my $dtb_addr = '';
-
- $exp->send("tftp $kern_addr $kbin\n");
- $exp->expect(10,
- [qr/#/, sub { exp_continue; }],
- '=> ') || die "Kernel load failed";
- if (defined $dtb) {
- $dtb_addr = '7f0000';
- $exp->send("tftp $dtb_addr $dtb\n");
- $exp->expect(10,
- [qr/#/, sub { exp_continue; }],
- '=> ') || die "Device tree load failed";
+ $uboot_prompt) || die "No U-Boot prompt deteceted";
+ foreach my $cmdspec (@uboot_init) {
+ my ($cmd, $timeout);
+ if (ref($cmdspec) eq "HASH") {
+ $cmd = $cmdspec->{command};
+ $timeout = $cmdspec->{timeout};
+ } else {
+ $cmd = $cmdspec;
+ $timeout = 10;
+ }
+ if ($cmd =~ /\$NB_MYIP/) {
+ my $ip = (grep /inet /, `ip addr show $netif`)[0] || die "Problem determining our IP address";
+ $ip =~ s/\s*inet ([0-9.]*).*/$1/;
+ $cmd =~ s/\$NB_MYIP/$ip/g;
+ }
+ if ($cmd =~ /\$NB_PREFIX/) {
+ my $p = $prefix;
+ $p =~ s|/*$||;
+ $cmd =~ s/\$NB_PREFIX/$p/g;
+ }
+ chomp($cmd);
+ $exp->send("$cmd\n");
+ $exp->expect($timeout, $uboot_prompt) || die "U-Boot prompt timeout";
+ }
+
+ # Load files if there are some load lines in the script
+ if (scalar(@$modules) > 0 && !$variables->{NO_BOOT}) {
+ my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);
+ my $dtb;
+ @$modules = map { if (/\.dtb$/) { $dtb=$_; (); } else { $_ } } @$modules;
+ my $initrd = shift @$modules;
+
+ if (defined $kbin) {
+ die "No '--uboot-addr kernel' given" unless $uboot_addr{kernel};
+ $exp->send("tftpboot $uboot_addr{kernel} $prefix$kbin\n");
+ $exp->expect(10,
+ [qr/##/, sub { exp_continue; }],
+ $uboot_prompt) || die "Kernel load timeout";
+ }
+ if (defined $dtb) {
+ die "No '--uboot-addr fdt' given" unless $uboot_addr{fdt};
+ $exp->send("tftpboot $uboot_addr{fdt} $prefix$dtb\n");
+ $exp->expect(10,
+ [qr/##/, sub { exp_continue; }],
+ $uboot_prompt) || die "Device tree load timeout";
+ }
+ if (defined $initrd) {
+ die "No '--uboot-addr ramdisk' given" unless $uboot_addr{ramdisk};
+ $exp->send("tftpboot $uboot_addr{ramdisk} $prefix$initrd\n");
+ $exp->expect(10,
+ [qr/##/, sub { exp_continue; }],
+ $uboot_prompt) || die "Initrd load timeout";
+ } else {
+ $uboot_addr{ramdisk} = '-';
+ }
+
+ $kcmd //= '';
+ $exp->send("setenv bootargs $kcmd\n");
+ $exp->expect(5, $uboot_prompt) || die "U-Boot prompt timeout";
+
}
- if (defined $initrd) {
- $initrd_addr = 'b00000';
- $exp->send("tftp $initrd_addr $initrd\n");
- $exp->expect(10,
- [qr/#/, sub { exp_continue; }],
- '=> ') || die "Initrd load failed";
+ if (!$variables->{NO_BOOT}) {
+ $uboot_cmd //= $variables->{UBOOT_CMD} // 'bootm $kernel_addr $ramdisk_addr $fdt_addr';
+ $uboot_cmd =~ s/\$kernel_addr/$uboot_addr{kernel}/g;
+ $uboot_cmd =~ s/\$ramdisk_addr/$uboot_addr{ramdisk}/g;
+ $uboot_cmd =~ s/\$fdt_addr/$uboot_addr{fdt}/g;
+
+ $exp->send($uboot_cmd . "\n");
+ $exp->expect(5, "\n") || die "U-Boot command timeout";
}
- $exp->send("set bootargs '$kcmd'\n");
- $exp->expect(5, '=> ') || die "uBoot prompt timeout";
- $exp->send("bootm $kern_addr $initrd_addr $dtb_addr\n");
- $exp->expect(5, "\n") || die "uBoot command timeout";
}
-if (defined $exp) {
+### Serial line interaction
+if ($interaction && defined $exp) {
# Serial line of the target is available
my $interrupt = 'Ctrl-C';
if ($interactive && !@exiton) {
$interrupt = '"~~."';
}
- print "novaboot: Serial line interaction (press $interrupt to interrupt)...\n";
+ print STDERR "novaboot: Serial line interaction (press $interrupt to interrupt)...\n";
$exp->log_stdout(1);
if (@exiton) {
- $exp->expect(undef, @expect_raw, @exiton);
+ $exp->expect($exiton_timeout, @expect_raw, @exiton) || die("exiton timeout");
} else {
my @inputs = ($exp);
- if (-t STDIN) { # Set up bi-directional communication if we run on terminal
- my $infile = new IO::File;
- $infile->IO::File::fdopen(*STDIN,'r');
- my $in_object = Expect->exp_init($infile);
- $in_object->set_group($exp);
-
- if ($interactive) {
- $in_object->set_seq('~~\.', sub { print "novaboot: Escape sequence detected\r\n"; undef; });
- $in_object->manual_stty(0); # Use raw terminal mode
- } else {
- $in_object->manual_stty(1); # Do not modify terminal settings
- }
- push(@inputs, $in_object);
+ my $infile = new IO::File;
+ $infile->IO::File::fdopen(*STDIN,'r');
+ my $in_object = Expect->exp_init($infile);
+ $in_object->set_group($exp);
+
+ if ($interactive) {
+ $in_object->set_seq('~~\.', sub { print STDERR "novaboot: Escape sequence detected\r\n"; undef; });
+ $in_object->manual_stty(0); # Use raw terminal mode
+ } else {
+ $in_object->manual_stty(1); # Do not modify terminal settings
}
+ push(@inputs, $in_object);
#use Data::Dumper;
#print Dumper(\@expect_raw);
$exp->expect(undef, @expect_raw) if @expect_raw;
}
## Kill dhcpc or tftpd
-if (defined $dhcp_tftp) {
+if (defined $dhcp_tftp || defined $tftp) {
die("novaboot: This should kill servers on background\n");
}
+# Always finish novaboot output with newline
+print "\n" if $final_eol;
+
## Documentation
=head1 NAME
-novaboot - A tool for booting various operating systems on various hardware or in qemu
+novaboot - Boots a locally compiled operating system on a remote
+target or in qemu
=head1 SYNOPSIS
=head1 DESCRIPTION
-This program makes booting of an operating system (e.g. NOVA or Linux)
-as simple as running a local program. It facilitates booting on local
-or remote hosts or in emulators such as qemu. Novaboot operation is
-controlled by command line options and by a so called novaboot script,
-which can be thought as a generalization of bootloader configuration
-files (see L</"NOVABOOT SCRIPT SYNTAX">). Based on this input,
-novaboot setups everything for the target host to boot the desired
-configuration, i.e. it generates the bootloader configuration file in
-the proper format, deploy the binaries and other needed files to
-required locations, perhaps on a remote boot server and reset the
-target host. Finally, target host's serial output is redirected to
-standard output if that is possible.
-
+This program makes booting of a locally compiled operating system (OS)
+(e.g. NOVA or Linux) on remote targets as simple as running a program
+locally. It automates things like copying OS images to a TFTP server,
+generation of bootloader configuration files, resetting of target
+hardware or redirection of target's serial line to stdin/out. Novaboot
+is highly configurable and it makes it easy to boot a single image on
+different targets or different images on a single target.
+
+Novaboot operation is controlled by command line options and by a so
+called novaboot script, which can be thought as a generalization of
+bootloader configuration files (see L</"NOVABOOT SCRIPT SYNTAX">).
Typical way of using novaboot is to make the novaboot script
executable and set its first line to I<#!/usr/bin/env novaboot>. Then,
booting a particular OS configuration becomes the same as executing a
local program - the novaboot script.
-For example, with C<novaboot> you can:
+Novaboot uses configuration files to, among other things, define
+command line options needed for different targets. Users typically use
+only the B<-t>/B<--target> command line option to select the target.
+Internally, this option expands to the pre-configured options.
+Configuration files are searched at multiple places, which allows to
+have per-system, per-user or per-project configurations. Configuration
+file syntax is described in section L</"CONFIGURATION FILES">.
+
+Simple examples of using C<novaboot>:
=over 3
=item 1.
-Run an OS in Qemu. This is the default action when no other action is
-specified by command line switches. Thus running C<novaboot ./script>
-(or C<./script> as described above) will run Qemu and make it boot the
-configuration specified in the F<script>.
+Run an OS in Qemu. This is can be specified with the B<--qemu> option.
+Thus running C<novaboot --qemu myos> (or C<./myos --qemu> as described
+above) will run Qemu and make it boot the configuration specified in
+the F<myos> script.
=item 2.
Create a bootloader configuration file (currently supported
-bootloaders are GRUB, GRUB2, Pulsar and U-Boot) and copy it with all
-other files needed for booting to a remote boot server.
+bootloaders are GRUB, GRUB2, ISOLINUX, Pulsar and U-Boot) and copy it
+with all other files needed for booting to a remote boot server. Then
+use a TCP/IP-controlled relay/serial-to-TCP converter to reset the
+target and receive its serial output.
- ./script --server=192.168.1.1:/tftp --iprelay=192.168.1.2
-
-This command copies files to the TFTP server and uses
-TCP/IP-controlled relay to reset the target host and receive its
-serial output.
+ ./myos --grub2 --server=192.168.1.1:/tftp --iprelay=192.168.1.2
=item 3.
-Run DHCP and TFTP server on developer's machine to PXE-boot the target
-host from it. E.g.
+Run DHCP and TFTP server on developer's machine to boot the target
+from it.
- ./script --dhcp-tftp
+ ./myos --dhcp-tftp
-When a PXE-bootable machine is connected via Ethernet to developer's
-machine, it will boot the configuration described in I<script>.
+This is useful when no network infrastructure is in place and
+the target is connected directly to developer's box.
=item 4.
-Create bootable ISO images. E.g.
+Create bootable ISO image.
novaboot --iso -- script1 script2
-The created ISO image will have GRUB bootloader installed on it and
-the boot menu will allow selecting between I<script1> and I<script2>
-configurations.
+The created ISO image will have ISOLINUX bootloader installed on it
+and the boot menu will allow selecting between I<script1> and
+I<script2> configurations.
=back
-Note that the options needed for a specific target can be stored in a
-L</"CONFIGURATION FILE">. Then it is sufficient to use only the B<-t>
-option to specify the name of the target.
-
=head1 PHASES AND OPTIONS
Novaboot performs its work in several phases. Each phase can be
-influenced by several options, certain phases can be skipped. The list
-of phases (in the execution order) and the corresponding options
-follow.
+influenced by several command line options, certain phases can be
+skipped. The list of phases (in the execution order) is as follows.
+
+=over
+
+=item 1. L<Configuration reading|/Configuration reading phase>
+
+=item 2. L<Command line processing|/Command line processing phase>
+
+=item 3. L<Script preprocessing|/Script preprocessing phase>
+
+=item 4. L<File generation|/File generation phase>
+
+=item 5. L<Target connection|/Target connection check>
+
+=item 6. L<File deployment|/File deployment phase>
+
+=item 7. L<Target power-on and reset|/Target power-on and reset phase>
+
+=item 8. L<Interaction with the bootloader|/Interaction with the bootloader on the target>
+
+=item 9. L<Target interaction|/Target interaction phase>
+
+=back
+
+Each phase is described in the following sections together with the
+command line options that control it.
=head2 Configuration reading phase
-After starting, novaboot reads configuration files. By default, it
-searches for files named F<.novaboot> starting from the directory of
-the novaboot script (or working directory, see bellow) and continuing
-upwards up to the root directory. The configuration files are read in
-order from the root directory downwards with latter files overriding
-settings from the former ones.
+After starting, novaboot reads configuration files. Their content is
+described in section L</"CONFIGURATION FILES">. By default,
+configuration is read from multiple locations. First from the system
+configuration directory (F</etc/novaboot.d/>), second from the user
+configuration file (F<~/.config/novaboot>) and third from F<.novaboot>
+files along the path to the current directory. Alternatively, a single
+configuration file specified with the B<-c> switch or with the
+C<NOVABOOT_CONFIG> environment variable is read. The latter read files
+override settings from the former ones.
+
+The system configuration directory is determined by the content of
+NOVABOOT_CONFIG_DIR environment variable and defaults to
+F</etc/novaboot.d>. Files in this directory with names consisting
+solely of English letters, numbers, dashes '-' and underscores '_'
+(note that dot '.' is not included) are read in alphabetical order.
+
+Then, the user configuration file is read from
+F<$XDG_CONFIG_HOME/novaboot>. If C<$XDG_CONFIG_HOME> environemnt
+variable is not set F<~/.config/novaboot> is read instead.
+
+Finally, novaboot searches for files named F<.novaboot> starting from the
+directory of the novaboot script (or working directory, see bellow)
+and continuing upwards up to the root directory. The found
+configuration files are then read in the opposite order (i.e. from the
+root directory downwards). This allows to have, for example, a project
+specific configuration in F<~/project/.novaboot>.
+
+Note the difference between F<~/.config/novaboot> and F<~/.novaboot>.
+The former one is read always, whereas the latter only when novaboot
+script or working directory is under the C<$HOME> directory.
In certain cases, the location of the novaboot script cannot be
determined in this early phase. This happens either when the script is
-read from the standard input or when novaboot is invoked explicitly
-and options precede the script name, as in the example L</"4."> above.
-In this case the current working directory is used as a starting point
-for configuration file search.
+read from the standard input or when novaboot is invoked explicitly as
+in the example L</"4."> above. In this case the current working
+directory is used as a starting point for configuration file search
+instead of the novaboot script directory.
=over 8
=item --dump-config
-Dump the current configuration to stdout end exits. Useful as an
+Dump the current configuration to stdout end exit. Useful as an
initial template for a configuration file.
=item -h, --help
=item -t, --target=I<target>
This option serves as a user configurable shortcut for other novaboot
-options. The effect of this option is the same as the options stored
-in the C<%targets> configuration variable under key I<target>. See
-also L</"CONFIGURATION FILE">.
+options. The effect of this option is the same as specifying the
+options stored in the C<%targets> configuration variable under key
+I<target>. See also L</"CONFIGURATION FILES">.
+
+When this option is not given, novaboot tries to determine the target
+to use from either B<NOVABOOT_TARGET> environment variable or from
+B<$default_target> configuration file variable.
=back
Append a string to the first C<load> line in the novaboot script. This
can be used to append parameters to the kernel's or root task's
-command line. Can appear multiple times.
+command line. This option can appear multiple times.
=item -b, --bender
=item --chainloader=I<chainloader>
-Chainloader that is loaded before the kernel and other files specified
-in the novaboot script. E.g. 'bin/boot/bender promisc'.
+Specifies a chainloader that is loaded before the kernel and other
+files specified in the novaboot script. E.g. 'bin/boot/bender
+promisc'.
=item --dump
GRUB or GRUB2 config files. This is useful for specifying GRUB's
timeout.
-=item --grub-prefix=I<prefix>
+=item --prefix=I<prefix>
-Specifies I<prefix> that is put in front of every file name in GRUB's
-F<menu.lst>. The default value is the absolute path to the build directory.
+Specifies I<prefix> (e.g. F</srv/tftp>) that is put in front of every
+file name in generated bootloader configuration files (or in U-Boot
+commands).
If the I<prefix> contains string $NAME, it will be replaced with the
name of the novaboot script (see also B<--name>).
+If the I<prefix> contains string $BUILDDIR, it will be replaced with
+the build directory (see also B<--build-dir>).
+
+=item --grub-prefix
+
+Alias for B<--prefix>.
+
=item --grub2[=I<filename>]
-Generate GRUB2 menuentry in I<filename>. If I<filename> is not
-specified F<grub.cfg> is used. The content of the menuentry can be
-customized with B<--grub-preable>, B<--grub2-prolog> or
+Generate GRUB2 menu entry in I<filename>. If I<filename> is not
+specified F<grub.cfg> is used. The content of the menu entry can be
+customized with B<--grub-preamble>, B<--grub2-prolog> or
B<--grub_prefix> options.
-In order to use the the generated menuentry on your development
+In order to use the the generated menu entry on your development
machine that uses GRUB2, append the following snippet to
F</etc/grub.d/40_custom> file and regenerate your grub configuration,
i.e. run update-grub on Debian/Ubuntu.
=item --grub2-prolog=I<prolog>
-Specifies text I<preable> that is put at the beginning of the entry
-GRUB2 entry.
+Specifies text that is put at the beginning of the GRUB2 menu entry.
=item -m, --make[=make command]
=item --no-file-gen
-Do not generate files on the fly (i.e. "<" syntax) except for the
-files generated via "<<WORD" syntax.
+Do not run external commands to generate files (i.e. "<" syntax and
+C<run> keyword). This switch does not influence generation of files
+specified with "<<WORD" syntax.
=item -p, --pulsar[=mac]
=item --amt=I<"[user[:password]@]host[:port]>
-Use Intel AMT to connect to target. Using SOL redirection and WS management
-to powercycle it. The IP address or FQDN of the PC is given by I<host>
-parameter. If I<password> is not specified, environment variable
-AMT_PASSWORD is used. I<port> is defining port number for SOL,
-if not specified, default is 16992. Default I<user> is admin.
+Use Intel AMT technology to control the target machine. WS management
+is used to powercycle it and Serial-Over-Lan (SOL) for input/output.
+The hostname or (IP address) is given by the I<host> parameter. If
+I<password> is not specified, environment variable AMT_PASSWORD is
+used. The I<port> specifies a TCP port for SOL. If not specified, the
+default is 16992. Default I<user> is admin.
=item --iprelay=I<addr[:port]>
=item --remote-expect=I<string>
-Wait for reception of I<string> after establishing the the remote
-connection before continuing.
+Wait for reception of I<string> after establishing the remote
+connection.
+
+=item --remote-expect-silent=I<string>
+The same as B<--remote-expect> except that the remote output is not
+echoed to stdout while waiting for the I<string>. Everything after the
+matched string is printed to stdout, so you may want to include line
+end characters in the I<string> as well.
=back
be booted via PXE BIOS (or similar mechanism) on the test machine
directly connected by a plain Ethernet cable to your workstation.
-The DHCP and TFTP servers require root privileges and C<novaboot>
+The DHCP and TFTP servers requires root privileges and C<novaboot>
uses C<sudo> command to obtain those. You can put the following to
-I</etc/sudoers> to allow running the necessary commands without
-asking for password.
+I</etc/sudoers> to allow running the necessary commands without asking
+for password.
- 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
+ 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 --listen --secure -v -v -v --pidfile tftpd.pid *, /usr/bin/touch dhcpd.leases, /usr/bin/pkill --pidfile=dhcpd.pid, /usr/bin/pkill --pidfile=tftpd.pid
your_login ALL=NOPASSWD: NOVABOOT
+=item --tftp
+
+Starts a TFTP server on your workstation. This is similar to
+B<--dhcp-tftp> except that DHCP server is not started.
+
+The TFTP server require root privileges and C<novaboot> uses C<sudo>
+command to obtain those. You can put the following to I</etc/sudoers>
+to allow running the necessary commands without asking for password.
+
+ Cmnd_Alias NOVABOOT = /usr/sbin/in.tftpd --listen --secure -v -v -v --pidfile tftpd.pid *, /usr/bin/pkill --pidfile=tftpd.pid
+ your_login ALL=NOPASSWD: NOVABOOT
+
+=item --tftp-port=I<port>
+
+Port to run the TFTP server on. Implies B<--tftp>.
+
+=item --netif=I<network interface>
+
+Network interface used to deploy files to the target. Default value is
+I<eth0>. This influences the configuration of the DHCP server started
+by B<--dhcp-tftp> and the value that B<$NB_MYIP> get replaced with in
+U-Boot conversation.
+
=item --iso[=filename]
Generates the ISO image that boots NOVA system via GRUB. If no filename
=item --server[=[[user@]server:]path]
-Copy all files needed for booting to another location (implies B<-g>
-unless B<--grub2> is given). The files will be copied (by B<rsync>
-tool) to the directory I<path>. If the I<path> contains string $NAME,
-it will be replaced with the name of the novaboot script (see also
-B<--name>).
+Copy all files needed for booting to another location. The files will
+be copied (by B<rsync> tool) to the directory I<path>. If the I<path>
+contains string $NAME, it will be replaced with the name of the
+novaboot script (see also B<--name>).
+
+=item --rsync-flags=I<flags>
+
+Specifies which I<flags> are appended to F<rsync> command line when
+copying files as a result of I<--server> option.
=item --concat
concatenating all files of the same name from all subdirectories of
I<path-wo-name> found on the "server".
-=item --rsync-flags=I<flags>
+=item --ider
-Specifies which I<flags> are appended to F<rsync> command line when
-copying files as a result of I<--server> option.
+Use Intel AMT technology for IDE redirection. This allows the target
+machine to boot from novaboot created ISO image. Implies B<--iso>.
+
+The experimental C<amtider> utility needed by this option can be
+obtained from https://github.com/wentasah/amtterm.
=back
=head2 Target power-on and reset phase
+At this point, the target is reset (or switched on/off). There is
+several ways how this can be accomplished. Resetting a physical target
+can currently be accomplished by the following options: B<--amt>,
+B<--iprelay>, B<--reset-cmd>.
+
=over 8
=item --on, --off
-Switch on/off the target machine. Currently works only with
-B<--iprelay>.
+Switch on/off the target machine and exit. The script (if any) is
+completely ignored. Currently it works only with B<--iprelay> or
+B<--amt>.
=item -Q, --qemu[=I<qemu-binary>]
Command that resets the target.
+=item --no-reset, --reset
+
+Disable/enable resetting of the target.
+
=back
=head2 Interaction with the bootloader on the target
=over 8
-=item --uboot
+=item --uboot[=I<prompt>]
-Interact with uBoot bootloader to boot the thing described in the
-novaboot script. Implementation of this option is currently tied to a
-particular board that we use. It may be subject to changes in the
-future!
+Interact with U-Boot bootloader to boot the thing described in the
+novaboot script. I<prompt> specifies the U-Boot's prompt (default is
+"=> ", other common prompts are "U-Boot> " or "U-Boot# ").
+Implementation of this option is currently tied to a particular board
+that we use. It may be subject to changes in the future!
+
+=item --no-uboot
+
+Disable U-Boot interaction previously enabled with B<--uboot>.
=item --uboot-init
Command(s) to send the U-Boot bootloader before loading the images and
-booting them.
+booting them. This option can be given multiple times. After sending
+commands from each option novaboot waits for U-Boot I<prompt>.
+
+If the command contains string I<$NB_MYIP> then this string is
+replaced by IPv4 address of eth0 interface (see also B<--netif>).
+Similarly I<$NB_PREFIX> is replaced with prefix given by B<--prefix>.
+
+See also C<uboot> keyword in L</"NOVABOOT SCRIPT SYNTAX">).
+
+=item --uboot-addr I<name>=I<address>
+
+Load address of U-Boot's C<tftpboot> command for loading I<name>,
+where name is one of I<kernel>, I<ramdisk> or I<fdt> (flattened device
+tree).
+
+The default addresses are ${I<name>_addr_r}, i.e. U-Boot environment
+variables used by convention for this purpose.
+
+=item --uboot-cmd=I<command>
+
+Specifies U-Boot command used to execute the OS. If the command
+contains strings C<$kernel_addr>, C<$ramdisk_addr>, C<$fdt_addr>,
+these are replaced with the addresses configured with B<--uboot-addr>.
+
+The default value is
+
+ bootm $kernel_addr $ramdisk_addr $fdt_addr
+
+or the C<UBOOT_CMD> variable if defined in the novaboot script.
=back
=item --exiton=I<string>
When I<string> is sent by the target, novaboot exits. This option can
-be specified multiple times.
+be specified multiple times, in which case novaboot exits whenever
+either of the specified strings is sent.
If I<string> is C<-re>, then the next B<--exiton>'s I<string> is
treated as regular expression. For example:
--exiton -re --exiton 'error:.*failed'
+=item --exiton-re=I<regex>
+
+The same as --exiton -re --exiton I<regex>.
+
+=item --exiton-timeout=I<seconds>
+
+By default B<--exiton> waits for the string match forever. When this
+option is specified, "exiton" timeouts after the specifies number of
+seconds and novaboot returns non-zero exit code.
+
=item -i, --interactive
Setup things for interactive use of target. Your terminal will be
to the target and does no longer interrupt novaboot. Use "~~."
sequence to exit novaboot.
+=item --no-interaction, --interaction
+
+Skip resp. force target interaction phase. When skipped, novaboot exits
+immediately when boot is initiated.
+
=item --expect=I<string>
When I<string> is received from the target, send the string specified
Example: C<--expect='Continue?' --sendcont='yes\n'>
+=item --final-eol, --no-final-eol
+
+By default, B<novaboot> always prints an end-of-line character at the
+end of its execution in order to ensure that the output of programs
+started after novaboot appears at the beginning of the line. When this
+is not desired B<--no-final-eol> option can be used to override this
+behavior.
+
=back
=head1 NOVABOOT SCRIPT SYNTAX
absolute path of the directory containing the interpreted novaboot
script.
+Lines starting with C<run> keyword contain shell commands that are run
+during file generation phase. This is the same as the "< CMD" syntax
+for C<load> keyboard except that the command's output is not
+redirected to a file. The ordering of commands is the same as they
+appear in the novaboot script.
+
+Lines starting with C<uboot> represent U-Boot commands that are sent
+to the target if B<--uboot> option is given. Having a U-Boot line in
+the novaboot script is the same as passing an equivalent
+B<--uboot-init> option to novaboot. The C<uboot> keyword can be
+suffixed with timeout specification. The syntax is C<uboot:Ns>, where
+C<N> is the whole number of seconds. If the U-Boot command prompt does
+not appear before the timeout, novaboot fails. The default timeout is
+10 seconds.
+
Example (Linux):
#!/usr/bin/env novaboot
load bzImage console=ttyS0,115200
+ run make -C buildroot
load rootfs.cpio < gen_cpio buildroot/images/rootfs.cpio "myapp->/etc/init.d/S99myapp"
Example (NOVA User Land - NUL):
parameters. If this variable is defined and non-empty, the variable
HYPERVISOR_PARAMS is not used.
+=item NO_BOOT
+
+If this variable is 1, the system is not booted. This is currently
+only implemented for U-Boot bootloader where it is useful for
+interacting with the bootloader without booting the system - e.g. for
+flashing.
+
=item QEMU
Use a specific qemu binary (can be overridden with B<-Q>) and flags
Use specific qemu flags (can be overridden with B<-q>).
+=item UBOOT_CMD
+
+See L</--uboot-cmd>.
+
=item WVDESC
-Description of the wvtest-compliant program.
+Description of the WvTest-compliant program.
=item WVTEST_TIMEOUT
=back
-=head1 CONFIGURATION FILE
+=head1 CONFIGURATION FILES
Novaboot can read its configuration from one or more files. By
-default, novaboot looks for files named F<.novaboot> as described in
-L</Configuration reading phase>. Alternatively, its location can be
-specified with the B<-c> switch or with the NOVABOOT_CONFIG
-environment variable. The configuration file has perl syntax and
-should set values of certain Perl variables. The current configuration
-can be dumped with the B<--dump-config> switch. Some configuration
-variables can be overridden by environment variables (see below) or by
-command line switches.
+default, novaboot looks for files in F</etc/novaboot.d>, file
+F<~/.config/novaboot> and files named F<.novaboot> as described in
+L</Configuration reading phase>. Alternatively, configuration file
+location can be specified with the B<-c> switch or with the
+NOVABOOT_CONFIG environment variable. The configuration file has Perl
+syntax (i.e. it is better to put C<1;> as the last line) and should set
+values of certain Perl variables. The current configuration can be
+dumped with the B<--dump-config> switch. Some configuration variables
+can be overridden by environment variables (see below) or by command
+line switches.
Supported configuration variables include:
=item $default_target
Default target (see below) to use when no target is explicitly
-specified on command line with the B<--target> option.
+specified with the B<--target> command line option or
+B<NOVABOOT_TARGET> environment variable.
=item %targets
-Hash of shortcuts to be used with the B<--target> option. If the hash
-contains, for instance, the following pair of values
+Hash of target definitions to be used with the B<--target> option. The
+key is the identifier of the target, the value is the string with
+command line options. For instance, if the configuration file contains:
- 'mybox' => '--server=boot:/tftproot --serial=/dev/ttyUSB0 --grub',
+ $targets{'mybox'} = '--server=boot:/tftproot --serial=/dev/ttyUSB0 --grub',
then the following two commands are equivalent:
- ./script --server=boot:/tftproot --serial=/dev/ttyUSB0 --grub
- ./script -t mybox
+ ./myos --server=boot:/tftproot --serial=/dev/ttyUSB0 --grub
+ ./myos -t mybox
=back
Name of the novaboot configuration file to use instead of the default
one(s).
+=item NOVABOOT_CONFIG_DIR
+
+Name of the novaboot configuration directory. When not specified
+F</etc/novaboot.d> is used.
+
+=item NOVABOOT_TARGET
+
+Name of the novaboot target to use. This overrides the value of
+B<$default_target> from the configuration file and can be overriden
+with the B<--target> command line option.
+
=item NOVABOOT_BENDER
Defining this variable has the same meaning as B<--bender> option.
=head1 AUTHORS
Michal Sojka <sojka@os.inf.tu-dresden.de>
+
+=cut
+
+# LocalWords: novaboot Novaboot NOVABOOT TFTP PXE DHCP filename stty
+# LocalWords: chainloader stdout Qemu qemu preprocessing ISOLINUX bootable
+# LocalWords: config subprocesses sudo sudoers tftp dhcp IDE stdin
+# LocalWords: subdirectories TTY whitespace heredoc POSIX WvTest