Commit 26ccfb7a authored by Will Fancher's avatar Will Fancher
Browse files

nixos/image/repart: Use own assertions / warnings.

It was easy to accidentally trigger infinite recursion if you depended
on `toplevel` in any way before. For instance, if you used
`CopyBlocks` with an image containing `toplevel`. This was because
`toplevel`'s assertion / warning logic has to be evaluated, but that
means evaluating `image.repart`'s assertions / warnings, which
requires evaluating the `repartConfig` attrsets to check for malformed
`Label`s. That causes the module system to type check *all*
`repartConfig` keys, even though most of them aren't used in the
assertions / warnings. So evaluating `system.build.image` evaluates
`repartConfig.CopyBlocks`, which evaluates `toplevel`, which evaluates
assertions / warnings, which evaluates `repartConfig.CopyBlocks` to
type check it. Infinite loop.

Even ignoring this recursion problem, it's still better for the repart
module to have its own assertions / warnings options. You don't have
to use `toplevel` in a repart image, so its assertions / warnings
would have been ignored in that case anyway. This way they're *always*
checked when you build an image.
parent 4b0d1225
Loading
Loading
Loading
Loading
+77 −55
Original line number Diff line number Diff line
@@ -3,6 +3,7 @@

{
  config,
  options,
  pkgs,
  lib,
  utils,
@@ -258,50 +259,29 @@ in
      '';
    };

    assertions = lib.mkOption {
      type = options.assertions.type;
      default = [ ];
      internal = true;
      visible = false;
      description = ''
        Assertions only evaluated by the repart image, not by the system toplevel.
      '';
    };

  config = {
    assertions = lib.mapAttrsToList (
      fileName: partitionConfig:
      let
        inherit (partitionConfig) repartConfig;
        labelLength = builtins.stringLength repartConfig.Label;
      in
      {
        assertion = repartConfig ? Label -> GPTMaxLabelLength >= labelLength;
        message = ''
          The partition label '${repartConfig.Label}'
          defined for '${fileName}' is ${toString labelLength} characters long,
          but the maximum label length supported by UEFI is ${toString GPTMaxLabelLength}.
    warnings = lib.mkOption {
      type = options.warnings.type;
      default = [ ];
      internal = true;
      visible = false;
      description = ''
        Warnings only evaluated by the repart image, not by the system toplevel.
      '';
      }
    ) cfg.partitions;

    warnings = lib.filter (v: v != null) (
      lib.mapAttrsToList (
        fileName: partitionConfig:
        let
          inherit (partitionConfig) repartConfig;
          suggestedMaxLabelLength = GPTMaxLabelLength - 2;
          labelLength = builtins.stringLength repartConfig.Label;
        in
        if (repartConfig ? Label && labelLength >= suggestedMaxLabelLength) then
          ''
            The partition label '${repartConfig.Label}'
            defined for '${fileName}' is ${toString labelLength} characters long.
            The suggested maximum label length is ${toString suggestedMaxLabelLength}.
    };

            If you use sytemd-sysupdate style A/B updates, this might
            not leave enough space to increment the version number included in
            the label in a future release. For example, if your label is
            ${toString GPTMaxLabelLength} characters long (the maximum enforced by UEFI) and
            you're at version 9, you cannot increment this to 10.
          ''
        else
          null
      ) cfg.partitions
    );
  };

  config = {
    image.baseName =
      let
        version = config.image.repart.version;
@@ -352,6 +332,47 @@ in
        };

        finalPartitions = lib.mapAttrs addClosure cfg.partitions;

        assertions = lib.mapAttrsToList (
          fileName: partitionConfig:
          let
            inherit (partitionConfig) repartConfig;
            labelLength = builtins.stringLength repartConfig.Label;
          in
          {
            assertion = repartConfig ? Label -> GPTMaxLabelLength >= labelLength;
            message = ''
              The partition label '${repartConfig.Label}'
              defined for '${fileName}' is ${toString labelLength} characters long,
              but the maximum label length supported by UEFI is ${toString GPTMaxLabelLength}.
            '';
          }
        ) cfg.partitions;

        warnings = lib.filter (v: v != null) (
          lib.mapAttrsToList (
            fileName: partitionConfig:
            let
              inherit (partitionConfig) repartConfig;
              suggestedMaxLabelLength = GPTMaxLabelLength - 2;
              labelLength = builtins.stringLength repartConfig.Label;
            in
            if (repartConfig ? Label && labelLength >= suggestedMaxLabelLength) then
              ''
                The partition label '${repartConfig.Label}'
                defined for '${fileName}' is ${toString labelLength} characters long.
                The suggested maximum label length is ${toString suggestedMaxLabelLength}.

                If you use sytemd-sysupdate style A/B updates, this might
                not leave enough space to increment the version number included in
                the label in a future release. For example, if your label is
                ${toString GPTMaxLabelLength} characters long (the maximum enforced by UEFI) and
                you're at version 9, you cannot increment this to 10.
              ''
            else
              null
          ) cfg.partitions
        );
      };

    system.build.image =
@@ -367,8 +388,7 @@ in
        );

        mkfsEnv = mkfsOptionsToEnv cfg.mkfsOptions;
      in
      pkgs.callPackage ./repart-image.nix {
        val = pkgs.callPackage ./repart-image.nix {
          systemd = cfg.package;
          imageFileBasename = config.image.baseName;
          inherit (cfg)
@@ -382,6 +402,8 @@ in
            ;
          inherit fileSystems definitionsDirectory mkfsEnv;
        };
      in
      lib.asserts.checkAssertWarn cfg.assertions cfg.warnings val;
  };

  meta.maintainers = with lib.maintainers; [