Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions lib/systems/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,10 @@ rec {
then selectEmulator pkgs
else throw "Don't know how to run ${final.config} executables.";

staticEmulator = pkgs:
if builtins.elem final.system pkgs.qemu-user-static.passthru.qemuUserPlatforms then
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any reason to check and return null, rather than just always returning qemu-user-static and letting the meta checks produce an error if its an unsupported platform?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is to check whether the emulated platform is supported instead of the host platform. Could you elaborate on what the change would look like?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change would be to have emulator and staticEmulator have a consistent interface. So like emulator, staticEmulator would throw if unsupported, and there'd be a staticEmulatorAvailable to mirror emulatorAvailable. I just want to make sure we don't have two interfaces that work differently for such similar concepts.

"${pkgs.qemu-user-static}/bin/qemu-${final.qemuArch}"
else null;
}) // mapAttrs (n: v: v final.parsed) inspect.predicates
// mapAttrs (n: v: v final.gcc.arch or "default") architectures.predicates
// args;
Expand Down
45 changes: 37 additions & 8 deletions nixos/modules/system/boot/binfmt.nix
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ let

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

# Mapping of systems to “magicOrExtension” and “mask”. Mostly taken from:
# - https://github.com/cleverca22/nixos-configs/blob/master/qemu.nix
Expand Down Expand Up @@ -279,39 +280,67 @@ in {
'';
type = types.listOf types.str;
};

preferStaticEmulators = mkOption {
default = false;
description = lib.mdDoc ''
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: {
name = system;
value = { config, ... }: let
interpreter = getEmulator system;
staticEmulator = getStaticEmulator system;
interpreter =
if cfg.preferStaticEmulators && staticEmulator != null then staticEmulator
else getEmulator system;
qemuArch = getQemuArch system;

preserveArgvZero = "qemu-${qemuArch}" == baseNameOf interpreter;
isQemu = "qemu-${qemuArch}" == baseNameOf interpreter;
isStaticEmulator = interpreter == staticEmulator;

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

interpreter = mkDefault interpreterReg;
wrapInterpreterInShell = mkDefault (!config.preserveArgvZero);
interpreterSandboxPath = mkDefault (dirOf (dirOf config.interpreter));
fixBinary = mkDefault isStaticEmulator;
wrapInterpreterInShell = mkDefault (!config.preserveArgvZero && !config.fixBinary);
interpreterSandboxPath = mkDefault
(if config.fixBinary then null else dirOf (dirOf config.interpreter));
} // (magics.${system} or (throw "Cannot create binfmt registration for system ${system}")));
}) cfg.emulatedSystems);
nix.settings = lib.mkIf (cfg.emulatedSystems != []) {
extra-platforms = cfg.emulatedSystems ++ lib.optional pkgs.stdenv.hostPlatform.isx86_64 "i686-linux";
extra-sandbox-paths = let
ruleFor = system: cfg.registrations.${system};
hasWrappedRule = lib.any (system: (ruleFor system).wrapInterpreterInShell) cfg.emulatedSystems;
in [ "/run/binfmt" ]
hasNonFixedRule = lib.any (system: !(ruleFor system).fixBinary) cfg.emulatedSystems;
in [ ]
++ lib.optional hasNonFixedRule "/run/binfmt"
++ lib.optional hasWrappedRule "${pkgs.bash}"
++ (map (system: (ruleFor system).interpreterSandboxPath) cfg.emulatedSystems);
++ (lib.filter (p: p != null)
(map (system: (ruleFor system).interpreterSandboxPath) cfg.emulatedSystems));
};

environment.etc."binfmt.d/nixos.conf".source = builtins.toFile "binfmt_nixos.conf"
Expand Down
23 changes: 23 additions & 0 deletions nixos/tests/systemd-binfmt.nix
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,27 @@ in {
).lower()
'';
};

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

