#!/usr/bin/perl -w # $Id: elilo.pl,v 0.14 2008/07/30 09:36:48 rw Exp $ use strict; my $C = $0; $C =~ s%^.*/%%; my $dbg = (exists( $ENV{"ELILO_DEBUG"})) ? $ENV{"ELILO_DEBUG"} : ""; my $Edition = q(@EDITION@); my $MPold = "$dbg/boot"; my $MPnew = "$dbg/boot/efi"; my $Dlibold = "$dbg@LIBEXECDIR@/elilo"; my $Dlib = "$dbg@LIBEXECDIR@/efi"; my $Fconf = "elilo.conf"; my $Sconf = "$dbg/etc/" . $Fconf; my $Reserved = qr(^(efi-mountpoint|vendor-directory|elilo-origin))o; my %Sconf = (); 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 = (); open( IN, "< $f") || return( %r ); # fixme: add failure-detection while ( ) { chomp; next unless ( m/$Reserved\s*\=\s*(.+)?\s*$/xo ); $r{$1} = $2; } close( IN); return( %r ); } sub Transfer ($$) { my( $in, $dir) = @_; my( $out, $tmp, $c, @f, %f, $opt); my $default_label = ""; my $default_loc; 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)\s*=\s*% ) { chomp; s%^(\s*(?:image|initrd)\s*=\s*)(/\S+/)?([^/\s]+)\s*$%$1$3%; my( $t, $p, $f) = ($1, $2, $3); $_ .= "\n"; if ( ! defined( $p) ) { $p = "/boot/"; } if ( ! defined( $f) ) { Warn( "$in: $.: invalid file-specification\n" . ">> $_"); $c++; } elsif ( exists( $f{$f}) ) { Info( 3, "$in: $.: file duplication skipped (previous: $f{$f})\n" . ">> $_"); } elsif ( $opt && ! -r "$dbg$p$f" ) { Info( 0, "$C: $in: $.: missing optional '$p$f' skipped\n"); } else { push @f, $dbg . "$p$f"; $f{$f} = $.; } } 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! (May be forced by appending 'ro')\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]; my $rc = ($fatal) ? 1 : 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( $rc, "$C[0]: failed to execute: $!\n"); } elsif ($? & 127) { Panic( $rc, sprintf( "$C[0]: died with signal %d, %s coredump\n", ($? & 127), ($? & 128) ? 'with' : 'without')); } elsif ( $? >> 8 != 0 ) { Panic( $rc, "$C[0]: failed\n"); } } 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) = @_; Info( 3, "### isMP($d):\n"); foreach my $f ( ("/proc/mounts", "/etc/mtab") ) { Info( 4, "#### looking in $f\n"); 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, "### no info available => 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', 'purge') && ! $Opt{'help'} ); Version() if ( $Opt{'version'} ); pod2usage(-exitstatus => 0, -verbose => 2) if ( $Opt{'man'} ); pod2usage(1) if ( $Opt{'help'} ); $test = 1 if ( $Opt{'test'} ); $verbose += $Opt{'verbose'} if ( $Opt{'verbose'} ); } # try to read variables $Sconf %Sconf = Parse( $Sconf); # check environment if ( exists( $Sconf{"efi-mountpoint"}) ) { $MP = $dbg . $Sconf{"efi-mountpoint"}; Panic( 1, "EFI partions 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 partiton 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" ); } $D = $MP . "/efi/" . $VD; Info( 1, "## Using '$D'...\n"); System( 1, "mkdir", "-p", $D) unless ( -d $D ); if ( -r $Sconf ) { # extract kernels and initrds and write fixed .conf my @F = Transfer( $Sconf, $D); # copy stuff 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( 1, "-p", "$Dlib/elilo.efi", $D); InstallFPSWA( $MP); } elsif ( $MP eq $MPold ) { Panic( 2, "$D/$Fconf: not found\n"); } else { Panic( 2, "$Sconf: not found\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: -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