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"; optionalString fixBinary "F";
in ":${name}:${type}:${offset'}:${magicOrExtension}:${mask'}:${interpreter}:${flags}"; in ":${name}:${type}:${offset'}:${magicOrExtension}:${mask'}:${interpreter}:${flags}";
activationSnippet = name: { interpreter, ... }: '' activationSnippet = name: { interpreter, wrapInterpreterInShell, ... }: if wrapInterpreterInShell then ''
rm -f /run/binfmt/${name} rm -f /run/binfmt/${name}
cat > /run/binfmt/${name} << 'EOF' cat > /run/binfmt/${name} << 'EOF'
#!${pkgs.bash}/bin/sh #!${pkgs.bash}/bin/sh
exec -- ${interpreter} "$@" exec -- ${interpreter} "$@"
EOF EOF
chmod +x /run/binfmt/${name} 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; 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: # Mapping of systems to “magicOrExtension” and “mask”. Mostly taken from:
# - https://github.com/cleverca22/nixos-configs/blob/master/qemu.nix # - https://github.com/cleverca22/nixos-configs/blob/master/qemu.nix
@ -238,6 +242,25 @@ in {
''; '';
type = types.bool; 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 = { config = {
boot.binfmt.registrations = builtins.listToAttrs (map (system: { boot.binfmt.registrations = builtins.listToAttrs (map (system: {
name = system; name = system;
value = { value = let
interpreter = getEmulator system; 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}")); } // (magics.${system} or (throw "Cannot create binfmt registration for system ${system}"));
}) cfg.emulatedSystems); }) cfg.emulatedSystems);
# TODO: add a nix.extraPlatforms option to NixOS! # TODO: add a nix.extraPlatforms option to NixOS!
nix.extraOptions = lib.mkIf (cfg.emulatedSystems != []) '' nix.extraOptions = lib.mkIf (cfg.emulatedSystems != []) ''
extra-platforms = ${toString (cfg.emulatedSystems ++ lib.optional pkgs.stdenv.hostPlatform.isx86_64 "i686-linux")} extra-platforms = ${toString (cfg.emulatedSystems ++ lib.optional pkgs.stdenv.hostPlatform.isx86_64 "i686-linux")}
''; '';
nix.sandboxPaths = lib.mkIf (cfg.emulatedSystems != []) nix.sandboxPaths = lib.mkIf (cfg.emulatedSystems != []) (
([ "/run/binfmt" "${pkgs.bash}" ] ++ (map (system: dirOf (dirOf (getEmulator system))) 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" environment.etc."binfmt.d/nixos.conf".source = builtins.toFile "binfmt_nixos.conf"
(lib.concatStringsSep "\n" (lib.mapAttrsToList makeBinfmtLine config.boot.binfmt.registrations)); (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, # Teach the kernel how to run armv7l and aarch64-linux binaries,
# and run GNU Hello for these architectures. # and run GNU Hello for these architectures.
import ./make-test-python.nix ({ pkgs, ... }: {
name = "systemd-binfmt"; { system ? builtins.currentSystem,
machine = { config ? {},
boot.binfmt.emulatedSystems = [ pkgs ? import ../.. { inherit system config; }
"armv7l-linux" }:
"aarch64-linux"
]; 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 preserveArgvZero = makeTest {
helloArmv7l = pkgs.pkgsCross.armv7l-hf-multiplatform.hello; name = "systemd-binfmt-preserve-argv0";
helloAarch64 = pkgs.pkgsCross.aarch64-multiplatform.hello; machine = {
in '' boot.binfmt.emulatedSystems = [
machine.start() "aarch64-linux"
assert "world" in machine.succeed( ];
"${helloArmv7l}/bin/hello" };
) testScript = let
assert "world" in machine.succeed( testAarch64 = expectArgv0 pkgs.pkgsCross.aarch64-multiplatform;
"${helloAarch64}/bin/hello" in ''
) machine.start()
''; machine.succeed("exec -a meow ${testAarch64} meow")
}) '';
};
}