summary refs log tree commit diff
diff options
context:
space:
mode:
authorNiklas Hambüchen <mail@nh2.me>2020-07-06 22:08:31 +0200
committerGitHub <noreply@github.com>2020-07-06 22:08:31 +0200
commitd676d5d11988e5c40c15ac92cc6631de7cc63795 (patch)
tree066d83ced785954c652afebb88f929adeab0c26e
parentd4d9d9c552a769fc538ed35dfe9674932b705af1 (diff)
parenta90ae331ecc3b3410379d61cf95828dde2b5c4a6 (diff)
downloadnixpkgs-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.nix30
-rw-r--r--nixos/modules/system/boot/loader/grub/install-grub.pl90
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";
 }