environment.systemPackages = [
(pkgs.writeShellScriptBin "test-chroot" ''
set -euo pipefail
mkdir -p /tmp/chroot
cp ${pkgs.pkgsCross.aarch64-multiplatform.pkgsStatic.busybox}/bin/busybox /tmp/chroot/busybox
chroot /tmp/chroot /busybox uname -m | grep aarch64
'')
];
};
testScript = ''
machine.start()
machine.succeed("test-chroot")
'';
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
diff --git a/meson.build b/meson.build
index 96de1a6ef9..908d841f6a 100644
--- a/meson.build
+++ b/meson.build
@@ -420,8 +420,7 @@ zlib = dependency('zlib', required: true, kwargs: static_kwargs)
libaio = not_found
if not get_option('linux_aio').auto() or have_block
libaio = cc.find_library('aio', has_headers: ['libaio.h'],
- required: get_option('linux_aio'),
- kwargs: static_kwargs)
+ required: get_option('linux_aio'))
endif
linux_io_uring = not_found
if not get_option('linux_io_uring').auto() or have_block
25 changes: 20 additions & 5 deletions pkgs/applications/virtualization/qemu/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@
, uringSupport ? stdenv.isLinux, liburing
, canokeySupport ? false, canokey-qemu
, capstoneSupport ? true, capstone
, pluginsSupport ? !stdenv.hostPlatform.isStatic
, enableDocs ? true
, enableTools ? true
, enableBlobs ? true
, hostCpuOnly ? false
, hostCpuTargets ? (if hostCpuOnly
then (lib.optional stdenv.isx86_64 "i386-softmmu"
Expand Down Expand Up @@ -63,8 +66,9 @@ stdenv.mkDerivation rec {
pkg-config flex bison meson ninja

# Don't change this to python3 and python3.pkgs.*, breaks cross-compilation
python3Packages.python python3Packages.sphinx python3Packages.sphinx-rtd-theme
python3Packages.python
]
++ lib.optionals enableDocs [ python3Packages.sphinx python3Packages.sphinx-rtd-theme ]
++ lib.optionals gtkSupport [ wrapGAppsHook ]
++ lib.optionals hexagonSupport [ glib ]
++ lib.optionals stdenv.isDarwin [ sigtool ];
Expand Down Expand Up @@ -99,6 +103,7 @@ stdenv.mkDerivation rec {
++ lib.optionals capstoneSupport [ capstone ];

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

outputs = [ "out" ] ++ lib.optional guestAgentSupport "ga";
# On aarch64-linux we would shoot over the Hydra's 2G output limit.
Expand Down Expand Up @@ -127,7 +132,10 @@ stdenv.mkDerivation rec {
revert = true;
})
]
++ lib.optional nixosTestRunner ./force-uid0-on-9p.patch;
++ lib.optional nixosTestRunner ./force-uid0-on-9p.patch

# Remove for QEMU 8.1
++ lib.optional stdenv.hostPlatform.isStatic ./aio-find-static-library.patch;

postPatch = ''
# Otherwise tries to ensure /var/run exists.
Expand All @@ -151,7 +159,7 @@ stdenv.mkDerivation rec {
configureFlags = [
"--disable-strip" # We'll strip ourselves after separating debug info.
(lib.enableFeature enableDocs "docs")
"--enable-tools"
(lib.enableFeature enableTools "tools")
"--localstatedir=/var"
"--sysconfdir=/etc"
# Always use our Meson, not the bundled version, which doesn't
Expand All @@ -178,7 +186,12 @@ stdenv.mkDerivation rec {
++ lib.optional smbdSupport "--smbd=${samba}/bin/smbd"
++ lib.optional uringSupport "--enable-linux-io-uring"
++ lib.optional canokeySupport "--enable-canokey"
++ lib.optional capstoneSupport "--enable-capstone";
++ lib.optional capstoneSupport "--enable-capstone"
++ lib.optional (!pluginsSupport) "--disable-plugins"
++ lib.optional (!enableBlobs) "--disable-install-blobs"

# FIXME: "multiple definition of `strtoll'" with libnbcompat
++ lib.optional stdenv.hostPlatform.isStatic "--static --extra-ldflags=-Wl,--allow-multiple-definition";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice if we could figure out why this is happening. One definition is from libnbcompat — where's the other from?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems to be a conflict between libnbcompat and musl:

/nix/store/kbhvb5y763zm2xskz94j176qv2dvbv58-x86_64-unknown-linux-musl-binutils-2.40/bin/x86_64-unknown-linux-musl-ld: /nix/store/g29g3hydcrrjkisrmk96gnx23mnnldrj-musl-static-x86_64-unknown-linux-musl-1.2.3/li
b/libc.a(strtol.lo): in function `strtoll.localalias':                                                  
(.text.strtoll+0x0): multiple definition of `strtoll'; /nix/store/dp31apbsw47a9bm37w45kh96vkngh2az-compat-netbsd-static-x86_64-unknown-linux-musl-9.2/lib/libnbcompat.a(strtoll.o):(.text+0x0): first defined he
re

Also it appears that there are many more build dependencies that we can remove. I'll play around and see how much further we can reduce the input closure.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I wonder why we don't see this on other packages. I'd like to take a look, but don't know when I'll have the capacity…


dontWrapGApps = true;

Expand Down Expand Up @@ -241,7 +254,9 @@ stdenv.mkDerivation rec {

# Add a ‘qemu-kvm’ wrapper for compatibility/convenience.
postInstall = ''
ln -s $out/bin/qemu-system-${stdenv.hostPlatform.qemuArch} $out/bin/qemu-kvm
if [ -f $out/bin/qemu-system-${stdenv.hostPlatform.qemuArch} ]; then
ln -s $out/bin/qemu-system-${stdenv.hostPlatform.qemuArch} $out/bin/qemu-kvm
fi
'';

passthru = {
Expand Down
52 changes: 52 additions & 0 deletions pkgs/applications/virtualization/qemu/user-static.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{ lib, pkgsStatic }:

let
qemuUserPlatforms = [
"aarch64-linux"
"armv7l-linux"
"i386-linux"
"powerpc-linux"
"powerpc64-linux"
"powerpc64le-linux"
"riscv32-linux"
"riscv64-linux"
"x86_64-linux"
];

qemuTargets = map (system: (lib.systems.elaborate { inherit system; }).qemuArch + "-linux-user") qemuUserPlatforms;

static = (pkgsStatic.qemu.override {
guestAgentSupport = false;
numaSupport = false;
seccompSupport = false;
alsaSupport = false;
pulseSupport = false;
sdlSupport = false;
jackSupport = false;
gtkSupport = false;
vncSupport = false;
smartcardSupport = false;
spiceSupport = false;
ncursesSupport = false;
libiscsiSupport = false;
smbdSupport = false;
tpmSupport = false;
uringSupport = false;
capstoneSupport = false;
enableDocs = false;
enableTools = false;
enableBlobs = false;
hostCpuTargets = qemuTargets;
}).overrideAttrs (old: {
# HACK: Otherwise the result will have the entire buildinput closure
# injected by the pkgsStatic stdenv
# <https://github.com/NixOS/nixpkgs/issues/83667>
postFixup = (old.postFixup or "") + ''
rm -f $out/nix-support/propagated-build-inputs
'';
});
in static // {
passthru = (static.passthru or {}) // {
inherit qemuUserPlatforms;
};
}
2 changes: 2 additions & 0 deletions pkgs/top-level/all-packages.nix
Original file line number Diff line number Diff line change
Expand Up @@ -33652,6 +33652,8 @@ with pkgs;
inherit (darwin) sigtool;
};

qemu-user-static = callPackage ../applications/virtualization/qemu/user-static.nix { };

qemu-utils = callPackage ../applications/virtualization/qemu/utils.nix { };

canokey-qemu = callPackage ../applications/virtualization/qemu/canokey-qemu.nix { };
Expand Down