nixos/binfmt: Preserve argv[0] when using QEMU

This commit is contained in:
Zhaofeng Li 2021-10-28 01:03:50 -07:00
parent 3011531cf9
commit 9e5d0a9458
2 changed files with 115 additions and 24 deletions

View file

@ -20,16 +20,20 @@ let
optionalString fixBinary "F";
in ":${name}:${type}:${offset'}:${magicOrExtension}:${mask'}:${interpreter}:${flags}";
activationSnippet = name: { interpreter, ... }: ''
activationSnippet = name: { interpreter, wrapInterpreterInShell, ... }: if wrapInterpreterInShell then ''
rm -f /run/binfmt/${name}
cat > /run/binfmt/${name} << 'EOF'
#!${pkgs.bash}/bin/sh
exec -- ${interpreter} "$@"
EOF
chmod +x /run/binfmt/${name}
'' else ''
rm -f /run/binfmt/${name}
ln -s ${interpreter} /run/binfmt/${name}
'';
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
@ -238,6 +242,25 @@ in {
'';
type = types.bool;
};
wrapInterpreterInShell = mkOption {
default = true;
description = ''
Whether to wrap the interpreter in a shell script.
This allows a shell command to be set as the interpreter.
'';
type = types.bool;
};
interpreterSandboxPath = mkOption {
internal = true;
default = null;
description = ''
Path of the interpreter to expose in the build sandbox.
'';
type = types.nullOr types.path;
};
};
}));
};
@ -257,16 +280,37 @@ in {
config = {
boot.binfmt.registrations = builtins.listToAttrs (map (system: {
name = system;
value = {
value = let
interpreter = getEmulator system;
qemuArch = getQemuArch system;
preserveArgvZero = "qemu-${qemuArch}" == baseNameOf interpreter;
interpreterReg = let
wrapperName = "qemu-${qemuArch}-binfmt-P";
wrapper = pkgs.wrapQemuBinfmtP wrapperName interpreter;
in
if preserveArgvZero then "${wrapper}/bin/${wrapperName}"
else interpreter;
in {
inherit preserveArgvZero;
interpreter = interpreterReg;
wrapInterpreterInShell = !preserveArgvZero;
interpreterSandboxPath = dirOf (dirOf interpreterReg);
} // (magics.${system} or (throw "Cannot create binfmt registration for system ${system}"));
}) cfg.emulatedSystems);
# TODO: add a nix.extraPlatforms option to NixOS!
nix.extraOptions = lib.mkIf (cfg.emulatedSystems != []) ''
extra-platforms = ${toString (cfg.emulatedSystems ++ lib.optional pkgs.stdenv.hostPlatform.isx86_64 "i686-linux")}
'';
nix.sandboxPaths = lib.mkIf (cfg.emulatedSystems != [])
([ "/run/binfmt" "${pkgs.bash}" ] ++ (map (system: dirOf (dirOf (getEmulator system))) cfg.emulatedSystems));
nix.sandboxPaths = lib.mkIf (cfg.emulatedSystems != []) (
let
ruleFor = system: cfg.registrations.${system};
hasWrappedRule = lib.any (system: (ruleFor system).wrapInterpreterInShell) cfg.emulatedSystems;
in [ "/run/binfmt" ]
++ lib.optional hasWrappedRule "${pkgs.bash}"
++ (map (system: (ruleFor system).interpreterSandboxPath) cfg.emulatedSystems)
);
environment.etc."binfmt.d/nixos.conf".source = builtins.toFile "binfmt_nixos.conf"
(lib.concatStringsSep "\n" (lib.mapAttrsToList makeBinfmtLine config.boot.binfmt.registrations));

View file

@ -1,24 +1,71 @@
# Teach the kernel how to run armv7l and aarch64-linux binaries,
# and run GNU Hello for these architectures.
import ./make-test-python.nix ({ pkgs, ... }: {
name = "systemd-binfmt";
machine = {
boot.binfmt.emulatedSystems = [
"armv7l-linux"
"aarch64-linux"
];
{ system ? builtins.currentSystem,
config ? {},
pkgs ? import ../.. { inherit system config; }
}:
with import ../lib/testing-python.nix { inherit system pkgs; };
let
expectArgv0 = xpkgs: xpkgs.runCommandCC "expect-argv0" {
src = pkgs.writeText "expect-argv0.c" ''
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv) {
fprintf(stderr, "Our argv[0] is %s\n", argv[0]);
if (strcmp(argv[0], argv[1])) {
fprintf(stderr, "ERROR: argv[0] is %s, should be %s\n", argv[0], argv[1]);
return 1;
}
return 0;
}
'';
} ''
$CC -o $out $src
'';
in {
basic = makeTest {
name = "systemd-binfmt";
machine = {
boot.binfmt.emulatedSystems = [
"armv7l-linux"
"aarch64-linux"
];
};
testScript = let
helloArmv7l = pkgs.pkgsCross.armv7l-hf-multiplatform.hello;
helloAarch64 = pkgs.pkgsCross.aarch64-multiplatform.hello;
in ''
machine.start()
assert "world" in machine.succeed(
"${helloArmv7l}/bin/hello"
)
assert "world" in machine.succeed(
"${helloAarch64}/bin/hello"
)
'';
};
testScript = let
helloArmv7l = pkgs.pkgsCross.armv7l-hf-multiplatform.hello;
helloAarch64 = pkgs.pkgsCross.aarch64-multiplatform.hello;
in ''
machine.start()
assert "world" in machine.succeed(
"${helloArmv7l}/bin/hello"
)
assert "world" in machine.succeed(
"${helloAarch64}/bin/hello"
)
'';
})
preserveArgvZero = makeTest {
name = "systemd-binfmt-preserve-argv0";
machine = {
boot.binfmt.emulatedSystems = [
"aarch64-linux"
];
};
testScript = let
testAarch64 = expectArgv0 pkgs.pkgsCross.aarch64-multiplatform;
in ''
machine.start()
machine.succeed("exec -a meow ${testAarch64} meow")
'';
};
}