forked from pool/grub2
337 lines
8.4 KiB
Perl
337 lines
8.4 KiB
Perl
|
#!/usr/bin/perl
|
||
|
|
||
|
use strict;
|
||
|
use integer;
|
||
|
use bytes;
|
||
|
eval 'use File::Copy qw(copy move)';
|
||
|
eval 'use File::Temp qw(mkstemp mktemp)';
|
||
|
eval 'use POSIX qw(uname)';
|
||
|
eval 'use Cwd qw(realpath)';
|
||
|
|
||
|
my $device;
|
||
|
my $diskboot;
|
||
|
my $instdev;
|
||
|
my $diskboot_start;
|
||
|
my $default_backup;
|
||
|
my $default = "/etc/default/grub_installdevice";
|
||
|
my $debug = 0;
|
||
|
|
||
|
$debug = 1 if ($ARGV[0] =~ m/^(--debug|-d)$/);
|
||
|
|
||
|
sub is_part ($) {
|
||
|
my ($dev) = @_;
|
||
|
my $ret;
|
||
|
|
||
|
$dev = realpath($dev);
|
||
|
if ($dev =~ qr{/dev/(.+)}) {
|
||
|
$ret = 1 if (-e "/sys/class/block/$1/partition");
|
||
|
}
|
||
|
$ret;
|
||
|
}
|
||
|
|
||
|
sub is_abstraction ($) {
|
||
|
my ($path) = @_;
|
||
|
my @abs;
|
||
|
|
||
|
chomp( @abs = qx{grub2-probe --target=abstraction $path} );
|
||
|
die "Failed to probe $path for target abstraction\n" if ($? != 0);
|
||
|
@abs;
|
||
|
}
|
||
|
|
||
|
sub default_installdevice () {
|
||
|
my $ret;
|
||
|
|
||
|
if ( -w $default ) {
|
||
|
open( IN, "< $default") || return;
|
||
|
while ( <IN> ) {
|
||
|
chomp;
|
||
|
(m{^/dev}) && ($ret = $_, last);
|
||
|
}
|
||
|
close ( IN );
|
||
|
}
|
||
|
$ret;
|
||
|
}
|
||
|
|
||
|
sub new_installdevice ($) {
|
||
|
my ($dev) = @_;
|
||
|
my $cfg;
|
||
|
|
||
|
die unless (open( IN, "< $default"));
|
||
|
|
||
|
while ( <IN> ) {
|
||
|
if (m{^/dev}) {
|
||
|
$cfg .= "${dev}\n";
|
||
|
} else {
|
||
|
$cfg .= $_;
|
||
|
}
|
||
|
}
|
||
|
close ( IN );
|
||
|
|
||
|
my ($out, $newf) = mkstemp('/tmp/grub.installdevice.XXXXX');
|
||
|
die unless (print ( $out $cfg));
|
||
|
close ( $out );
|
||
|
|
||
|
$default_backup = mktemp("${default}.old.XXXXX");
|
||
|
copy($default, $default_backup);
|
||
|
move($newf, $default);
|
||
|
}
|
||
|
|
||
|
sub is_grub_drive ($$$) {
|
||
|
my ( $prefix, $path, $isdev ) = @_;
|
||
|
my $tgt;
|
||
|
my ($td, $tp);
|
||
|
my ($pd, $pp);
|
||
|
my $pattern = qr{\((hd[0-9]+)?,?((?:gpt|msdos)[0-9]+)?\)};
|
||
|
|
||
|
if ($isdev) {
|
||
|
chomp( $tgt = qx{grub2-probe --target=drive -d $path} );
|
||
|
} else {
|
||
|
chomp( $tgt = qx{grub2-probe --target=drive $path} );
|
||
|
}
|
||
|
|
||
|
die "Failed to probe $path for target drive\n" if ($? != 0);
|
||
|
( $tgt =~ $pattern ) && (($td, $tp) = ($1, $2)) || return ;
|
||
|
( $prefix =~ $pattern ) && (($pd, $pp) = ($1, $2)) || return ;
|
||
|
return if ($pd && $pd ne $td);
|
||
|
return 1 unless ($tp);
|
||
|
($pp eq $tp) ? 1 : 0;
|
||
|
}
|
||
|
|
||
|
sub embed_part_start ($){
|
||
|
my ($dev) = @_;
|
||
|
my @blk;
|
||
|
my $ret;
|
||
|
|
||
|
chomp (@blk = qx{lsblk --list --ascii --noheadings --output PATH,PTTYPE,PARTTYPE $dev});
|
||
|
die "Failed to get block device information for $dev\n" if ($? != 0);
|
||
|
foreach (@blk) {
|
||
|
my ($path, $pttype, $parttype) = split /\s+/;
|
||
|
if ($pttype eq 'dos') {
|
||
|
$ret = 1;
|
||
|
last;
|
||
|
} elsif ($pttype eq 'gpt' && $parttype eq '21686148-6449-6e6f-744e-656564454649') {
|
||
|
if ($path =~ qr{/dev/(.+)}) {
|
||
|
if ( -r "/sys/class/block/$1/start" ) {
|
||
|
chomp ($ret = qx{cat /sys/class/block/$1/start});
|
||
|
last;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$ret;
|
||
|
}
|
||
|
|
||
|
sub check_mbr ($) {
|
||
|
my ($dev) = @_;
|
||
|
my $devh;
|
||
|
my $mbr;
|
||
|
|
||
|
open( $devh, "< $dev" ) or die "$0: cannot open $dev: $!\n";
|
||
|
sysread( $devh, $mbr, 512 ) == 512 or die "$0: $dev: read error\n";
|
||
|
close( $devh );
|
||
|
my( $magic ) = unpack('H4', $mbr);
|
||
|
return if ($magic ne 'eb63');
|
||
|
|
||
|
my( $version ) = unpack('x128H4', $mbr);
|
||
|
return if ($version ne '0020');
|
||
|
|
||
|
my( $sector_nr ) = unpack('x92L<', $mbr);
|
||
|
return if ($sector_nr ne embed_part_start($dev));
|
||
|
|
||
|
my( $drive_nr ) = unpack('x100H2', $mbr);
|
||
|
return if ($drive_nr ne 'ff');
|
||
|
|
||
|
$sector_nr;
|
||
|
}
|
||
|
|
||
|
sub check_diskboot ($$) {
|
||
|
my ($dev, $sector_nr) = @_;
|
||
|
my $devh;
|
||
|
my $diskboot;
|
||
|
my @ret;
|
||
|
|
||
|
open($devh, "< $dev" ) or die "$0: cannot open $dev: $!\n";
|
||
|
# print "looks at sector $sector_nr of the same hard drive for core.img\n";
|
||
|
sysseek($devh, $sector_nr*512, 0) or die "$0: $dev: $!\n";
|
||
|
# grub-core/boot/i386/pc/diskboot.S
|
||
|
sysread($devh, $diskboot, 512 ) == 512 or die "$0: $dev: read error\n";
|
||
|
close($devh);
|
||
|
|
||
|
my( $magic ) = unpack('H8', $diskboot);
|
||
|
# print $magic , "\n";
|
||
|
|
||
|
# 5256be1b - upstream diskboot.S
|
||
|
# 5256be63 - trustedgrub2 1.4
|
||
|
# 5256be56 - diskboot.S with mjg TPM patches (e.g. in openSUSE Tumbleweed)
|
||
|
return if ($magic !~ m/(5256be1b|5256be63|5256be56)/);
|
||
|
|
||
|
for (1..3) {
|
||
|
my $nr;
|
||
|
my $s = 512 - 12 * $_;
|
||
|
my( $nr_low, $nr_high, $size ) = unpack("x${s}L<L<S<", $diskboot);
|
||
|
|
||
|
last unless ($nr = ($nr_high << 32) + $nr_low);
|
||
|
last unless ($size);
|
||
|
push @ret, $nr;
|
||
|
push @ret, $size;
|
||
|
}
|
||
|
|
||
|
@ret;
|
||
|
}
|
||
|
|
||
|
sub lzma_start ($$) {
|
||
|
my ($core, $size) = @_;
|
||
|
my $off;
|
||
|
my $r;
|
||
|
|
||
|
$r = ($size > 8192) ? 8192 : $size;
|
||
|
# Find the last 6 bytes of lzma_decode to find the offset of the lzma_stream:
|
||
|
$off = index( unpack( "H".($r<<1), $core ), 'd1e9dffeffff' );
|
||
|
if ($off != -1) {
|
||
|
$off >>= 1;
|
||
|
$off += 8;
|
||
|
$off = (($off + 0b1111) >> 4) << 4;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
sub decomp_lzma ($$) {
|
||
|
my ($core, $off) = @_;
|
||
|
my $comp_size;
|
||
|
my $decomp_size;
|
||
|
my $lzma;
|
||
|
my $lzmah;
|
||
|
my $unlzma;
|
||
|
|
||
|
# grub-core/boot/i386/pc/startup_raw.S
|
||
|
my $tmpf = "/tmp/lzma_grub.lzma";
|
||
|
($comp_size, $decomp_size) = unpack ("x8VV", $core);
|
||
|
$lzma = pack( "CVVx4", 0x5d, 0x00010000, $decomp_size );
|
||
|
$lzma .= substr( $core, $off, $comp_size );
|
||
|
|
||
|
open($lzmah, "> $tmpf") or die "$0: cannot open $tmpf : $!\n";
|
||
|
binmode $lzmah;
|
||
|
print $lzmah $lzma;
|
||
|
close($lzmah);
|
||
|
|
||
|
$unlzma = qx{lzcat $tmpf};
|
||
|
die if ($? != 0);
|
||
|
die "decompressed size mismatch\n" if (length($unlzma) != $decomp_size);
|
||
|
|
||
|
($unlzma, $decomp_size);
|
||
|
}
|
||
|
|
||
|
sub search_prefix (@) {
|
||
|
my ($unlzma, $decomp_size) = @_;
|
||
|
|
||
|
my ($mod_base) = unpack("x19V", $unlzma);
|
||
|
my ($mod_magic, $mod_off, $mod_sz) = unpack("x$mod_base A4 L< L<", $unlzma);
|
||
|
die "module magic mismatch\n" if ( $mod_magic ne "mimg" );
|
||
|
die "module out of bound" if ($mod_base + $mod_sz > $decomp_size);
|
||
|
my $mod_start = $mod_base + $mod_off;
|
||
|
my $mod_end = $mod_base + $mod_sz;
|
||
|
my $embed;
|
||
|
my $prefix;
|
||
|
while ($mod_start < ($mod_end - 8)) {
|
||
|
my ($type, $sz) = unpack("x${mod_start} L< L<", $unlzma);
|
||
|
last if ($mod_start + $sz > $mod_end);
|
||
|
last if ($sz < 8);
|
||
|
if ($type == 2) {
|
||
|
($embed) = unpack(join('', 'x', $mod_start + 8, 'A', $sz - 8), $unlzma);
|
||
|
} elsif ($type == 3) {
|
||
|
($prefix) = unpack(join('', 'x', $mod_start + 8, 'A', $sz - 8), $unlzma);
|
||
|
}
|
||
|
$sz = (($sz + 0b11) >> 2) << 2;
|
||
|
$mod_start += $sz;
|
||
|
}
|
||
|
|
||
|
$prefix;
|
||
|
}
|
||
|
|
||
|
sub part_to_disk ($) {
|
||
|
my ($dev) = @_;
|
||
|
my $ret;
|
||
|
|
||
|
if ($dev =~ m{/dev/disk/by-uuid/}) {
|
||
|
$dev = realpath($dev);
|
||
|
}
|
||
|
|
||
|
my @regexp = (
|
||
|
qr{(/dev/disk/(?:by-id|by-path)/.+)-part[0-9]+},
|
||
|
qr{(/dev/[a-z]+d[a-z])[0-9]+},
|
||
|
qr{(/dev/nvme[0-9]+n[0-9]+)p[0-9]+}
|
||
|
);
|
||
|
|
||
|
foreach (@regexp) {
|
||
|
if ($dev =~ $_) {
|
||
|
$ret = $1;
|
||
|
last;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$ret;
|
||
|
}
|
||
|
|
||
|
sub get_prefix ($@) {
|
||
|
my ($dev, ($sector_nr, $size)) = @_;
|
||
|
my $devh;
|
||
|
my $core;
|
||
|
my $off;
|
||
|
my $prefix;
|
||
|
|
||
|
$size <<= 9;
|
||
|
$sector_nr <<= 9;
|
||
|
|
||
|
open( $devh, "< $dev" ) or die "$0: cannot open $dev: $!\n";
|
||
|
sysseek( $devh, $sector_nr, 0) or die "$0: $dev: $!\n";
|
||
|
sysread( $devh, $core, $size ) == $size or die "$0: $dev: read error\n";
|
||
|
close( $devh );
|
||
|
|
||
|
$off = lzma_start($core, $size);
|
||
|
return if ($off == -1);
|
||
|
|
||
|
$prefix = search_prefix( decomp_lzma($core, $off) );
|
||
|
}
|
||
|
|
||
|
eval {
|
||
|
|
||
|
my @uname = uname();
|
||
|
die "machine hardware is not x86_64\n" if ($uname[4] ne 'x86_64');
|
||
|
|
||
|
die "no install device config or no permission to alter it\n" unless ($instdev = default_installdevice());
|
||
|
die "/boot is abstraction\n" if (is_abstraction("/boot"));
|
||
|
die "$instdev is NOT partition\n" unless (is_part($instdev));
|
||
|
|
||
|
chomp ( $device = qx{grub2-probe --target=disk /boot} );
|
||
|
die "no disk for /boot\n" unless ( $device );
|
||
|
|
||
|
my $sector_nr = check_mbr($device);
|
||
|
|
||
|
die "$device mbr is not used for suse grub embedding\n" unless ($sector_nr);
|
||
|
|
||
|
my @core_sectors = check_diskboot($device, $sector_nr);
|
||
|
|
||
|
die "core image is not single continuous chunk\n" if (@core_sectors != 2);
|
||
|
|
||
|
die "starting sector of startup_raw $core_sectors[0]" .
|
||
|
" did not follow diskboot $sector_nr\n" if ($core_sectors[0] != $sector_nr + 1);
|
||
|
|
||
|
my $prefix = get_prefix($device, @core_sectors);
|
||
|
|
||
|
die "$prefix is not pointing to /boot" unless ($prefix && is_grub_drive ($prefix, '/boot', 0));
|
||
|
|
||
|
my $instdisk = part_to_disk($instdev);
|
||
|
|
||
|
die "cannot determine disk device for $instdev" unless ($instdisk);
|
||
|
die "$instdisk is not grub disk" unless (is_grub_drive($prefix, $instdisk, 1));
|
||
|
|
||
|
new_installdevice($instdisk);
|
||
|
|
||
|
print "The system has been detected using grub in master boot record for booting this updated system with \$prefix=$prefix. However the $default has the install device set to the partition, $instdev. To avoid potential breakage in the application binary interface between grub image and modules, the install device of grub has been changed to use the disk device, $instdisk, to update the master boot record with new grub in order to keep up with the new binary.\n";
|
||
|
|
||
|
print "The backup of the original file is $default_backup\n";
|
||
|
|
||
|
};
|
||
|
|
||
|
print "No fixup required: $@" if ($debug && $@);
|