diff options
author | Niklas Hambüchen <mail@nh2.me> | 2020-07-06 22:08:31 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-07-06 22:08:31 +0200 |
commit | d676d5d11988e5c40c15ac92cc6631de7cc63795 (patch) | |
tree | 066d83ced785954c652afebb88f929adeab0c26e | |
parent | d4d9d9c552a769fc538ed35dfe9674932b705af1 (diff) | |
parent | a90ae331ecc3b3410379d61cf95828dde2b5c4a6 (diff) | |
download | nixpkgs-d676d5d11988e5c40c15ac92cc6631de7cc63795.tar nixpkgs-d676d5d11988e5c40c15ac92cc6631de7cc63795.tar.gz nixpkgs-d676d5d11988e5c40c15ac92cc6631de7cc63795.tar.bz2 nixpkgs-d676d5d11988e5c40c15ac92cc6631de7cc63795.tar.lz nixpkgs-d676d5d11988e5c40c15ac92cc6631de7cc63795.tar.xz nixpkgs-d676d5d11988e5c40c15ac92cc6631de7cc63795.tar.zst nixpkgs-d676d5d11988e5c40c15ac92cc6631de7cc63795.zip |
Merge pull request #85895 from nh2/extra-grub-install-flags
grub: Add `boot.loader.grub.extraGrubInstallArgs` option
-rw-r--r-- | nixos/modules/system/boot/loader/grub/grub.nix | 30 | ||||
-rw-r--r-- | nixos/modules/system/boot/loader/grub/install-grub.pl | 90 |
2 files changed, 92 insertions, 28 deletions
diff --git a/nixos/modules/system/boot/loader/grub/grub.nix b/nixos/modules/system/boot/loader/grub/grub.nix index b760c3f96dd..49e73588ed6 100644 --- a/nixos/modules/system/boot/loader/grub/grub.nix +++ b/nixos/modules/system/boot/loader/grub/grub.nix @@ -61,6 +61,7 @@ let inherit (efi) canTouchEfiVariables; inherit (cfg) version extraConfig extraPerEntryConfig extraEntries forceInstall useOSProber + extraGrubInstallArgs extraEntriesBeforeNixOS extraPrepareConfig configurationLimit copyKernels default fsIdentifier efiSupport efiInstallAsRemovable gfxmodeEfi gfxmodeBios gfxpayloadEfi gfxpayloadBios; path = with pkgs; makeBinPath ( @@ -298,6 +299,33 @@ in ''; }; + extraGrubInstallArgs = mkOption { + default = [ ]; + example = [ "--modules=nativedisk ahci pata part_gpt part_msdos diskfilter mdraid1x lvm ext2" ]; + type = types.listOf types.str; + description = '' + Additional arguments passed to <literal>grub-install</literal>. + + A use case for this is to build specific GRUB2 modules + directly into the GRUB2 kernel image, so that they are available + and activated even in the <literal>grub rescue</literal> shell. + + They are also necessary when the BIOS/UEFI is bugged and cannot + correctly read large disks (e.g. above 2 TB), so GRUB2's own + <literal>nativedisk</literal> and related modules can be used + to use its own disk drivers. The example shows one such case. + This is also useful for booting from USB. + See the + <link xlink:href="http://git.savannah.gnu.org/cgit/grub.git/tree/grub-core/commands/nativedisk.c?h=grub-2.04#n326"> + GRUB source code + </link> + for which disk modules are available. + + The list elements are passed directly as <literal>argv</literal> + arguments to the <literal>grub-install</literal> program, in order. + ''; + }; + extraPerEntryConfig = mkOption { default = ""; example = "root (hd0)"; @@ -669,7 +697,7 @@ in in pkgs.writeScript "install-grub.sh" ('' #!${pkgs.runtimeShell} set -e - export PERL5LIB=${with pkgs.perlPackages; makePerlPath [ FileSlurp XMLLibXML XMLSAX XMLSAXBase ListCompare ]} + export PERL5LIB=${with pkgs.perlPackages; makePerlPath [ FileSlurp XMLLibXML XMLSAX XMLSAXBase ListCompare JSON ]} ${optionalString cfg.enableCryptodisk "export GRUB_ENABLE_CRYPTODISK=y"} '' + flip concatMapStrings cfg.mirroredBoots (args: '' ${pkgs.perl}/bin/perl ${install-grub-pl} ${grubConfig args} $@ diff --git a/nixos/modules/system/boot/loader/grub/install-grub.pl b/nixos/modules/system/boot/loader/grub/install-grub.pl index 918a66866e9..b788e427dff 100644 --- a/nixos/modules/system/boot/loader/grub/install-grub.pl +++ b/nixos/modules/system/boot/loader/grub/install-grub.pl @@ -8,6 +8,7 @@ use File::stat; use File::Copy; use File::Slurp; use File::Temp; +use JSON; require List::Compare; use POSIX; use Cwd; @@ -20,6 +21,16 @@ my $dom = XML::LibXML->load_xml(location => $ARGV[0]); sub get { my ($name) = @_; return $dom->findvalue("/expr/attrs/attr[\@name = '$name']/*/\@value"); } +sub getList { + my ($name) = @_; + my @list = (); + foreach my $entry ($dom->findnodes("/expr/attrs/attr[\@name = '$name']/list/string/\@value")) { + $entry = $entry->findvalue(".") or die; + push(@list, $entry); + } + return @list; +} + sub readFile { my ($fn) = @_; local $/ = undef; open FILE, "<$fn" or return undef; my $s = <FILE>; close FILE; @@ -241,7 +252,7 @@ if ($grubVersion == 1) { timeout $timeout "; if ($splashImage) { - copy $splashImage, "$bootPath/background.xpm.gz" or die "cannot copy $splashImage to $bootPath\n"; + copy $splashImage, "$bootPath/background.xpm.gz" or die "cannot copy $splashImage to $bootPath: $!\n"; $conf .= "splashimage " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/background.xpm.gz\n"; } } @@ -319,7 +330,7 @@ else { "; if ($font) { - copy $font, "$bootPath/converted-font.pf2" or die "cannot copy $font to $bootPath\n"; + copy $font, "$bootPath/converted-font.pf2" or die "cannot copy $font to $bootPath: $!\n"; $conf .= " insmod font if loadfont " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/converted-font.pf2; then @@ -347,7 +358,7 @@ else { background_color '$backgroundColor' "; } - copy $splashImage, "$bootPath/background$suffix" or die "cannot copy $splashImage to $bootPath\n"; + copy $splashImage, "$bootPath/background$suffix" or die "cannot copy $splashImage to $bootPath: $!\n"; $conf .= " insmod " . substr($suffix, 1) . " if background_image --mode '$splashMode' " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/background$suffix; then @@ -381,8 +392,8 @@ sub copyToKernelsDir { # kernels or initrd if this script is ever interrupted. if (! -e $dst) { my $tmp = "$dst.tmp"; - copy $path, $tmp or die "cannot copy $path to $tmp\n"; - rename $tmp, $dst or die "cannot rename $tmp to $dst\n"; + copy $path, $tmp or die "cannot copy $path to $tmp: $!\n"; + rename $tmp, $dst or die "cannot rename $tmp to $dst: $!\n"; } $copied{$dst} = 1; return ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/kernels/$name"; @@ -405,10 +416,10 @@ sub addEntry { # Make sure initrd is not world readable (won't work if /boot is FAT) umask 0137; my $initrdSecretsPathTemp = File::Temp::mktemp("$initrdSecretsPath.XXXXXXXX"); - system("$path/append-initrd-secrets", $initrdSecretsPathTemp) == 0 or die "failed to create initrd secrets\n"; + system("$path/append-initrd-secrets", $initrdSecretsPathTemp) == 0 or die "failed to create initrd secrets: $!\n"; # Check whether any secrets were actually added if (-e $initrdSecretsPathTemp && ! -z _) { - rename $initrdSecretsPathTemp, $initrdSecretsPath or die "failed to move initrd secrets into place\n"; + rename $initrdSecretsPathTemp, $initrdSecretsPath or die "failed to move initrd secrets into place: $!\n"; $copied{$initrdSecretsPath} = 1; $initrd .= " " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/kernels/$initrdName-secrets"; } else { @@ -575,7 +586,7 @@ if (get("useOSProber") eq "true") { } # Atomically switch to the new config -rename $tmpFile, $confFile or die "cannot rename $tmpFile to $confFile\n"; +rename $tmpFile, $confFile or die "cannot rename $tmpFile to $confFile: $!\n"; # Remove obsolete files from $bootPath/kernels. @@ -596,9 +607,12 @@ struct(GrubState => { efi => '$', devices => '$', efiMountPoint => '$', + extraGrubInstallArgs => '@', }); +# If you add something to the state file, only add it to the end +# because it is read line-by-line. sub readGrubState { - my $defaultGrubState = GrubState->new(name => "", version => "", efi => "", devices => "", efiMountPoint => "" ); + my $defaultGrubState = GrubState->new(name => "", version => "", efi => "", devices => "", efiMountPoint => "", extraGrubInstallArgs => () ); open FILE, "<$bootPath/grub/state" or return $defaultGrubState; local $/ = "\n"; my $name = <FILE>; @@ -611,24 +625,34 @@ sub readGrubState { chomp($devices); my $efiMountPoint = <FILE>; chomp($efiMountPoint); + # Historically, arguments in the state file were one per each line, but that + # gets really messy when newlines are involved, structured arguments + # like lists are needed (they have to have a separator encoding), or even worse, + # when we need to remove a setting in the future. Thus, the 6th line is a JSON + # object that can store structured data, with named keys, and all new state + # should go in there. + my $jsonStateLine = <FILE>; + # For historical reasons we do not check the values above for un-definedness + # (that is, when the state file has too few lines and EOF is reached), + # because the above come from the first version of this logic and are thus + # guaranteed to be present. + $jsonStateLine = defined $jsonStateLine ? $jsonStateLine : '{}'; # empty JSON object + chomp($jsonStateLine); + my %jsonState = %{decode_json($jsonStateLine)}; + my @extraGrubInstallArgs = @{$jsonState{'extraGrubInstallArgs'}}; close FILE; - my $grubState = GrubState->new(name => $name, version => $version, efi => $efi, devices => $devices, efiMountPoint => $efiMountPoint ); + my $grubState = GrubState->new(name => $name, version => $version, efi => $efi, devices => $devices, efiMountPoint => $efiMountPoint, extraGrubInstallArgs => \@extraGrubInstallArgs ); 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; -} -my @deviceTargets = getDeviceTargets(); +my @deviceTargets = getList('devices'); my $prevGrubState = readGrubState(); my @prevDeviceTargets = split/,/, $prevGrubState->devices; +my @extraGrubInstallArgs = getList('extraGrubInstallArgs'); +my @prevExtraGrubInstallArgs = $prevGrubState->extraGrubInstallArgs; my $devicesDiffer = scalar (List::Compare->new( '-u', '-a', \@deviceTargets, \@prevDeviceTargets)->get_symmetric_difference()); +my $extraGrubInstallArgsDiffer = scalar (List::Compare->new( '-u', '-a', \@extraGrubInstallArgs, \@prevExtraGrubInstallArgs)->get_symmetric_difference()); my $nameDiffer = get("fullName") ne $prevGrubState->name; my $versionDiffer = get("fullVersion") ne $prevGrubState->version; my $efiDiffer = $efiTarget ne $prevGrubState->efi; @@ -637,25 +661,25 @@ if (($ENV{'NIXOS_INSTALL_GRUB'} // "") eq "1") { warn "NIXOS_INSTALL_GRUB env var deprecated, use NIXOS_INSTALL_BOOTLOADER"; $ENV{'NIXOS_INSTALL_BOOTLOADER'} = "1"; } -my $requireNewInstall = $devicesDiffer || $nameDiffer || $versionDiffer || $efiDiffer || $efiMountPointDiffer || (($ENV{'NIXOS_INSTALL_BOOTLOADER'} // "") eq "1"); +my $requireNewInstall = $devicesDiffer || $extraGrubInstallArgsDiffer || $nameDiffer || $versionDiffer || $efiDiffer || $efiMountPointDiffer || (($ENV{'NIXOS_INSTALL_BOOTLOADER'} // "") eq "1"); # install a symlink so that grub can detect the boot drive -my $tmpDir = File::Temp::tempdir(CLEANUP => 1) or die "Failed to create temporary space"; -symlink "$bootPath", "$tmpDir/boot" or die "Failed to symlink $tmpDir/boot"; +my $tmpDir = File::Temp::tempdir(CLEANUP => 1) or die "Failed to create temporary space: $!"; +symlink "$bootPath", "$tmpDir/boot" or die "Failed to symlink $tmpDir/boot: $!"; # 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"; - my @command = ("$grub/sbin/grub-install", "--recheck", "--root-directory=$tmpDir", Cwd::abs_path($dev)); + my @command = ("$grub/sbin/grub-install", "--recheck", "--root-directory=$tmpDir", Cwd::abs_path($dev), @extraGrubInstallArgs); if ($forceInstall eq "true") { push @command, "--force"; } if ($grubTarget ne "") { push @command, "--target=$grubTarget"; } - (system @command) == 0 or die "$0: installation of GRUB on $dev failed\n"; + (system @command) == 0 or die "$0: installation of GRUB on $dev failed: $!\n"; } } @@ -663,7 +687,7 @@ if (($requireNewInstall != 0) && ($efiTarget eq "no" || $efiTarget eq "both")) { # install EFI GRUB if (($requireNewInstall != 0) && ($efiTarget eq "only" || $efiTarget eq "both")) { print STDERR "installing the GRUB $grubVersion EFI boot loader into $efiSysMountPoint...\n"; - my @command = ("$grubEfi/sbin/grub-install", "--recheck", "--target=$grubTargetEfi", "--boot-directory=$bootPath", "--efi-directory=$efiSysMountPoint"); + my @command = ("$grubEfi/sbin/grub-install", "--recheck", "--target=$grubTargetEfi", "--boot-directory=$bootPath", "--efi-directory=$efiSysMountPoint", @extraGrubInstallArgs); if ($forceInstall eq "true") { push @command, "--force"; } @@ -674,17 +698,29 @@ if (($requireNewInstall != 0) && ($efiTarget eq "only" || $efiTarget eq "both")) push @command, "--removable" if $efiInstallAsRemovable eq "true"; } - (system @command) == 0 or die "$0: installation of GRUB EFI into $efiSysMountPoint failed\n"; + (system @command) == 0 or die "$0: installation of GRUB EFI into $efiSysMountPoint failed: $!\n"; } # update GRUB state file if ($requireNewInstall != 0) { - open FILE, ">$bootPath/grub/state" or die "cannot create $bootPath/grub/state: $!\n"; + # Temp file for atomic rename. + my $stateFile = "$bootPath/grub/state"; + my $stateFileTmp = $stateFile . ".tmp"; + + open FILE, ">$stateFileTmp" or die "cannot create $stateFileTmp: $!\n"; print FILE get("fullName"), "\n" or die; 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; + my %jsonState = ( + extraGrubInstallArgs => \@extraGrubInstallArgs + ); + my $jsonStateLine = encode_json(\%jsonState); + print FILE $jsonStateLine, "\n" or die; close FILE or die; + + # Atomically switch to the new state file + rename $stateFileTmp, $stateFile or die "cannot rename $stateFileTmp to $stateFile: $!\n"; } |