SHA256
1
0
forked from pool/elilo
elilo/elilo.pl

495 lines
12 KiB
Perl

#!/usr/bin/perl -w
# $Id: elilo.pl,v 0.21 2010/12/10 20:31:09 rw Exp $
use strict;
my $C = $0; $C =~ s{^.*/}{};
my $dbg = (exists( $ENV{"ELILO_DEBUG"})) ? $ENV{"ELILO_DEBUG"} : "";
my $Edition = q(@EDITION@);
my $Arch = q(@ARCH@);
my $LibD = q(@LIBEXECDIR@);
my $MPold = "$dbg/boot";
my $MPnew = "$dbg/boot/efi";
my $Dlibold = "$dbg$LibD/elilo";
my $Dlib = "$dbg$LibD/efi";
my $Fconf = "elilo.conf";
my $Sconf = "$dbg/etc/" . $Fconf;
my $Reserved = qr(^(efi-mountpoint|vendor-directory|elilo-origin|precious))o;
my %Sconf = ();
my $keep = -1;
my $test = 0;
my $verbose = 0;
my $warn = 0;
my $optional = 1;
my $MP = ""; # Mount-Point for EFI/FAT partition
my $VD = "SuSE"; # vendor-specific directory in $MP/efi
my $D = ""; # will be $MP.$VD
my %Labels = ();
my $Disclaimer = <<EoD;
# This file has been transformed by /sbin/elilo.
# Please do NOT edit here -- edit /etc/elilo.conf instead!
# Otherwise your changes will be lost e.g. during kernel-update.
#
EoD
$| = 1;
sub Version() {
my $v = q($Revision: 0.21 $ );
$v =~ s/^\$ Rev.*:\ ([0-9.]+)\ \$\ /$1/x;
$v .= " (part of elilo-$Edition)" if ( $Edition ne "\@EDITION\@" );
print "$C version $v\n";
exit( 0 );
}
sub Info ($$) {
print STDERR $_[1] if ( $verbose >= $_[0] );
}
sub Warn ($) {
print STDERR "$C: Warning: $_[0]";
$warn ++;
}
sub Panic ($$) {
print STDERR "$C: $_[1]";
exit( $_[0]) if ( $_[0] );
$warn ++;
print STDERR "...trying to proceed anyway!\n";
}
sub Parse ($) {
my( $f) = @_;
my %r = ();
Info( 3, "### Parse '$f'\n");
open( IN, "< $f") || return( %r );
# fixme: add failure-detection
while ( <IN> ) {
chomp;
next unless ( m/$Reserved\s*(?:\=\s*(.+))?\s*$/xo );
$r{$1} = (defined($2)) ? $2 : "true";
Info( 3, ">>> $1 = '$r{$1}'\n");
}
close( IN);
return( %r );
}
sub Transfer ($$) {
my( $in, $dir) = @_;
my( $out, $tmp, $c, @f, %f, $opt);
my $default_label = "";
my $default_loc;
my $image = "";
my @Out = ();
$out = "$dir/$Fconf";
$tmp = "$out.tmp";
$opt = $optional;
open( IN, "< $in") || Panic(1, "$in: failed to open: $!\n");
Info( 1, "## filter '$in' to");
if ( ! $test ) {
Info( 1, " '$tmp'\n");
open(OUT, "> $tmp") || Panic( 1, "$tmp: failed to create: $!\n");
push @Out, $Disclaimer;
} elsif ( $verbose >= 2 ) {
Info( 1, " STDOUT\n");
open( OUT, ">&STDOUT");
push @Out, $Disclaimer unless ( $verbose < 3 );
} else {
Info( 1, " /dev/null\n");
open( OUT, ">/dev/null");
}
while ( <IN> ) {
next if ( m/$Reserved/xo );
if ( m{^\s*(?:image|initrd|vmm)\s*=\s*} ) {
chomp;
s{^(\s*(image|initrd|vmm)\s*=\s*)(/\S+/)?([^/\s]+)\s*$}{$1$4};
my( $t, $p, $f) = ($2, $3, $4);
#Info( 0, "$C: $in: $.: t=$t p=$p f=$f\n");
$_ .= "\n";
if ( ! defined( $p) ) {
$p = "/boot/";
}
$image = "" if ( $t eq "image" );
if ( ! defined( $f) ) {
Warn( "$in: $.: invalid file-specification\n" .
">> $_");
$c++;
} elsif ( $opt && ! -r "$dbg$p$f" ) {
Info( 0, "$C: $in: $.: missing optional '$p$f' skipped\n");
} elsif ( exists( $f{$f}) ) {
$image = $dbg . "$p$f" if ( $t eq "image" );
Info( 3, "$in: $.: file duplication skipped (previous: $f{$f})\n" .
">> $_");
} else {
$image = $dbg . "$p$f" if ( $t eq "image" );
push @f, $dbg . "$p$f";
$f{$f} = $.;
}
} elsif ( $image && m{^(\s*description\s*=\s*")([^"]+)(".*)$} ) {
my( $p, $d, $s) = ($1, $2, $3);
my $t = "";
if ( $d =~ m{\%L} ) {
if ( -l $image ) {
($t = readlink( $image)) =~ s{^vmlinuz-}{};
} else {
#($t = $image) =~ s{^.*vmlinux-}{};
$t = "no symlink";
}
Info( 2, " \%L => '$t'\n");
$d =~ s{\%L}{$t};
} elsif ( $d =~ m{\%G} ) {
my $cmd = "/sbin/get_kernel_version";
if ( -x $cmd ) {
chomp( $t = `$cmd $image`);
} else {
$t = "";
}
$d =~ s{\%G}{$t};
Info( 2, " \%G => '$t'\n");
}
$_ = $p . $d . $s . "\n";
} elsif (m{^(\s*label\s*=\s*)(\S+)}) {
my ($pre, $label) = ($1, $2);
if (exists $Labels{$label}) {
my $t = 1;
my $l = $label;
while ( exists $Labels{$l} ) {
$l = sprintf "%s.%d", $label, $t++;
}
Warn( "duplicate label '$label', replaced with '$l'\n");
$label = $l;
$_ = $pre . $l . "\n";
}
$Labels{$label} = 1;
} elsif (m{^\s*default\s*=\s*(\S+)}) {
$default_label = $1;
$default_loc = $#Out + 1;
} elsif (m{^\s*read-only\s*$}) {
$_ = "#read-only # Deprecated!" .
" (Add comment in '$Sconf' to overrule.)" .
"\n";
Info( 2, " $in: $.: deprecated 'read-only' ignored.\n");
} elsif (m{^\s*relocatable\s*$} && $Arch =~ m{86}) {
$_ = "#relocatable # Unsupported on this architecture!\n" .
"# (May be forced by adding a comment in '$Sconf'.)\n";
Info( 2, " $in: $.: unsupported 'relocatable' ignored.\n");
}
push @Out, $_;
}
if ($default_label ne "" && !exists $Labels{$default_label}) {
$Out[$default_loc] = "#" . $Out[$default_loc];
Warn( "undefined default label '$default_label' discarded\n");
}
print OUT @Out;
close( OUT);
close( IN);
Info( 2, "## end of $in\n") unless $test;
foreach ( @f ) {
if ( ! -r $_ ) {
Warn( "$_: not found\n");
$c++;
}
}
if ( $c ) {
Info( 2, "> unlink( $tmp)\n");
unlink( $tmp);
Panic( 2, "$in: broken references\n");
}
if ( ! $test ) {
Info( 1, "> unlink( $out)\n");
unlink( $out);
Info( 1, "> rename( $tmp, $out)\n");
rename( $tmp, $out);
# fixme: add failure-detection
}
return( @f );
}
sub System($@) {
my( $fatal, @C) = @_;
my $cmd = $C[0];
foreach my $c ( @C[1..$#C] ) {
if ( $c =~ /\s/ ) {
$cmd .= " '$c'";
} else {
$cmd .= " $c";
}
}
Info( 1, "> $cmd\n");
return 0 if ( $test );
system @C;
if ($? == -1) {
Panic( $fatal, "$C[0]: failed to execute: $!\n");
} elsif ($? & 127) {
Panic( $fatal, sprintf( "$C[0]: died with signal %d, %s coredump\n",
($? & 127), ($? & 128) ? 'with' : 'without'));
} elsif ( $? >> 8 != 0 ) {
Panic( $fatal, "$C[0]: failed\n");
}
}
sub Purge($) {
my( $d) = @_;
if ( $keep > 0) {
Info( 1, "## skip removal of old files from '$d'\n");
return 0;
}
Info( 1, "## remove old files from '$d'\n");
my @F = glob( "$d/*");
foreach my $f ( @F ) {
next unless ( $f =~ m{.*/((vm|)linu(x|z)|initrd)(|[-.]\S+)$} );
Info( 1, "> rm $f\n");
unlink( $f) unless ($test);
}
}
sub Install($$$$) {
my( $f, $o, $s, $d) = @_;
my @C = ( "install", $o, $s, $d);
if ( $o eq "-p" ) {
@C = ( "cp", "--preserve=timestamps", $s, $d);
}
System( $f, @C);
}
sub InstallFPSWA($) {
my ( $d) = @_;
my $Dfpswa = $Dlib;
$d .= "/efi/Intel Firmware";
$Dfpswa = $Dlibold unless ( -r "$Dfpswa/fpswa.efi" );
return 0 unless ( -r "$Dfpswa/fpswa.efi" );
my $head = "## fpswa: Floating Point Software Assist\n";
if ( -d $d && -r "$d/fpswa.efi" ) {
# check, if we need to update and failing that do nothing?!
my $c = "$Dfpswa/fpswa-cmp-version";
if ( -x $c ) {
my $chk = `$c "$d/fpswa.efi" "$Dfpswa/fpswa.efi"`;
if ( $chk =~ /older/ ) {
Info( 1, $head .
"## Update '$d/fpswa.efi'.\n");
Info( 2,
"## $chk");
Install( 0, "-p", "$Dfpswa/fpswa.efi", $d);
} else {
Info( 1, $head .
"## Do NOT update '$d/fpswa.efi'.\n");
Info( 2,
"## $chk");
}
} else {
use File::Compare;
if ( compare( "$d/fpswa.efi", "$Dfpswa/fpswa.efi") == 0 ) {
Info( 2, $head .
"## Already installed.\n");
} else {
Info( 1, $head .
"## Unable to compare versions.\n" .
"## Installation skipped!\n");
}
}
} else {
Info( 1, $head . "## Install 'fpswa.efi' to '$d'.\n");
System( 0, "mkdir", $d) unless ( -d $d );
Install( 0, "-p", "$Dfpswa/fpswa.efi", $d);
}
}
sub isMP($) {
my ( $d) = @_;
my @I = ("/proc/mounts", "/etc/mtab");
Info( 3, "### isMP($d):");
foreach my $f ( @I ) {
open( IN, "< $f") || next;
while ( <IN> ) {
chomp;
my @F = split;
if ( $F[1] eq $d ) {
Info( 3, " found in '$f' line $. => true\n");
close( IN);
return( 1);
}
}
close( IN);
Info( 3, " not found in '$f' => false\n");
return( 0);
}
Info( 3, " not found in [" . join( ", ", @I) . "] => false\n");
return( 0);
}
{
use Getopt::Long;
use Pod::Usage;
$Getopt::Long::debug = 0;
$Getopt::Long::ignorecase = 0;
$Getopt::Long::bundling = 1;
$Getopt::Long::passthrough = 0;
my %Opt = ();
pod2usage(2) unless ( GetOptions( \%Opt,
'help|h', 'man|m', 'version|V', 'verbose|v+',
'test|t', 'keep|k', 'purge|K') &&
( ! $Opt{'purge'} || ! $Opt{'keep'} ) &&
! $Opt{'help'} );
Version() if ( $Opt{'version'} );
pod2usage(-exitstatus => 0, -verbose => 2) if ( $Opt{'man'} );
pod2usage(1) if ( $Opt{'help'} );
$test = 1 if ( $Opt{'test'} );
$keep = 0 if ( $Opt{'purge'} );
$keep = 1 if ( $Opt{'keep'} );
$verbose += $Opt{'verbose'} if ( $Opt{'verbose'} );
}
# run-time init
if ( $Arch =~ m{ARCH} ) {
chomp( $Arch = qx{uname -m});
Info( 3, "### Arch: '$Arch'\n");
}
if ( $Dlib =~ m{LIBEXECDIR} ) {
$Dlib = "$dbg/usr/lib" . (($Arch eq "x86_64") ? "64/" : "/");
$Dlibold = $Dlib . "elilo";
$Dlib = $Dlib . "efi";
$Dlib = $Dlibold if (! -r "$Dlib/elilo.efi" && -r "$Dlibold/elilo.efi");
Info( 3, "### Dlib: '$Dlib'\n");
}
# try to read variables from $Sconf
%Sconf = Parse( $Sconf);
# check environment
if ( exists( $Sconf{"efi-mountpoint"}) ) {
$MP = $dbg . $Sconf{"efi-mountpoint"};
Panic( 2, "EFI partition specification in $Sconf invalid.\n")
unless ( -d $MP ); # or is it "$MP/efi"?
} elsif ( -d $MPnew . "/efi/" . $VD || isMP($MPnew) ) {
$MP = $MPnew;
} elsif ( -d $MPold . "/efi/" . $VD ) {
$MP = $MPold;
} else {
Info( 1, "## Neither new ($MPnew/efi/$VD) nor old ($MPold/efi/$VD)?\n");
Panic( 2, "EFI partition not found.\n");
}
Info( 2, "## Mount-point '$MP'...\n");
if ( exists( $Sconf{"vendor-directory"}) ) {
$VD = $Sconf{"vendor-directory"};
Info( 1, "## Don't forget: '$VD != SuSE'--NVRAM (efibootmgr) issue!\n")
unless ( $VD eq "SuSE" );
}
if ( exists( $Sconf{"precious"}) && $keep < 0 ) {
$keep = 1;
}
$D = $MP . "/efi/" . $VD;
Info( 1, "## Using '$D'...\n");
System( 2, "mkdir", "-p", $D) unless ( -d $D );
if ( -r $Sconf ) {
# extract kernels, etc. and write fixed .conf
my @F = Transfer( $Sconf, $D);
# remove old files
Purge( $D);
# copy stuff
Info( 1, "## copy new files to '$D'\n");
unshift @F, "$Dlib/elilo.efi";
foreach ( @F ) {
Install( 0, "-p", $_, $D);
}
# take care of FPSWA
InstallFPSWA( $MP);
} elsif ( $MP eq $MPold && -r "$D/$Fconf" ) {
# assume old setup with only '/vmlinuz' and '/initrd'
Install( 2, "-p", "$Dlib/elilo.efi", $D);
InstallFPSWA( $MP);
} elsif ( $MP eq $MPold ) {
Panic( 2, "$D/$Fconf: not found\n");
} elsif ( ! -e $Sconf ) {
Panic( 2, "$Sconf: not found\n");
} else {
Panic( 2, "$Sconf: not readable\n");
}
if ( $warn > 0 ) {
Panic( 1, sprintf("%d warning%s encountered.\n", $warn, ($warn==1)?"":"s"));
}
exit( 0);
__END__
=head1 NAME
elilo - Installer for the EFI Linux Loader
=head1 SYNOPSIS
/sbin/elilo [options]
Options:
-k --keep don't purge old files
-t --test test only
-v --verbose increase verbosity
-h --help brief help message
--man full documentation
-V --version display version
=head1 OPTIONS
=over 8
=item B<--test>
Test only. Do not really write anything, no new boot configuration nor
kernel/initrd images.
Use together with B<-v> to find out what B<elilo> is about to do.
=item B<--verbose>
Increase level of verbosity.
=item B<--help>
Print a brief help message and exits.
=item B<--man>
Prints the manual page and exits.
=item B<--version>
Prints the version information and exits.
=back
=head1 DESCRIPTION
This program will perform all steps to transfer the
necessary parts to the appropriate locations...
=head1 LIMITATIONS
For now, I<all> image-entries are treated as "optional" in
order to more closely match the behavior of the real
loader (i.e. C<elilo.efi>), which silently ignores missing files
while reading the configuration.
This may be considered a bug by experienced B<LILO> users,
where only those specifically marked as such are treated that way.
It is planned to introduce keywords like C<mandatory> and C<optional>
in future releases though.
=head1 SEE ALSO
/usr/share/doc/packages/elilo
=cut