Unverified Commit d44717c4 authored by Sandro Jäckel's avatar Sandro Jäckel Committed by GitHub
Browse files

nixos/binfmt: Add option to use static emulators when available (#334859)

parents 268beb4e fc0cc3fe
Loading
Loading
Loading
Loading
+5 −20
Original line number Diff line number Diff line
@@ -277,25 +277,6 @@ let
      let
        selectEmulator = pkgs:
          let
            qemu-user = pkgs.qemu.override {
              smartcardSupport = false;
              spiceSupport = false;
              openGLSupport = false;
              virglSupport = false;
              vncSupport = false;
              gtkSupport = false;
              sdlSupport = false;
              alsaSupport = false;
              pulseSupport = false;
              pipewireSupport = false;
              jackSupport = false;
              smbdSupport = false;
              seccompSupport = false;
              tpmSupport = false;
              capstoneSupport = false;
              enableDocs = false;
              hostCpuTargets = [ "${final.qemuArch}-linux-user" ];
            };
            wine = (pkgs.winePackagesFor "wine${toString final.parsed.cpu.bits}").minimal;
          in
          # Note: we guarantee that the return value is either `null` or a path
@@ -306,7 +287,7 @@ let
          else if final.isWindows
          then "${wine}/bin/wine${optionalString (final.parsed.cpu.bits == 64) "64"}"
          else if final.isLinux && pkgs.stdenv.hostPlatform.isLinux && final.qemuArch != null
          then "${qemu-user}/bin/qemu-${final.qemuArch}"
          then "${pkgs.qemu-user}/bin/qemu-${final.qemuArch}"
          else if final.isWasi
          then "${pkgs.wasmtime}/bin/wasmtime"
          else if final.isMmix
@@ -315,6 +296,10 @@ let
      in {
        emulatorAvailable = pkgs: (selectEmulator pkgs) != null;

        # whether final.emulator pkgs.pkgsStatic works
        staticEmulatorAvailable = pkgs: final.emulatorAvailable pkgs
          && (final.isLinux || final.isWasi || final.isMmix);

        emulator = pkgs:
          if (final.emulatorAvailable pkgs)
          then selectEmulator pkgs
+1 −0
Original line number Diff line number Diff line
@@ -96,6 +96,7 @@ lib.runTests (
        canExecute = null;
        emulator = null;
        emulatorAvailable = null;
        staticEmulatorAvailable = null;
        isCompatible = null;
      }?${platformAttrName};
    };
+28 −8
Original line number Diff line number Diff line
@@ -28,8 +28,6 @@ let
         ''
    else interpreter;

  getEmulator = system: (lib.systems.elaborate { inherit system; }).emulator pkgs;
  getQemuArch = system: (lib.systems.elaborate { inherit system; }).qemuArch;

  # Mapping of systems to “magicOrExtension” and “mask”. Mostly taken from:
  # - https://github.com/cleverca22/nixos-configs/blob/master/qemu.nix
@@ -280,28 +278,50 @@ in {
        '';
        type = types.listOf (types.enum (builtins.attrNames magics));
      };

      preferStaticEmulators = mkOption {
        default = false;
        description = ''
          Whether to use static emulators when available.

          This enables the kernel to preload the emulator binaries when
          the binfmt registrations are added, obviating the need to make
          the emulator binaries available inside chroots and chroot-like
          sandboxes.
        '';
        type = types.bool;
      };
    };
  };

  config = {
    assertions = lib.mapAttrsToList (name: reg: {
      assertion = reg.fixBinary -> !reg.wrapInterpreterInShell;
      message = "boot.binfmt.registrations.\"${name}\" cannot have fixBinary when the interpreter is invoked through a shell.";
    }) cfg.registrations;

    boot.binfmt.registrations = builtins.listToAttrs (map (system: assert system != pkgs.stdenv.hostPlatform.system; {
      name = system;
      value = { config, ... }: let
        interpreter = getEmulator system;
        qemuArch = getQemuArch system;
        elaborated = lib.systems.elaborate { inherit system; };
        useStaticEmulator = cfg.preferStaticEmulators && elaborated.staticEmulatorAvailable pkgs;
        interpreter = elaborated.emulator (if useStaticEmulator then pkgs.pkgsStatic else pkgs);

        inherit (elaborated) qemuArch;
        isQemu = "qemu-${qemuArch}" == baseNameOf interpreter;

        preserveArgvZero = "qemu-${qemuArch}" == baseNameOf interpreter;
        interpreterReg = let
          wrapperName = "qemu-${qemuArch}-binfmt-P";
          wrapper = pkgs.wrapQemuBinfmtP wrapperName interpreter;
        in
          if preserveArgvZero then "${wrapper}/bin/${wrapperName}"
          if isQemu && !useStaticEmulator then "${wrapper}/bin/${wrapperName}"
          else interpreter;
      in ({
        preserveArgvZero = mkDefault preserveArgvZero;
        preserveArgvZero = mkDefault isQemu;

        interpreter = mkDefault interpreterReg;
        wrapInterpreterInShell = mkDefault (!config.preserveArgvZero);
        fixBinary = mkDefault useStaticEmulator;
        wrapInterpreterInShell = mkDefault (!config.preserveArgvZero && !config.fixBinary);
        interpreterSandboxPath = mkDefault (dirOf (dirOf config.interpreter));
      } // (magics.${system} or (throw "Cannot create binfmt registration for system ${system}")));
    }) cfg.emulatedSystems);
+25 −0
Original line number Diff line number Diff line
@@ -87,4 +87,29 @@ in {
      ).lower()
    '';
  };

  chroot = makeTest {
    name = "systemd-binfmt-chroot";
    nodes.machine = { pkgs, lib, ... }: {
      boot.binfmt.emulatedSystems = [
        "aarch64-linux" "wasm32-wasi"
      ];
      boot.binfmt.preferStaticEmulators = true;

      environment.systemPackages = [
        (pkgs.writeShellScriptBin "test-chroot" ''
          set -euo pipefail
          mkdir -p /tmp/chroot
          cp ${lib.getExe' pkgs.pkgsCross.aarch64-multiplatform.pkgsStatic.busybox "busybox"} /tmp/chroot/busybox
          cp ${lib.getExe pkgs.pkgsCross.wasi32.yaml2json} /tmp/chroot/yaml2json # wasi binaries that build are hard to come by
          chroot /tmp/chroot /busybox uname -m | grep aarch64
          echo 42 | chroot /tmp/chroot /yaml2json | grep 42
        '')
      ];
    };
    testScript = ''
      machine.start()
      machine.succeed("test-chroot")
    '';
  };
}
+5 −8
Original line number Diff line number Diff line
@@ -94,10 +94,9 @@ stdenv.mkDerivation (finalAttrs: {
    ++ lib.optionals stdenv.hostPlatform.isDarwin [ sigtool ]
    ++ lib.optionals (!userOnly) [ dtc ];

  buildInputs = [ zlib glib pixman
    vde2 lzo snappy libtasn1
    gnutls nettle curl libslirp
  ]
  buildInputs = [ glib zlib ]
    ++ lib.optionals (!minimal) [ dtc pixman vde2 lzo snappy libtasn1 gnutls nettle libslirp ]
    ++ lib.optionals (!userOnly) [ curl ]
    ++ lib.optionals ncursesSupport [ ncurses ]
    ++ lib.optionals stdenv.hostPlatform.isDarwin [ CoreServices Cocoa Hypervisor Kernel rez setfile vmnet ]
    ++ lib.optionals seccompSupport [ libseccomp ]
@@ -112,8 +111,7 @@ stdenv.mkDerivation (finalAttrs: {
    ++ lib.optionals smartcardSupport [ libcacard ]
    ++ lib.optionals spiceSupport [ spice-protocol spice ]
    ++ lib.optionals usbredirSupport [ usbredir ]
    ++ lib.optionals stdenv.hostPlatform.isLinux [ libcap_ng libcap attr ]
    ++ lib.optionals (stdenv.hostPlatform.isLinux && !userOnly) [ libaio ]
    ++ lib.optionals (stdenv.hostPlatform.isLinux && !userOnly) [ libcap_ng libcap attr libaio ]
    ++ lib.optionals xenSupport [ xen ]
    ++ lib.optionals cephSupport [ ceph ]
    ++ lib.optionals glusterfsSupport [ glusterfs libuuid ]
@@ -124,8 +122,7 @@ stdenv.mkDerivation (finalAttrs: {
    ++ lib.optionals smbdSupport [ samba ]
    ++ lib.optionals uringSupport [ liburing ]
    ++ lib.optionals canokeySupport [ canokey-qemu ]
    ++ lib.optionals capstoneSupport [ capstone ]
    ++ lib.optionals (!userOnly) [ dtc ];
    ++ lib.optionals capstoneSupport [ capstone ];

  dontUseMesonConfigure = true; # meson's configurePhase isn't compatible with qemu build
  dontAddStaticConfigureFlags = true;