mirror of
https://github.com/xcat2/xcat-core.git
synced 2026-09-10 19:46:24 +00:00
feat(copycds): build the riscv64 grub2 loader from the Ubuntu media
riscv64 nodes have no boot loader unless one reaches /tftpboot/boot/grub2, and nothing on an Ubuntu management node puts one there. The grub2 image the media carry cannot serve: it holds a built-in configuration that looks for the live filesystem, so a node that loads it drops to a grub prompt instead of reading the configuration nodeset writes. copycd now builds a netboot image from the grub2 package the media ship, and warns when it cannot, because the node has no other source for one. An image already in place is kept only when it is a whole executable image for the architecture the firmware loads and carries the prefix this boot path needs; one that is not is removed, so a rebuild that cannot run leaves nodeset reporting a missing loader rather than serving an unusable one. The media of every other architecture are untouched. Signed-off-by: Vinícius Ferrão <2031761+viniciusferrao@users.noreply.github.com>
This commit is contained in:
@@ -219,6 +219,166 @@ sub install_boot_files
|
||||
return;
|
||||
}
|
||||
|
||||
# The grub2 image on the media boots only from the media: it carries a built-in
|
||||
# configuration that looks for the live filesystem and never reads the network
|
||||
# configuration nodeset writes. A netboot image is built from the grub2 package
|
||||
# the media ships instead.
|
||||
my %MEDIA_GRUB2_BUILDS = (
|
||||
'riscv64' => {
|
||||
format => 'riscv64-efi',
|
||||
package => 'grub-efi-riscv64-bin',
|
||||
machine => 0x5064,
|
||||
},
|
||||
);
|
||||
|
||||
# Where the loader looks for the configuration nodeset writes, stored inside the image.
|
||||
my $GRUB2_PREFIX = '/boot/grub2';
|
||||
|
||||
# The modules the network path needs before it can read a configuration file.
|
||||
my @GRUB2_NETBOOT_MODULES = qw(
|
||||
efinet tftp http net normal linux echo test configfile
|
||||
search search_label search_fs_uuid search_fs_file
|
||||
gzio part_gpt part_msdos ext2 fat all_video video font terminal reboot halt
|
||||
);
|
||||
|
||||
sub install_media_grub2_loader {
|
||||
my ($path, $arch, $callback) = @_;
|
||||
|
||||
my $build = $MEDIA_GRUB2_BUILDS{$arch};
|
||||
return unless $build;
|
||||
|
||||
my $tftpdir = xCAT::TableUtils->getTftpDir();
|
||||
unless ($tftpdir) {
|
||||
_no_grub2_loader($arch, 'the TFTP directory is not known', $callback);
|
||||
return;
|
||||
}
|
||||
my $target = "$tftpdir/boot/grub2/grub2.$arch";
|
||||
return $target if _is_netboot_loader($target, $build);
|
||||
|
||||
# A file that failed the check is left where it is. The check cannot tell an image built for
|
||||
# another boot path from one this plugin did not build: the loader on the media carries the
|
||||
# same modules and differs only in the prefix. Moving a file aside on that evidence can take
|
||||
# a working loader away from every node of the architecture, so it is replaced only once a
|
||||
# working one exists, by the rename below.
|
||||
my $kept = -e $target ? 1 : 0;
|
||||
|
||||
my ($package) = glob("$path/pool/main/g/grub2/$build->{package}_*_$arch.deb");
|
||||
unless ($package && -r $package) {
|
||||
_no_grub2_loader($arch, "the media carry no $build->{package} package", $callback, $kept);
|
||||
return;
|
||||
}
|
||||
|
||||
my $workdir = tempdir(CLEANUP => 1);
|
||||
if (system('dpkg-deb', '-x', $package, $workdir) != 0) {
|
||||
_no_grub2_loader($arch, "$package could not be unpacked", $callback, $kept);
|
||||
return;
|
||||
}
|
||||
|
||||
my $moduledir = "$workdir/usr/lib/grub/$build->{format}";
|
||||
unless (-d $moduledir) {
|
||||
_no_grub2_loader($arch, "$package carries no $build->{format} modules", $callback, $kept);
|
||||
return;
|
||||
}
|
||||
|
||||
mkpath("$tftpdir/boot/grub2");
|
||||
|
||||
# Built beside the target and renamed, so an interrupted run cannot leave a partial
|
||||
# loader that nodeset would hand to every node of the architecture.
|
||||
my $partial = "$target.$$";
|
||||
my $rc = system('grub-mkimage', '-O', $build->{format}, '-d', $moduledir,
|
||||
'-p', $GRUB2_PREFIX, '-o', $partial, @GRUB2_NETBOOT_MODULES);
|
||||
unless ($rc == 0 and _is_netboot_loader($partial, $build)) {
|
||||
unlink $partial;
|
||||
_no_grub2_loader($arch, 'grub-mkimage could not build it', $callback, $kept);
|
||||
return;
|
||||
}
|
||||
chmod 0644, $partial;
|
||||
unless (rename($partial, $target)) {
|
||||
unlink $partial;
|
||||
_no_grub2_loader($arch, "it could not be renamed to $target: $!", $callback, $kept);
|
||||
return;
|
||||
}
|
||||
$callback->({ data => "Installed $target from the media" }) if $callback;
|
||||
return $target;
|
||||
}
|
||||
|
||||
# UEFI loads the loader as a PE image for one machine, so anything else -- a truncated
|
||||
# file, a stub carrying only headers, or the loader of another architecture -- cannot boot
|
||||
# a node and is replaced. The fields below are the ones an image must have to be executed
|
||||
# at all: sections to load, an entry point to jump to, and the subsystem UEFI runs.
|
||||
sub _is_uefi_image {
|
||||
my ($file, $machine) = @_;
|
||||
|
||||
my $size = -s $file;
|
||||
return 0 unless ($machine and $size);
|
||||
open(my $fh, '<', $file) or return 0;
|
||||
binmode($fh);
|
||||
|
||||
my $ok = 0;
|
||||
my ($dos, $coff, $optional);
|
||||
if (read($fh, $dos, 64) == 64
|
||||
and substr($dos, 0, 2) eq 'MZ'
|
||||
and seek($fh, unpack('V', substr($dos, 60, 4)), 0)
|
||||
and read($fh, $coff, 24) == 24
|
||||
and substr($coff, 0, 4) eq "PE\0\0"
|
||||
and unpack('v', substr($coff, 4, 2)) == $machine
|
||||
and unpack('v', substr($coff, 6, 2)) > 0
|
||||
and read($fh, $optional, 72) == 72
|
||||
and unpack('v', substr($optional, 0, 2)) == 0x20b)
|
||||
{
|
||||
my $entry = unpack('V', substr($optional, 16, 4));
|
||||
my $image = unpack('V', substr($optional, 56, 4));
|
||||
my $headers = unpack('V', substr($optional, 60, 4));
|
||||
my $system = unpack('v', substr($optional, 68, 2));
|
||||
|
||||
# 10 is the EFI application subsystem, the only one the firmware loads.
|
||||
$ok = ($entry and $image and $headers and $system == 10
|
||||
and $headers <= $size and $image <= $size);
|
||||
}
|
||||
close($fh);
|
||||
return $ok ? 1 : 0;
|
||||
}
|
||||
|
||||
# The modules a net boot cannot happen without. grub-mkimage records the name of every module
|
||||
# it embeds, so their absence says the file is not a grub2 image built for this boot path,
|
||||
# whatever its headers claim.
|
||||
my @GRUB2_REQUIRED_MODULES = qw(efinet tftp http linux normal configfile search);
|
||||
|
||||
# grub-mkimage stores the prefix inside the image, so a loader built for this boot path
|
||||
# carries the directory nodeset writes its configuration into. The image on the media has
|
||||
# the same modules but no such prefix, which is why it cannot find that configuration: a
|
||||
# file without it is replaced rather than trusted.
|
||||
sub _is_netboot_loader {
|
||||
my ($file, $build) = @_;
|
||||
|
||||
return 0 unless _is_uefi_image($file, $build->{machine});
|
||||
open(my $fh, '<', $file) or return 0;
|
||||
binmode($fh);
|
||||
my $image = do { local $/; <$fh> };
|
||||
close($fh);
|
||||
return 0 unless defined $image and index($image, $GRUB2_PREFIX) >= 0;
|
||||
for my $module (@GRUB2_REQUIRED_MODULES) {
|
||||
return 0 if index($image, "\0$module\0") < 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
# Nothing else on an Ubuntu management node installs this loader, so a node of this
|
||||
# architecture cannot boot until an administrator supplies one.
|
||||
sub _no_grub2_loader {
|
||||
my ($arch, $reason, $callback, $kept) = @_;
|
||||
|
||||
return unless $callback;
|
||||
my $consequence = $kept
|
||||
? "The grub2.$arch already in the boot/grub2 directory of the TFTP root was left alone. It "
|
||||
. "was not built for this boot path, so check that nodes of this architecture still boot."
|
||||
: "Nodes of this architecture will not boot until one is placed in the boot/grub2 "
|
||||
. "directory of the TFTP root.";
|
||||
$callback->({
|
||||
warning => [ "No grub2.$arch boot loader was installed because $reason. $consequence" ] });
|
||||
return;
|
||||
}
|
||||
|
||||
sub is_ubuntu_live_media
|
||||
{
|
||||
my $media_path = shift;
|
||||
@@ -521,6 +681,7 @@ sub copycd
|
||||
}
|
||||
|
||||
$callback->({ data => "Media copy operation successful" });
|
||||
install_media_grub2_loader($temppath, $arch, $callback);
|
||||
unless ($noosimage) {
|
||||
my @ret = xCAT::SvrUtils->update_tables_with_templates($distname, $arch, $temppath, $osdistroname, $legacyUB20);
|
||||
if ($ret[0] != 0) {
|
||||
|
||||
Reference in New Issue
Block a user