-
-
Notifications
You must be signed in to change notification settings - Fork 19.5k
Grub Update #2449
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Grub Update #2449
Changes from all commits
4f832b5
f2bef62
3c6e2fb
02ab48d
c5bdb46
fba9f64
a6e6c85
70c1177
525acb4
1f460e0
99b4792
d4e2040
769d2dc
5870ae6
8329d12
809caa8
2b703f8
62fedf6
d4a9645
8b36bf5
8ff4b3b
429f785
87d5e45
3bf2267
7264941
b651097
6549bcf
c02bc3a
4f096c0
cc62623
0f6079d
940c57e
2ea1433
dd18e67
babcd70
36a4773
cf7f7a5
0fdbc44
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,6 +20,13 @@ sub uniq { | |
| return @res; | ||
| } | ||
|
|
||
| sub runCommand { | ||
| my ($cmd) = @_; | ||
| open FILE, "$cmd 2>/dev/null |" or die "Failed to execute: $cmd\n"; | ||
| my @ret = <FILE>; | ||
| close FILE; | ||
| return ($?, @ret); | ||
| } | ||
|
|
||
| # Process the command line. | ||
| my $outDir = "/etc/nixos"; | ||
|
|
@@ -337,6 +344,20 @@ sub in { | |
| } | ||
| } | ||
|
|
||
| # Is this a btrfs filesystem? | ||
| if ($fsType eq "btrfs") { | ||
| my ($status, @info) = runCommand("btrfs subvol show $rootDir$mountPoint"); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not just use the backtick operator to run btrfs?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because btrfs doesn't actually return an error string in the cases I need to know it failed, therefore relying on the exit code of the application. In hindsight, I could have used ${^CHILD_ERROR_NATIVE} to grab the return code, I just don't really like the fact that I am retrieving a global variable for the return status.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It was most likely because I didn't want to deal with parsing errors from text vs the real subvolume strings. |
||
| if ($status != 0) { | ||
| die "Failed to retreive subvolume info for $mountPoint"; | ||
| } | ||
| my @subvols = join("", @info) =~ m/Name:[ \t\n]*([^ \t\n]*)/; | ||
| if ($#subvols > 0) { | ||
| die "Btrfs subvol name for $mountPoint listed multiple times in mount\n" | ||
| } elsif ($#subvols == 0) { | ||
| push @extraOptions, "subvol=$subvols[0]"; | ||
| } | ||
| } | ||
|
|
||
| # Emit the filesystem. | ||
| $fileSystems .= <<EOF; | ||
| fileSystems.\"$mountPoint\" = | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,6 @@ | ||
| use strict; | ||
| use warnings; | ||
| use Class::Struct; | ||
| use XML::LibXML; | ||
| use File::Basename; | ||
| use File::Path; | ||
|
|
@@ -27,6 +28,14 @@ sub writeFile { | |
| close FILE or die; | ||
| } | ||
|
|
||
| sub runCommand { | ||
| my ($cmd) = @_; | ||
| open FILE, "$cmd 2>/dev/null |" or die "Failed to execute: $cmd\n"; | ||
| my @ret = <FILE>; | ||
| close FILE; | ||
| return ($?, @ret); | ||
| } | ||
|
|
||
| my $grub = get("grub"); | ||
| my $grubVersion = int(get("version")); | ||
| my $extraConfig = get("extraConfig"); | ||
|
|
@@ -39,7 +48,7 @@ sub writeFile { | |
| my $copyKernels = get("copyKernels") eq "true"; | ||
| my $timeout = int(get("timeout")); | ||
| my $defaultEntry = int(get("default")); | ||
| my $explicitBootRoot = get("explicitBootRoot"); | ||
| my $fsIdentifier = get("fsIdentifier"); | ||
| $ENV{'PATH'} = get("path"); | ||
|
|
||
| die "unsupported GRUB version\n" if $grubVersion != 1 && $grubVersion != 2; | ||
|
|
@@ -48,24 +57,109 @@ sub writeFile { | |
|
|
||
| mkpath("/boot/grub", 0, 0700); | ||
|
|
||
|
|
||
| # Discover whether /boot is on the same filesystem as / and | ||
| # /nix/store. If not, then all kernels and initrds must be copied to | ||
| # /boot, and all paths in the GRUB config file must be relative to the | ||
| # root of the /boot filesystem. `$bootRoot' is the path to be | ||
| # prepended to paths under /boot. | ||
| my $bootRoot = "/boot"; | ||
| if (stat("/")->dev != stat("/boot")->dev) { | ||
| $bootRoot = ""; | ||
| $copyKernels = 1; | ||
| } elsif (stat("/boot")->dev != stat("/nix/store")->dev) { | ||
| # /boot. | ||
| if (stat("/boot")->dev != stat("/nix/store")->dev) { | ||
| $copyKernels = 1; | ||
| } | ||
|
|
||
| if ($explicitBootRoot ne "") { | ||
| $bootRoot = $explicitBootRoot; | ||
| # Discover information about the location of /boot | ||
| struct(Fs => { | ||
| device => '$', | ||
| type => '$', | ||
| mount => '$', | ||
| }); | ||
| sub GetFs { | ||
| my ($dir) = @_; | ||
| my ($status, @dfOut) = runCommand("df -T $dir"); | ||
| if ($status != 0 || $#dfOut != 1) { | ||
| die "Failed to retrieve output about $dir from `df`"; | ||
| } | ||
| my @boot = split(/[ \n\t]+/, $dfOut[1]); | ||
| return Fs->new(device => $boot[0], type => $boot[1], mount => $boot[6]); | ||
| } | ||
| struct (Grub => { | ||
| path => '$', | ||
| search => '$', | ||
| }); | ||
| my $driveid = 1; | ||
| sub GrubFs { | ||
| my ($dir) = @_; | ||
| my $fs = GetFs($dir); | ||
| my $path = "/" . substr($dir, length($fs->mount)); | ||
| my $search = ""; | ||
|
|
||
| if ($grubVersion > 1) { | ||
| # ZFS is completely separate logic as zpools are always identified by a label | ||
| # or custom UUID | ||
| if ($fs->type eq 'zfs') { | ||
| my $sid = index($fs->device, '/'); | ||
|
|
||
| if ($sid < 0) { | ||
| $search = '--label ' . $fs->device; | ||
| $path = '/@' . $path; | ||
| } else { | ||
| $search = '--label ' . substr($fs->device, 0, $sid); | ||
| $path = '/' . substr($fs->device, $sid) . '/@' . $path; | ||
| } | ||
| } else { | ||
| my %types = ('uuid' => '--fs-uuid', 'label' => '--label'); | ||
|
|
||
| if ($fsIdentifier eq 'provided') { | ||
| # If the provided dev is identifying the partition using a label or uuid, | ||
| # we should get the label / uuid and do a proper search | ||
| my @matches = $fs->device =~ m/\/dev\/disk\/by-(label|uuid)\/(.*)/; | ||
| if ($#matches > 1) { | ||
| die "Too many matched devices" | ||
| } elsif ($#matches == 1) { | ||
| $search = "$types{$matches[0]} $matches[1]" | ||
| } | ||
| } else { | ||
| # Determine the identifying type | ||
| $search = $types{$fsIdentifier} . ' '; | ||
|
|
||
| # Based on the type pull in the identifier from the system | ||
| my ($status, @devInfo) = runCommand("blkid -o export @{[$fs->device]}"); | ||
| if ($status != 0) { | ||
| die "Failed to get blkid info for @{[$fs->device]}"; | ||
| } | ||
| my @matches = join("", @devInfo) =~ m/@{[uc $fsIdentifier]}=([^\n]*)/; | ||
| if ($#matches != 0) { | ||
| die "Couldn't find a $types{$fsIdentifier} for @{[$fs->device]}\n" | ||
| } | ||
| $search .= $matches[0]; | ||
| } | ||
|
|
||
| # BTRFS is a special case in that we need to fix the referrenced path based on subvolumes | ||
| if ($fs->type eq 'btrfs') { | ||
| my ($status, @info) = runCommand("btrfs subvol show @{[$fs->mount]}"); | ||
| if ($status != 0) { | ||
| die "Failed to retreive subvolume info for @{[$fs->mount]}"; | ||
| } | ||
| my @subvols = join("", @info) =~ m/Name:[ \t\n]*([^ \t\n]*)/; | ||
| if ($#subvols > 0) { | ||
| die "Btrfs subvol name for @{[$fs->device]} listed multiple times in mount\n" | ||
| } elsif ($#subvols == 0) { | ||
| $path = "/$subvols[0]$path"; | ||
| } | ||
| } | ||
| } | ||
| if (not $search eq "") { | ||
| $search = "search --set=drive$driveid " . $search; | ||
| $path = "(\$drive$driveid)$path"; | ||
| $driveid += 1; | ||
| } | ||
| } | ||
| return Grub->new(path => $path, search => $search); | ||
| } | ||
| my $grubBoot = GrubFs("/boot"); | ||
| my $grubStore = GrubFs("/nix"); | ||
|
|
||
| # We don't need to copy if we can read the kernels directly | ||
| if ($grubStore->search ne "") { | ||
| $copyKernels = 0; | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nope, that is not necessarily true. Searching may fail if it's encrypted, for example! |
||
|
|
||
| # Generate the header. | ||
| my $conf .= "# Automatically generated. DO NOT EDIT THIS FILE!\n"; | ||
|
|
@@ -77,12 +171,14 @@ sub writeFile { | |
| "; | ||
| if ($splashImage) { | ||
| copy $splashImage, "/boot/background.xpm.gz" or die "cannot copy $splashImage to /boot\n"; | ||
| $conf .= "splashimage $bootRoot/background.xpm.gz\n"; | ||
| $conf .= "splashimage " . $grubBoot->path . "/background.xpm.gz\n"; | ||
| } | ||
| } | ||
|
|
||
| else { | ||
| $conf .= " | ||
| " . $grubBoot->search . " | ||
| " . $grubStore->search . " | ||
| if [ -s \$prefix/grubenv ]; then | ||
| load_env | ||
| fi | ||
|
|
@@ -103,7 +199,7 @@ sub writeFile { | |
| set timeout=$timeout | ||
| fi | ||
|
|
||
| if loadfont $bootRoot/grub/fonts/unicode.pf2; then | ||
| if loadfont " . $grubBoot->path . "/grub/fonts/unicode.pf2; then | ||
| set gfxmode=640x480 | ||
| insmod gfxterm | ||
| insmod vbe | ||
|
|
@@ -117,7 +213,7 @@ sub writeFile { | |
| copy $splashImage, "/boot/background.png" or die "cannot copy $splashImage to /boot\n"; | ||
| $conf .= " | ||
| insmod png | ||
| if background_image $bootRoot/background.png; then | ||
| if background_image " . $grubBoot->path . "/background.png; then | ||
| set color_normal=white/black | ||
| set color_highlight=black/white | ||
| else | ||
|
|
@@ -139,7 +235,7 @@ sub writeFile { | |
|
|
||
| sub copyToKernelsDir { | ||
| my ($path) = @_; | ||
| return $path unless $copyKernels; | ||
| return $grubStore->path . substr($path, length("/nix")) unless $copyKernels; | ||
| $path =~ /\/nix\/store\/(.*)/ or die; | ||
| my $name = $1; $name =~ s/\//-/g; | ||
| my $dst = "/boot/kernels/$name"; | ||
|
|
@@ -152,7 +248,7 @@ sub copyToKernelsDir { | |
| rename $tmp, $dst or die "cannot rename $tmp to $dst\n"; | ||
| } | ||
| $copied{$dst} = 1; | ||
| return "$bootRoot/kernels/$name"; | ||
| return $grubBoot->path . "/kernels/$name"; | ||
| } | ||
|
|
||
| sub addEntry { | ||
|
|
@@ -179,6 +275,8 @@ sub addEntry { | |
| $conf .= " " . ($xen ? "module" : "initrd") . " $initrd\n\n"; | ||
| } else { | ||
| $conf .= "menuentry \"$name\" {\n"; | ||
| $conf .= $grubBoot->search . "\n"; | ||
| $conf .= $grubStore->search . "\n"; | ||
| $conf .= " $extraPerEntryConfig\n" if $extraPerEntryConfig; | ||
| $conf .= " multiboot $xen $xenParams\n" if $xen; | ||
| $conf .= " " . ($xen ? "module" : "linux") . " $kernel $kernelParams\n"; | ||
|
|
@@ -196,7 +294,7 @@ sub addEntry { | |
| $conf .= "$extraEntries\n" unless $extraEntriesBeforeNixOS; | ||
|
|
||
| # extraEntries could refer to @bootRoot@, which we have to substitute | ||
| $conf =~ s/\@bootRoot\@/$bootRoot/g; | ||
| $conf =~ s/\@bootRoot\@/$grubBoot->path/g; | ||
|
|
||
| # Emit submenus for all system profiles. | ||
| sub addProfile { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -133,7 +133,7 @@ in | |
| }; | ||
|
|
||
| boot.initrd = mkIf inInitrd { | ||
| kernelModules = [ "spl" "zfs" ] ; | ||
| kernelModules = [ "spl" "zfs" ]; | ||
| extraUtilsCommands = | ||
| '' | ||
| cp -v ${zfsPkg}/sbin/zfs $out/bin | ||
|
|
@@ -148,6 +148,10 @@ in | |
| ''; | ||
| }; | ||
|
|
||
| boot.loader.grub = mkIf inInitrd { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't this only be needed if /boot itself is zfs?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, but when using zfs we cannot tell from the nix expression if /boot is a part of the / dataset or it's own dataset. It was just easier to assume that if they were intending to boot from a root zpool, grub would likely need zfs support. Using grub2 with compiled in zfs support adds no extra dependencies if you already have zfs installed on your machine.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's the overhead of zfs support in grub? Can we enable it unconditionally?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's pretty small as it just needs libzfs. I wouldn't mind enabling this by default but I'll leave it as a configurable option. The biggest issue I suppose is that any breakage to the zfs package (incompatible kernel api) and then you break the ability to build grub. This probably isn't a big deal but something to keep in mind. |
||
| zfsSupport = true; | ||
| }; | ||
|
|
||
| systemd.services."zpool-import" = { | ||
| description = "Import zpools"; | ||
| after = [ "systemd-udev-settle.service" ]; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please don't use tabs...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#3863