summaryrefslogtreecommitdiff
path: root/modules/interop.nix
blob: d055d4ba3f937df6dfefa02f72541d3483875588 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
{ lib, pkgs, config, ... }:

with builtins; with lib;
{
  imports = [
    (mkRenamedOptionModule [ "wsl" "compatibility" "interopPreserveArgvZero" ] [ "wsl" "interop" "preserveArgvZero" ])
  ];

  options.wsl.interop = with types; {
    register = mkOption {
      type = bool;
      default = false; # Use the existing registration by default
      description = "Explicitly register the binfmt_misc handler for Windows executables";
    };

    includePath = mkOption {
      type = bool;
      default = true;
      description = "Include Windows PATH in WSL PATH";
    };

    preserveArgvZero = mkOption {
      type = nullOr bool;
      default = null;
      description = ''
        Register binfmt interpreter for Windows executables with 'preserves argv[0]' flag.

        Default (null): autodetect, at some performance cost.
        To avoid the performance cost, set this to true for WSL Preview 0.58 and up,
        or to false for any older versions, including pre-Microsoft Store and Windows 10.
      '';
    };
  };

  config =
    let
      cfg = config.wsl.interop;
    in
    mkIf config.wsl.enable {

      boot.binfmt.registrations = mkIf cfg.register {
        WSLInterop =
          let
            compat = cfg.preserveArgvZero;

            # WSL Preview 0.58 and up registers the /init binfmt interp for Windows executable
            # with the "preserve argv[0]" flag, so if you run `./foo.exe`, the interp gets invoked
            # as `/init foo.exe ./foo.exe`.
            #   argv[0] --^        ^-- actual path
            #
            # Older versions expect to be called without the argv[0] bit, simply as `/init ./foo.exe`.
            #
            # We detect that by running `/init /known-not-existing-path.exe` and checking the exit code:
            # the new style interp expects at least two arguments, so exits with exit code 1,
            # presumably meaning "parsing error"; the old style interp attempts to actually run
            # the executable, fails to find it, and exits with 255.
            compatWrapper = pkgs.writeShellScript "nixos-wsl-binfmt-hack" ''
              /init /nixos-wsl-does-not-exist.exe
              [ $? -eq 255 ] && shift
              exec /init "$@"
            '';

            # use the autodetect hack if unset, otherwise call /init directly
            interpreter = if compat == null then compatWrapper else "/init";

            # enable for the wrapper and autodetect hack
            preserveArgvZero = if compat == false then false else true;
          in
          {
            magicOrExtension = "MZ";
            fixBinary = true;
            wrapInterpreterInShell = false;
            inherit interpreter preserveArgvZero;
          };
      };

      warnings =
        let
          registrations = config.boot.binfmt.registrations;
        in
        optional (!(registrations ? WSLInterop) && (length (attrNames config.boot.binfmt.registrations)) != 0) "Having any binfmt registrations without re-registering WSLInterop (wsl.interop.register) will break running .exe files from WSL2";
    };


}