mirror of
https://github.com/NixOS/nixpkgs.git
synced 2024-11-23 23:43:30 +00:00
Merge pull request #11484 from oxij/nixos-toposort-filesystems
lib: add toposort, nixos: use toposort for fileSystems to properly support bind and move mounts
This commit is contained in:
commit
3f70fcd4c1
@ -256,6 +256,86 @@ rec {
|
|||||||
reverseList = xs:
|
reverseList = xs:
|
||||||
let l = length xs; in genList (n: elemAt xs (l - n - 1)) l;
|
let l = length xs; in genList (n: elemAt xs (l - n - 1)) l;
|
||||||
|
|
||||||
|
/* Depth-First Search (DFS) for lists `list != []`.
|
||||||
|
|
||||||
|
`before a b == true` means that `b` depends on `a` (there's an
|
||||||
|
edge from `b` to `a`).
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
listDfs true hasPrefix [ "/home/user" "other" "/" "/home" ]
|
||||||
|
== { minimal = "/"; # minimal element
|
||||||
|
visited = [ "/home/user" ]; # seen elements (in reverse order)
|
||||||
|
rest = [ "/home" "other" ]; # everything else
|
||||||
|
}
|
||||||
|
|
||||||
|
listDfs true hasPrefix [ "/home/user" "other" "/" "/home" "/" ]
|
||||||
|
== { cycle = "/"; # cycle encountered at this element
|
||||||
|
loops = [ "/" ]; # and continues to these elements
|
||||||
|
visited = [ "/" "/home/user" ]; # elements leading to the cycle (in reverse order)
|
||||||
|
rest = [ "/home" "other" ]; # everything else
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
listDfs = stopOnCycles: before: list:
|
||||||
|
let
|
||||||
|
dfs' = us: visited: rest:
|
||||||
|
let
|
||||||
|
c = filter (x: before x us) visited;
|
||||||
|
b = partition (x: before x us) rest;
|
||||||
|
in if stopOnCycles && (length c > 0)
|
||||||
|
then { cycle = us; loops = c; inherit visited rest; }
|
||||||
|
else if length b.right == 0
|
||||||
|
then # nothing is before us
|
||||||
|
{ minimal = us; inherit visited rest; }
|
||||||
|
else # grab the first one before us and continue
|
||||||
|
dfs' (head b.right)
|
||||||
|
([ us ] ++ visited)
|
||||||
|
(tail b.right ++ b.wrong);
|
||||||
|
in dfs' (head list) [] (tail list);
|
||||||
|
|
||||||
|
/* Sort a list based on a partial ordering using DFS. This
|
||||||
|
implementation is O(N^2), if your ordering is linear, use `sort`
|
||||||
|
instead.
|
||||||
|
|
||||||
|
`before a b == true` means that `b` should be after `a`
|
||||||
|
in the result.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
toposort hasPrefix [ "/home/user" "other" "/" "/home" ]
|
||||||
|
== { result = [ "/" "/home" "/home/user" "other" ]; }
|
||||||
|
|
||||||
|
toposort hasPrefix [ "/home/user" "other" "/" "/home" "/" ]
|
||||||
|
== { cycle = [ "/home/user" "/" "/" ]; # path leading to a cycle
|
||||||
|
loops = [ "/" ]; } # loops back to these elements
|
||||||
|
|
||||||
|
toposort hasPrefix [ "other" "/home/user" "/home" "/" ]
|
||||||
|
== { result = [ "other" "/" "/home" "/home/user" ]; }
|
||||||
|
|
||||||
|
toposort (a: b: a < b) [ 3 2 1 ] == { result = [ 1 2 3 ]; }
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
toposort = before: list:
|
||||||
|
let
|
||||||
|
dfsthis = listDfs true before list;
|
||||||
|
toporest = toposort before (dfsthis.visited ++ dfsthis.rest);
|
||||||
|
in
|
||||||
|
if length list < 2
|
||||||
|
then # finish
|
||||||
|
{ result = list; }
|
||||||
|
else if dfsthis ? "cycle"
|
||||||
|
then # there's a cycle, starting from the current vertex, return it
|
||||||
|
{ cycle = reverseList ([ dfsthis.cycle ] ++ dfsthis.visited);
|
||||||
|
inherit (dfsthis) loops; }
|
||||||
|
else if toporest ? "cycle"
|
||||||
|
then # there's a cycle somewhere else in the graph, return it
|
||||||
|
toporest
|
||||||
|
# Slow, but short. Can be made a bit faster with an explicit stack.
|
||||||
|
else # there are no cycles
|
||||||
|
{ result = [ dfsthis.minimal ] ++ toporest.result; };
|
||||||
|
|
||||||
/* Sort a list based on a comparator function which compares two
|
/* Sort a list based on a comparator function which compares two
|
||||||
elements and returns true if the first argument is strictly below
|
elements and returns true if the first argument is strictly below
|
||||||
the second argument. The returned list is sorted in an increasing
|
the second argument. The returned list is sorted in an increasing
|
||||||
|
@ -2,6 +2,15 @@ pkgs: with pkgs.lib;
|
|||||||
|
|
||||||
rec {
|
rec {
|
||||||
|
|
||||||
|
# Check whenever fileSystem is needed for boot
|
||||||
|
fsNeededForBoot = fs: fs.neededForBoot
|
||||||
|
|| elem fs.mountPoint [ "/" "/nix" "/nix/store" "/var" "/var/log" "/var/lib" "/etc" ];
|
||||||
|
|
||||||
|
# Check whenever `b` depends on `a` as a fileSystem
|
||||||
|
# FIXME: it's incorrect to simply use hasPrefix here: "/dev/a" is not a parent of "/dev/ab"
|
||||||
|
fsBefore = a: b: ((any (x: elem x [ "bind" "move" ]) b.options) && (a.mountPoint == b.device))
|
||||||
|
|| (hasPrefix a.mountPoint b.mountPoint);
|
||||||
|
|
||||||
# Escape a path according to the systemd rules, e.g. /dev/xyzzy
|
# Escape a path according to the systemd rules, e.g. /dev/xyzzy
|
||||||
# becomes dev-xyzzy. FIXME: slow.
|
# becomes dev-xyzzy. FIXME: slow.
|
||||||
escapeSystemdPath = s:
|
escapeSystemdPath = s:
|
||||||
|
@ -12,7 +12,7 @@ let
|
|||||||
(fs: (fs.neededForBoot
|
(fs: (fs.neededForBoot
|
||||||
|| elem fs.mountPoint [ "/" "/nix" "/nix/store" "/var" "/var/log" "/var/lib" "/etc" ])
|
|| elem fs.mountPoint [ "/" "/nix" "/nix/store" "/var" "/var/log" "/var/lib" "/etc" ])
|
||||||
&& fs.fsType == "zfs")
|
&& fs.fsType == "zfs")
|
||||||
(attrValues config.fileSystems) != [];
|
config.system.build.fileSystems != [];
|
||||||
|
|
||||||
# Ascertain whether NixOS container support is required
|
# Ascertain whether NixOS container support is required
|
||||||
containerSupportRequired =
|
containerSupportRequired =
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
# the modules necessary to mount the root file system, then calls the
|
# the modules necessary to mount the root file system, then calls the
|
||||||
# init in the root file system to start the second boot stage.
|
# init in the root file system to start the second boot stage.
|
||||||
|
|
||||||
{ config, lib, pkgs, ... }:
|
{ config, lib, utils, pkgs, ... }:
|
||||||
|
|
||||||
with lib;
|
with lib;
|
||||||
|
|
||||||
@ -23,6 +23,12 @@ let
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
# The initrd only has to mount `/` or any FS marked as necessary for
|
||||||
|
# booting (such as the FS containing `/nix/store`, or an FS needed for
|
||||||
|
# mounting `/`, like `/` on a loopback).
|
||||||
|
fileSystems = filter utils.fsNeededForBoot config.system.build.fileSystems;
|
||||||
|
|
||||||
|
|
||||||
# Some additional utilities needed in stage 1, like mount, lvm, fsck
|
# Some additional utilities needed in stage 1, like mount, lvm, fsck
|
||||||
# etc. We don't want to bring in all of those packages, so we just
|
# etc. We don't want to bring in all of those packages, so we just
|
||||||
# copy what we need. Instead of using statically linked binaries,
|
# copy what we need. Instead of using statically linked binaries,
|
||||||
@ -71,7 +77,7 @@ let
|
|||||||
ln -sf kmod $out/bin/modprobe
|
ln -sf kmod $out/bin/modprobe
|
||||||
|
|
||||||
# Copy resize2fs if needed.
|
# Copy resize2fs if needed.
|
||||||
${optionalString (any (fs: fs.autoResize) (attrValues config.fileSystems)) ''
|
${optionalString (any (fs: fs.autoResize) fileSystems) ''
|
||||||
# We need mke2fs in the initrd.
|
# We need mke2fs in the initrd.
|
||||||
copy_bin_and_libs ${pkgs.e2fsprogs}/sbin/resize2fs
|
copy_bin_and_libs ${pkgs.e2fsprogs}/sbin/resize2fs
|
||||||
''}
|
''}
|
||||||
@ -128,21 +134,6 @@ let
|
|||||||
''; # */
|
''; # */
|
||||||
|
|
||||||
|
|
||||||
# The initrd only has to mount / or any FS marked as necessary for
|
|
||||||
# booting (such as the FS containing /nix/store, or an FS needed for
|
|
||||||
# mounting /, like / on a loopback).
|
|
||||||
#
|
|
||||||
# We need to guarantee that / is the first filesystem in the list so
|
|
||||||
# that if and when lustrateRoot is invoked, nothing else is mounted
|
|
||||||
fileSystems = let
|
|
||||||
filterNeeded = filter
|
|
||||||
(fs: fs.mountPoint != "/" && (fs.neededForBoot || elem fs.mountPoint [ "/nix" "/nix/store" "/var" "/var/log" "/var/lib" "/etc" ]));
|
|
||||||
filterRoot = filter
|
|
||||||
(fs: fs.mountPoint == "/");
|
|
||||||
allFileSystems = attrValues config.fileSystems;
|
|
||||||
in (filterRoot allFileSystems) ++ (filterNeeded allFileSystems);
|
|
||||||
|
|
||||||
|
|
||||||
udevRules = pkgs.stdenv.mkDerivation {
|
udevRules = pkgs.stdenv.mkDerivation {
|
||||||
name = "udev-rules";
|
name = "udev-rules";
|
||||||
allowedReferences = [ extraUtils ];
|
allowedReferences = [ extraUtils ];
|
||||||
@ -405,9 +396,8 @@ in
|
|||||||
};
|
};
|
||||||
|
|
||||||
config = mkIf (!config.boot.isContainer) {
|
config = mkIf (!config.boot.isContainer) {
|
||||||
|
|
||||||
assertions = [
|
assertions = [
|
||||||
{ assertion = any (fs: fs.mountPoint == "/") (attrValues config.fileSystems);
|
{ assertion = any (fs: fs.mountPoint == "/") fileSystems;
|
||||||
message = "The ‘fileSystems’ option does not specify your root file system.";
|
message = "The ‘fileSystems’ option does not specify your root file system.";
|
||||||
}
|
}
|
||||||
{ assertion = let inherit (config.boot) resumeDevice; in
|
{ assertion = let inherit (config.boot) resumeDevice; in
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
with lib;
|
with lib;
|
||||||
|
|
||||||
let
|
let
|
||||||
fileSystems = attrValues config.fileSystems ++ config.swapDevices;
|
fileSystems = config.system.build.fileSystems ++ config.swapDevices;
|
||||||
encDevs = filter (dev: dev.encrypted.enable) fileSystems;
|
encDevs = filter (dev: dev.encrypted.enable) fileSystems;
|
||||||
keyedEncDevs = filter (dev: dev.encrypted.keyFile != null) encDevs;
|
keyedEncDevs = filter (dev: dev.encrypted.keyFile != null) encDevs;
|
||||||
keylessEncDevs = filter (dev: dev.encrypted.keyFile == null) encDevs;
|
keylessEncDevs = filter (dev: dev.encrypted.keyFile == null) encDevs;
|
||||||
|
@ -5,7 +5,16 @@ with utils;
|
|||||||
|
|
||||||
let
|
let
|
||||||
|
|
||||||
fileSystems = attrValues config.fileSystems;
|
fileSystems' = toposort fsBefore (attrValues config.fileSystems);
|
||||||
|
|
||||||
|
fileSystems = if fileSystems' ? "result"
|
||||||
|
then # use topologically sorted fileSystems everywhere
|
||||||
|
fileSystems'.result
|
||||||
|
else # the assertion below will catch this,
|
||||||
|
# but we fall back to the original order
|
||||||
|
# anyway so that other modules could check
|
||||||
|
# their assertions too
|
||||||
|
(attrValues config.fileSystems);
|
||||||
|
|
||||||
prioOption = prio: optionalString (prio != null) " pri=${toString prio}";
|
prioOption = prio: optionalString (prio != null) " pri=${toString prio}";
|
||||||
|
|
||||||
@ -162,6 +171,17 @@ in
|
|||||||
|
|
||||||
config = {
|
config = {
|
||||||
|
|
||||||
|
assertions = let
|
||||||
|
ls = sep: concatMapStringsSep sep (x: x.mountPoint);
|
||||||
|
in [
|
||||||
|
{ assertion = ! (fileSystems' ? "cycle");
|
||||||
|
message = "The ‘fileSystems’ option can't be topologically sorted: mountpoint dependency path ${ls " -> " fileSystems'.cycle} loops to ${ls ", " fileSystems'.loops}";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
# Export for use in other modules
|
||||||
|
system.build.fileSystems = fileSystems;
|
||||||
|
|
||||||
boot.supportedFilesystems = map (fs: fs.fsType) fileSystems;
|
boot.supportedFilesystems = map (fs: fs.fsType) fileSystems;
|
||||||
|
|
||||||
# Add the mount helpers to the system path so that `mount' can find them.
|
# Add the mount helpers to the system path so that `mount' can find them.
|
||||||
@ -180,7 +200,7 @@ in
|
|||||||
# in your /etc/nixos/configuration.nix file.
|
# in your /etc/nixos/configuration.nix file.
|
||||||
|
|
||||||
# Filesystems.
|
# Filesystems.
|
||||||
${flip concatMapStrings fileSystems (fs:
|
${concatMapStrings (fs:
|
||||||
(if fs.device != null then fs.device
|
(if fs.device != null then fs.device
|
||||||
else if fs.label != null then "/dev/disk/by-label/${fs.label}"
|
else if fs.label != null then "/dev/disk/by-label/${fs.label}"
|
||||||
else throw "No device specified for mount point ‘${fs.mountPoint}’.")
|
else throw "No device specified for mount point ‘${fs.mountPoint}’.")
|
||||||
@ -191,7 +211,7 @@ in
|
|||||||
+ " " + (if skipCheck fs then "0" else
|
+ " " + (if skipCheck fs then "0" else
|
||||||
if fs.mountPoint == "/" then "1" else "2")
|
if fs.mountPoint == "/" then "1" else "2")
|
||||||
+ "\n"
|
+ "\n"
|
||||||
)}
|
) fileSystems}
|
||||||
|
|
||||||
# Swap devices.
|
# Swap devices.
|
||||||
${flip concatMapStrings config.swapDevices (sw:
|
${flip concatMapStrings config.swapDevices (sw:
|
||||||
@ -211,14 +231,15 @@ in
|
|||||||
|
|
||||||
formatDevice = fs:
|
formatDevice = fs:
|
||||||
let
|
let
|
||||||
mountPoint' = escapeSystemdPath fs.mountPoint;
|
mountPoint' = "${escapeSystemdPath fs.mountPoint}.mount";
|
||||||
device' = escapeSystemdPath fs.device;
|
device' = escapeSystemdPath fs.device;
|
||||||
|
device'' = "${device}.device";
|
||||||
in nameValuePair "mkfs-${device'}"
|
in nameValuePair "mkfs-${device'}"
|
||||||
{ description = "Initialisation of Filesystem ${fs.device}";
|
{ description = "Initialisation of Filesystem ${fs.device}";
|
||||||
wantedBy = [ "${mountPoint'}.mount" ];
|
wantedBy = [ mountPoint' ];
|
||||||
before = [ "${mountPoint'}.mount" "systemd-fsck@${device'}.service" ];
|
before = [ mountPoint' "systemd-fsck@${device'}.service" ];
|
||||||
requires = [ "${device'}.device" ];
|
requires = [ device'' ];
|
||||||
after = [ "${device'}.device" ];
|
after = [ device'' ];
|
||||||
path = [ pkgs.utillinux ] ++ config.system.fsPackages;
|
path = [ pkgs.utillinux ] ++ config.system.fsPackages;
|
||||||
script =
|
script =
|
||||||
''
|
''
|
||||||
|
@ -36,13 +36,11 @@ let
|
|||||||
|
|
||||||
fsToPool = fs: datasetToPool fs.device;
|
fsToPool = fs: datasetToPool fs.device;
|
||||||
|
|
||||||
zfsFilesystems = filter (x: x.fsType == "zfs") (attrValues config.fileSystems);
|
zfsFilesystems = filter (x: x.fsType == "zfs") config.system.build.fileSystems;
|
||||||
|
|
||||||
isRoot = fs: fs.neededForBoot || elem fs.mountPoint [ "/" "/nix" "/nix/store" "/var" "/var/log" "/var/lib" "/etc" ];
|
|
||||||
|
|
||||||
allPools = unique ((map fsToPool zfsFilesystems) ++ cfgZfs.extraPools);
|
allPools = unique ((map fsToPool zfsFilesystems) ++ cfgZfs.extraPools);
|
||||||
|
|
||||||
rootPools = unique (map fsToPool (filter isRoot zfsFilesystems));
|
rootPools = unique (map fsToPool (filter fsNeededForBoot zfsFilesystems));
|
||||||
|
|
||||||
dataPools = unique (filter (pool: !(elem pool rootPools)) allPools);
|
dataPools = unique (filter (pool: !(elem pool rootPools)) allPools);
|
||||||
|
|
||||||
@ -277,7 +275,7 @@ in
|
|||||||
|
|
||||||
systemd.services = let
|
systemd.services = let
|
||||||
getPoolFilesystems = pool:
|
getPoolFilesystems = pool:
|
||||||
filter (x: x.fsType == "zfs" && (fsToPool x) == pool) (attrValues config.fileSystems);
|
filter (x: x.fsType == "zfs" && (fsToPool x) == pool) config.system.build.fileSystems;
|
||||||
|
|
||||||
getPoolMounts = pool:
|
getPoolMounts = pool:
|
||||||
let
|
let
|
||||||
|
Loading…
Reference in New Issue
Block a user