#!/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 = <= $_[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 ( ) { 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 ( ) { 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 ( ) { 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 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 image-entries are treated as "optional" in order to more closely match the behavior of the real loader (i.e. C), which silently ignores missing files while reading the configuration. This may be considered a bug by experienced B users, where only those specifically marked as such are treated that way. It is planned to introduce keywords like C and C in future releases though. =head1 SEE ALSO /usr/share/doc/packages/elilo =cut