Merge pull request #5994 from ts468/grub

Add 'target' parameter for GRUB installation chain

Changed files
+163 -12
nixos
modules
system
boot
pkgs
tools
misc
grub
top-level
+29 -5
nixos/modules/system/boot/loader/grub/grub.nix
···
cfg = config.boot.loader.grub;
+
efi = config.boot.loader.efi;
+
realGrub = if cfg.version == 1 then pkgs.grub
else pkgs.grub2.override { zfsSupport = cfg.zfsSupport; };
···
then null
else realGrub;
+
grubEfi =
+
# EFI version of Grub v2
+
if (cfg.devices != ["nodev"]) && cfg.efiSupport && (cfg.version == 2)
+
then pkgs.grub2.override { zfsSupport = cfg.zfsSupport; efiSupport = cfg.efiSupport; }
+
else null;
+
f = x: if x == null then "" else "" + x;
grubConfig = pkgs.writeText "grub-config.xml" (builtins.toXML
{ splashImage = f config.boot.loader.grub.splashImage;
grub = f grub;
+
grubTarget = f grub.grubTarget;
shell = "${pkgs.stdenv.shell}";
fullVersion = (builtins.parseDrvName realGrub.name).version;
+
grubEfi = f grubEfi;
+
grubTargetEfi = if cfg.efiSupport && (cfg.version == 2) then f grubEfi.grubTarget else "";
+
inherit (efi) efiSysMountPoint canTouchEfiVariables;
inherit (cfg)
version extraConfig extraPerEntryConfig extraEntries
extraEntriesBeforeNixOS extraPrepareConfig configurationLimit copyKernels timeout
-
default devices fsIdentifier;
-
path = (makeSearchPath "bin" [
+
default devices fsIdentifier efiSupport;
+
path = (makeSearchPath "bin" ([
pkgs.coreutils pkgs.gnused pkgs.gnugrep pkgs.findutils pkgs.diffutils pkgs.btrfsProgs
-
pkgs.utillinux
-
]) + ":" + (makeSearchPath "sbin" [
+
pkgs.utillinux ] ++ (if cfg.efiSupport && (cfg.version == 2) then [pkgs.efibootmgr ] else [])
+
)) + ":" + (makeSearchPath "sbin" [
pkgs.mdadm pkgs.utillinux
]);
});
···
type = types.bool;
description = ''
Whether grub should be build against libzfs.
+
ZFS support is only available for GRUB v2.
+
This option is ignored for GRUB v1.
+
'';
+
};
+
+
efiSupport = mkOption {
+
default = false;
+
type = types.bool;
+
description = ''
+
Whether grub should be build with EFI support.
+
EFI support is only available for GRUB v2.
+
This option is ignored for GRUB v1.
'';
};
···
if cfg.devices == [] then
throw "You must set the option ‘boot.loader.grub.device’ to make the system bootable."
else
-
"PERL5LIB=${makePerlPath (with pkgs.perlPackages; [ FileSlurp XMLLibXML XMLSAX ])} " +
+
"PERL5LIB=${makePerlPath (with pkgs.perlPackages; [ FileSlurp XMLLibXML XMLSAX ListCompare ])} " +
(if cfg.enableCryptodisk then "GRUB_ENABLE_CRYPTODISK=y " else "") +
"${pkgs.perl}/bin/perl ${./install-grub.pl} ${grubConfig}";
+113 -7
nixos/modules/system/boot/loader/grub/install-grub.pl
···
use File::stat;
use File::Copy;
use File::Slurp;
+
require List::Compare;
use POSIX;
use Cwd;
···
my $grub = get("grub");
my $grubVersion = int(get("version"));
+
my $grubTarget = get("grubTarget");
my $extraConfig = get("extraConfig");
my $extraPrepareConfig = get("extraPrepareConfig");
my $extraPerEntryConfig = get("extraPerEntryConfig");
···
my $timeout = int(get("timeout"));
my $defaultEntry = int(get("default"));
my $fsIdentifier = get("fsIdentifier");
+
my $grubEfi = get("grubEfi");
+
my $grubTargetEfi = get("grubTargetEfi");
+
my $canTouchEfiVariables = get("canTouchEfiVariables");
+
my $efiSysMountPoint = get("efiSysMountPoint");
$ENV{'PATH'} = get("path");
die "unsupported GRUB version\n" if $grubVersion != 1 && $grubVersion != 2;
···
# Skip the read-only bind-mount on /nix/store.
next if $mountPoint eq "/nix/store" && (grep { $_ eq "rw" } @superOptions) && (grep { $_ eq "ro" } @mountOptions);
+
# Skip mount point generated by systemd-efi-boot-generator?
+
next if $fsType eq "autofs";
# Ensure this matches the intended directory
next unless PathInMount($dir, $mountPoint);
···
}
-
# Install GRUB if the version changed from the last time we installed
-
# it. FIXME: shouldn't we reinstall if ‘devices’ changed?
-
my $prevVersion = readFile("/boot/grub/version") // "";
-
if (($ENV{'NIXOS_INSTALL_GRUB'} // "") eq "1" || get("fullVersion") ne $prevVersion) {
+
#
+
# Install GRUB if the parameters changed from the last time we installed it.
+
#
+
+
struct(GrubState => {
+
version => '$',
+
efi => '$',
+
devices => '$',
+
efiMountPoint => '$',
+
});
+
sub readGrubState {
+
my $defaultGrubState = GrubState->new(version => "", efi => "", devices => "", efiMountPoint => "" );
+
open FILE, "</boot/grub/state" or return $defaultGrubState;
+
local $/ = "\n";
+
my $version = <FILE>;
+
chomp($version);
+
my $efi = <FILE>;
+
chomp($efi);
+
my $devices = <FILE>;
+
chomp($devices);
+
my $efiMountPoint = <FILE>;
+
chomp($efiMountPoint);
+
close FILE;
+
my $grubState = GrubState->new(version => $version, efi => $efi, devices => $devices, efiMountPoint => $efiMountPoint );
+
return $grubState
+
}
+
+
sub getDeviceTargets {
+
my @devices = ();
foreach my $dev ($dom->findnodes('/expr/attrs/attr[@name = "devices"]/list/string/@value')) {
$dev = $dev->findvalue(".") or die;
+
push(@devices, $dev);
+
}
+
return @devices;
+
}
+
+
# check whether to install GRUB EFI or not
+
sub getEfiTarget {
+
if (($grub ne "") && ($grubEfi ne "")) {
+
# EFI can only be installed when target is set;
+
# A target is also required then for non-EFI grub
+
if (($grubTarget eq "") || ($grubTargetEfi eq "")) { die }
+
else { return "both" }
+
} elsif (($grub ne "") && ($grubEfi eq "")) {
+
# TODO: It would be safer to disallow non-EFI grub installation if no taget is given.
+
# If no target is given, then grub auto-detects the target which can lead to errors.
+
# E.g. it seems as if grub would auto-detect a EFI target based on the availability
+
# of a EFI partition.
+
# However, it seems as auto-detection is currently relied on for non-x86_64 and non-i386
+
# architectures in NixOS. That would have to be fixed in the nixos modules first.
+
return "no"
+
} elsif (($grub eq "") && ($grubEfi ne "")) {
+
# EFI can only be installed when target is set;
+
if ($grubTargetEfi eq "") { die }
+
else {return "only" }
+
} else {
+
# at least one grub target has to be given
+
die
+
}
+
}
+
+
my @deviceTargets = getDeviceTargets();
+
my $efiTarget = getEfiTarget();
+
my $prevGrubState = readGrubState();
+
my @prevDeviceTargets = split/:/, $prevGrubState->devices;
+
+
my $devicesDiffer = scalar (List::Compare->new( '-u', '-a', \@deviceTargets, \@prevDeviceTargets)->get_symmetric_difference() );
+
my $versionDiffer = (get("fullVersion") eq \$prevGrubState->version);
+
my $efiDiffer = ($efiTarget eq \$prevGrubState->efi);
+
my $efiMountPointDiffer = ($efiSysMountPoint eq \$prevGrubState->efiMountPoint);
+
my $requireNewInstall = $devicesDiffer || $versionDiffer || $efiDiffer || $efiMountPointDiffer || (($ENV{'NIXOS_INSTALL_GRUB'} // "") eq "1");
+
+
+
# install non-EFI GRUB
+
if (($requireNewInstall != 0) && ($efiTarget eq "no" || $efiTarget eq "both")) {
+
foreach my $dev (@deviceTargets) {
next if $dev eq "nodev";
print STDERR "installing the GRUB $grubVersion boot loader on $dev...\n";
-
system("$grub/sbin/grub-install", "--recheck", Cwd::abs_path($dev)) == 0
-
or die "$0: installation of GRUB on $dev failed\n";
+
if ($grubTarget eq "") {
+
system("$grub/sbin/grub-install", "--recheck", Cwd::abs_path($dev)) == 0
+
or die "$0: installation of GRUB on $dev failed\n";
+
} else {
+
system("$grub/sbin/grub-install", "--recheck", "--target=$grubTarget", Cwd::abs_path($dev)) == 0
+
or die "$0: installation of GRUB on $dev failed\n";
+
}
}
-
writeFile("/boot/grub/version", get("fullVersion"));
+
}
+
+
+
# install EFI GRUB
+
if (($requireNewInstall != 0) && ($efiTarget eq "only" || $efiTarget eq "both")) {
+
print STDERR "installing the GRUB $grubVersion EFI boot loader into $efiSysMountPoint...\n";
+
if ($canTouchEfiVariables eq "true") {
+
system("$grubEfi/sbin/grub-install", "--recheck", "--target=$grubTargetEfi", "--efi-directory=$efiSysMountPoint") == 0
+
or die "$0: installation of GRUB EFI into $efiSysMountPoint failed\n";
+
} else {
+
system("$grubEfi/sbin/grub-install", "--recheck", "--target=$grubTargetEfi", "--efi-directory=$efiSysMountPoint", "--no-nvram") == 0
+
or die "$0: installation of GRUB EFI into $efiSysMountPoint failed\n";
+
}
+
}
+
+
+
# update GRUB state file
+
if ($requireNewInstall != 0) {
+
open FILE, ">/boot/grub/state" or die "cannot create /boot/grub/state: $!\n";
+
print FILE get("fullVersion"), "\n" or die;
+
print FILE $efiTarget, "\n" or die;
+
print FILE join( ":", @deviceTargets ), "\n" or die;
+
print FILE $efiSysMountPoint, "\n" or die;
+
close FILE or die;
}
+13
pkgs/tools/misc/grub/2.0x.nix
···
with stdenv.lib;
let
+
pcSystems = {
+
"i686-linux".target = "i386";
+
"x86_64-linux".target = "i386";
+
};
+
efiSystems = {
"i686-linux".target = "i386";
"x86_64-linux".target = "x86_64";
};
canEfi = any (system: stdenv.system == system) (mapAttrsToList (name: _: name) efiSystems);
+
inPCSystems = any (system: stdenv.system == system) (mapAttrsToList (name: _: name) pcSystems);
version = "2.02-git-1de3a4";
···
configureFlags = optional zfsSupport "--enable-libzfs"
++ optionals efiSupport [ "--with-platform=efi" "--target=${efiSystems.${stdenv.system}.target}" "--program-prefix=" ];
+
+
# save target that grub is compiled for
+
grubTarget = if efiSupport
+
then "${efiSystems.${stdenv.system}.target}-efi"
+
else if inPCSystems
+
then "${pcSystems.${stdenv.system}.target}-pc"
+
else "";
doCheck = false;
enableParallelBuilding = true;
+8
pkgs/top-level/perl-packages.nix
···
};
};
+
ListCompare = buildPerlPackage {
+
name = "List-Compare-1.18";
+
src = fetchurl {
+
url = mirror://cpan/authors/id/J/JK/JKEENAN/List-Compare-0.39.tar.gz;
+
sha256 = "1v4gn176faanzf1kr9axdp1220da7nkvz0d66mnk34nd0skjjxcl";
+
};
+
};
+
ArchiveCpio = buildPerlPackage {
name = "Archive-Cpio-0.09";
src = fetchurl {