diff --git a/nixos/modules/installer/tools/nixos-generate-config.pl b/nixos/modules/installer/tools/nixos-generate-config.pl index 66a8152a3a6..73dd87cef5c 100644 --- a/nixos/modules/installer/tools/nixos-generate-config.pl +++ b/nixos/modules/installer/tools/nixos-generate-config.pl @@ -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 = ; + close FILE; + return ($?, @ret); +} # Process the command line. my $outDir = "/etc/nixos"; @@ -337,6 +344,20 @@ EOF } } + # Is this a btrfs filesystem? + if ($fsType eq "btrfs") { + my ($status, @info) = runCommand("btrfs subvol show $rootDir$mountPoint"); + 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 .= <df or + mount. Note, zfs zpools / datasets are ignored + and will always be mounted using their labels. + ''; + }; + + zfsSupport = mkOption { + default = false; + type = types.bool; + description = '' + Whether grub should be build against libzfs. ''; }; @@ -260,6 +276,9 @@ in ${pkgs.coreutils}/bin/cp -pf "${v}" "/boot/${n}" '') config.boot.loader.grub.extraFiles); + assertions = [{ assertion = !cfg.zfsSupport || cfg.version == 2; + message = "Only grub version 2 provides zfs support";}]; + }) ]; diff --git a/nixos/modules/system/boot/loader/grub/install-grub.pl b/nixos/modules/system/boot/loader/grub/install-grub.pl index b4900358a5d..7ced51f57e1 100644 --- a/nixos/modules/system/boot/loader/grub/install-grub.pl +++ b/nixos/modules/system/boot/loader/grub/install-grub.pl @@ -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 = ; + close FILE; + return ($?, @ret); +} + my $grub = get("grub"); my $grubVersion = int(get("version")); my $extraConfig = get("extraConfig"); @@ -39,7 +48,7 @@ my $configurationLimit = int(get("configurationLimit")); 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,22 +57,108 @@ print STDERR "updating GRUB $grubVersion menu...\n"; 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; } # Generate the header. @@ -76,12 +171,14 @@ if ($grubVersion == 1) { "; 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 @@ -102,7 +199,7 @@ else { 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 @@ -116,7 +213,7 @@ else { 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 @@ -138,7 +235,7 @@ mkpath("/boot/kernels", 0, 0755) if $copyKernels; 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"; @@ -151,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 { @@ -178,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"; @@ -195,7 +294,7 @@ addEntry("NixOS - Default", $defaultConfig); $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 { diff --git a/nixos/modules/tasks/filesystems/zfs.nix b/nixos/modules/tasks/filesystems/zfs.nix index d7deb44c407..1c4bbc16b49 100644 --- a/nixos/modules/tasks/filesystems/zfs.nix +++ b/nixos/modules/tasks/filesystems/zfs.nix @@ -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 { + zfsSupport = true; + }; + systemd.services."zpool-import" = { description = "Import zpools"; after = [ "systemd-udev-settle.service" ]; diff --git a/nixos/release-combined.nix b/nixos/release-combined.nix index dae3b9210a8..23348e1d089 100644 --- a/nixos/release-combined.nix +++ b/nixos/release-combined.nix @@ -52,6 +52,10 @@ in rec { (all nixos.tests.installer.lvm) (all nixos.tests.installer.separateBoot) (all nixos.tests.installer.simple) + (all nixos.tests.installer.simpleLabels) + (all nixos.tests.installer.simpleProvided) + (all nixos.tests.installer.btrfsSimple) + (all nixos.tests.installer.btrfsSubvols) (all nixos.tests.ipv6) (all nixos.tests.kde4) (all nixos.tests.login) diff --git a/nixos/release.nix b/nixos/release.nix index e2b93640f91..f78ecb4383d 100644 --- a/nixos/release.nix +++ b/nixos/release.nix @@ -231,7 +231,7 @@ in rec { tests.installer.simpleLabels = forAllSystems (system: (import tests/installer.nix { inherit system; }).simpleLabels.test); tests.installer.simpleProvided = forAllSystems (system: (import tests/installer.nix { inherit system; }).simpleProvided.test); tests.installer.btrfsSimple = forAllSystems (system: (import tests/installer.nix { inherit system; }).btrfsSimple.test); - #tests.installer.btrfsSubvols = forAllSystems (system: (import tests/installer.nix { inherit system; }).btrfsSubvols.test); + tests.installer.btrfsSubvols = forAllSystems (system: (import tests/installer.nix { inherit system; }).btrfsSubvols.test); tests.influxdb = callTest tests/influxdb.nix {}; tests.ipv6 = callTest tests/ipv6.nix {}; tests.jenkins = callTest tests/jenkins.nix {}; diff --git a/nixos/tests/installer.nix b/nixos/tests/installer.nix index 6ee52fd63d8..154f3323d29 100644 --- a/nixos/tests/installer.nix +++ b/nixos/tests/installer.nix @@ -35,8 +35,8 @@ let # The configuration to install. - makeConfig = { testChannel, useEFI, grubVersion, grubDevice }: pkgs.writeText "configuration.nix" - '' + makeConfig = { testChannel, useEFI, grubVersion, grubDevice, grubIdentifier }: + pkgs.writeText "configuration.nix" '' { config, pkgs, modulesPath, ... }: { imports = @@ -54,6 +54,7 @@ let ''} boot.loader.grub.device = "${grubDevice}"; boot.loader.grub.extraConfig = "serial; terminal_output.serial"; + boot.loader.grub.fsIdentifier = "${grubIdentifier}"; ''} environment.systemPackages = [ ${optionalString testChannel "pkgs.rlwrap"} ]; @@ -93,7 +94,7 @@ let # disk, and then reboot from the hard disk. It's parameterized with # a test script fragment `createPartitions', which must create # partitions and filesystems. - testScriptFun = { createPartitions, testChannel, useEFI, grubVersion, grubDevice }: + testScriptFun = { createPartitions, testChannel, useEFI, grubVersion, grubDevice, grubIdentifier }: let # FIXME: OVMF doesn't boot from virtio http://www.mail-archive.com/edk2-devel@lists.sourceforge.net/msg01501.html iface = if useEFI || grubVersion == 1 then "scsi" else "virtio"; @@ -161,7 +162,7 @@ let $machine->succeed("cat /mnt/etc/nixos/hardware-configuration.nix >&2"); $machine->copyFileFromHost( - "${ makeConfig { inherit testChannel useEFI grubVersion grubDevice; } }", + "${ makeConfig { inherit testChannel useEFI grubVersion grubDevice grubIdentifier; } }", "/mnt/etc/nixos/configuration.nix"); # Perform the installation. @@ -216,13 +217,13 @@ let makeInstallerTest = name: - { createPartitions, testChannel ? false, useEFI ? false, grubVersion ? 2, grubDevice ? "/dev/vda" }: + { createPartitions, testChannel ? false, useEFI ? false, grubVersion ? 2, grubDevice ? "/dev/vda", grubIdentifier ? "uuid" }: makeTest { inherit iso; name = "installer-" + name; nodes = if testChannel then { inherit webserver; } else { }; testScript = testScriptFun { - inherit createPartitions testChannel useEFI grubVersion grubDevice; + inherit createPartitions testChannel useEFI grubVersion grubDevice grubIdentifier; }; }; diff --git a/pkgs/tools/misc/grub/2.0x.nix b/pkgs/tools/misc/grub/2.0x.nix index b1877bdcf98..e3c07af759c 100644 --- a/pkgs/tools/misc/grub/2.0x.nix +++ b/pkgs/tools/misc/grub/2.0x.nix @@ -1,30 +1,45 @@ -{ fetchurl, stdenv, flex, bison, gettext, ncurses, libusb, freetype, qemu -, devicemapper, EFIsupport ? false }: +{ stdenv, fetchurl, autogen, flex, bison, python, autoconf, automake +, gettext, ncurses, libusb, freetype, qemu, devicemapper +, linuxPackages ? null +, efiSupport ? false +, zfsSupport ? false +}: +with stdenv.lib; let + efiSystems = { + "i686-linux".target = "i386"; + "x86_64-linux".target = "x86_64"; + }; - prefix = "grub${if EFIsupport then "-efi" else ""}"; + canEfi = any (system: stdenv.system == system) (mapAttrsToList (name: _: name) efiSystems); - version = "2.00"; + prefix = "grub${if efiSupport then "-efi" else ""}"; + + version = "2.02-beta2"; unifont_bdf = fetchurl { url = "http://unifoundry.com/unifont-5.1.20080820.bdf.gz"; sha256 = "0s0qfff6n6282q28nwwblp5x295zd6n71kl43xj40vgvdqxv0fxx"; }; +in ( -in +assert efiSupport -> canEfi; +assert zfsSupport -> linuxPackages != null && linuxPackages.zfs != null; stdenv.mkDerivation rec { name = "${prefix}-${version}"; src = fetchurl { - url = "mirror://gnu/grub/grub-${version}.tar.xz"; - sha256 = "0n64hpmsccvicagvr0c6v0kgp2yw0kgnd3jvsyd26cnwgs7c6kkq"; + name = "grub-2.02-beta2.tar.xz"; + url = "http://alpha.gnu.org/gnu/grub/grub-2.02~beta2.tar.xz"; + sha256 = "13a13fhc0wf473dn73zhga15mjvkg6vqp4h25dxg4n7am2r05izn"; }; - nativeBuildInputs = [ flex bison ]; + nativeBuildInputs = [ autogen flex bison python autoconf automake ]; buildInputs = [ ncurses libusb freetype gettext devicemapper ] - ++ stdenv.lib.optional doCheck qemu; + ++ optional doCheck qemu + ++ optional zfsSupport linuxPackages.zfs; preConfigure = '' for i in "tests/util/"*.in @@ -43,27 +58,19 @@ stdenv.mkDerivation rec { # See . sed -i "tests/util/grub-shell.in" \ -e's/qemu-system-i386/qemu-system-x86_64 -nodefaults/g' - - # Fix for building on Glibc 2.16. Won't be needed once the - # gnulib in grub is updated. - sed -i '/gets is a security hole/d' grub-core/gnulib/stdio.in.h ''; prePatch = - '' gunzip < "${unifont_bdf}" > "unifont.bdf" + '' sh autogen.sh + gunzip < "${unifont_bdf}" > "unifont.bdf" sed -i "configure" \ -e "s|/usr/src/unifont.bdf|$PWD/unifont.bdf|g" ''; patches = [ ./fix-bash-completion.patch ]; - configureFlags = - let arch = if stdenv.system == "i686-linux" then "i386" - else if stdenv.system == "x86_64-linux" then "x86_64" - else throw "unsupported EFI firmware architecture"; - in - stdenv.lib.optionals EFIsupport - [ "--with-platform=efi" "--target=${arch}" "--program-prefix=" ]; + configureFlags = optional zfsSupport "--enable-libzfs" + ++ optionals efiSupport [ "--with-platform=efi" "--target=${efiSystems.${stdenv.system}.target}" "--program-prefix=" ]; doCheck = false; enableParallelBuilding = true; @@ -72,7 +79,7 @@ stdenv.mkDerivation rec { paxmark pms $out/sbin/grub-{probe,bios-setup} ''; - meta = { + meta = with stdenv.lib; { description = "GNU GRUB, the Grand Unified Boot Loader (2.x beta)"; longDescription = @@ -89,11 +96,8 @@ stdenv.mkDerivation rec { homepage = http://www.gnu.org/software/grub/; - license = stdenv.lib.licenses.gpl3Plus; + license = licenses.gpl3Plus; - platforms = if EFIsupport then - [ "i686-linux" "x86_64-linux" ] - else - stdenv.lib.platforms.gnu; + platforms = platforms.gnu; }; -} +}) diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index 05c9d695bc2..6e5caa3af9e 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -1235,9 +1235,11 @@ let buggyBiosCDSupport = config.grub.buggyBiosCDSupport or true; }; - grub2 = callPackage ../tools/misc/grub/2.0x.nix { libusb = libusb1; flex = flex_2_5_35; }; + grub2 = callPackage ../tools/misc/grub/2.0x.nix { }; - grub2_efi = grub2.override { EFIsupport = true; }; + grub2_efi = grub2.override { efiSupport = true; }; + + grub2_zfs = grub2.override { zfsSupport = true; }; gssdp = callPackage ../development/libraries/gssdp { inherit (gnome) libsoup;