POSIX 本机应用程序的 WSL POSIX 路径如何转换为 UNC?

How are WSL POSIX paths converted to UNC for Windows native applications?

我发现如果我从 WSL2 执行 Windows 本机程序 (PE),访问 POSIX 路径会神奇地起作用。

例如,如果我从 WSL bash 执行我的程序,我可以访问 /dev/random,但如果我从 CMD(命令提示符)执行相同的程序,我就不能。

我必须了解允许这样做的机制! :)

测试程序相当简单:

#include <stdio.h>
int main(int argc, char *argv[], char *envp[]) {
    printf("%p\n", fopen("/dev/urandom", "r"));
    return 0;
}

如果我从 WSL 实例内部执行此操作,它会成功打开设备。

但是,如果我通过 CMD 执行它,它会失败。

当我查看 API mon 时,我可以看到 open("/dev/urandom", "r") 已转换为 CreateFileA("\wsl.localhost\Ubuntu\dev\urandom", ...)

第一个问题:什么组件在做这个转换?

如果我用 CreateFile 替换 fopen 它会失败...所以它一定是 stdio 函数中的东西。

第二个问题:它怎么知道哪个WSL实例是父实例?

我没有看到 API 查询,没有环境给我提示。我能看到的唯一异常是进程启动时的开口\wsl.localhost\Ubuntu\tmp

第三个问题:这是否嵌套在进程树中?

当我从 WSL 内部执行 cmd.exe,然后执行我的测试程序时,它失败了。

但是,我编写了自己的本机 Windows 程序来执行我的测试程序并且测试程序成功,因此此行为确实存在于进程树中。

谁能解释一下这个魔法起作用的机制?什么API?哪个组件正在执行转换?上下文存储在哪里?它是如何查询的?它如何知道要查找哪个发行版?

我试图在 Microsoft 讨论[1] 中提出这个问题,但没有得到任何回应,所以我希望这里的人可以提供提示。

[1] https://github.com/microsoft/WSL/discussions/8212

简短摘要。我相信:

  • /init 处理传递给 Windows 可执行文件的 工作目录 的转换。
  • 当路径以目录分隔符(例如 /\)开始时,fopen 认为它是相对于工作目录卷的根。

例如:

  • 如果您从 /home/<username>
  • 执行代码
  • ... 那么工作目录将是 \wsl.localhost\Ubuntu\home\<username>.
  • ...“卷”(在本例中为共享名称)将是 \wsl.localhost\Ubuntu\
  • ...所以 /dev/random 打开为 \wsl.localhost\Ubuntu\dev\random.

试试这个,但是:

  • cd /mnt/c(或该坐骑内的任何位置)
  • 通过 /full/path/to/the.exe 调用您的程序。
  • fopen 在我的测试中失败了(我想你也会这样),因为...
  • ...传入的工作目录是C:\(或其子目录)。
  • ... 因此卷名也是 C:\.
  • ...和 ​​fopen 试图打开 C:\dev\random,它不存在。

更多详情:

What component is doing this conversion?

这部分(我相信)相当容易回答,尽管不是决定性的。如 中所述,当您在 WSL 中启动 Windows 可执行文件时,它会使用在 binfmt_misc 中注册的处理程序(参见 cat /proc/sys/fs/binfmt_misc/WSLInterop)来调用 WSL /init .

不幸的是,WSL 的 /init 是闭源的,因此很难全面了解启动过程中发生的情况。但我认为我们可以有把握地说处理程序 (/init) 将成为在 Windows 进程接收路径之前转换路径的组件。

需要注意的一件有趣的事情是 wslpath 命令通过符号链接映射到相同的二进制文件。当使用名称 wslpath 调用时,/init 二进制文件将进行 OS 路径转换。例如:

wslpath -w /dev/random
# \wsl.localhost\Ubuntu\dev\random
但真正的问题是...

所以我们知道 /init 知道如何转换路径,但是 它在启动 Windows 二进制文件时究竟转换了什么 ?这有点棘手,但我想我们可以推测被转换的是当前工作目录的路径。

试试这些简单的实验:

$ cd /home
$ wslpath -w .
\wsl.localhost\Ubuntu\home
$ powershell.exe -c "Get-Location"

Path
----
Microsoft.PowerShell.Core\FileSystem::\wsl.localhost\Ubuntu\home

$ cd /dev
$ wslpath -w .
\wsl.localhost\Ubuntu\dev
$ powershell.exe -c "Get-Location"

Path
----
Microsoft.PowerShell.Core\FileSystem::\wsl.localhost\Ubuntu\dev

$ cd /mnt/c
$ wslpath -w .
C:\
$ powershell.exe -c "Get-Location"

Path
----
C:\

还有一个问题

所以这是我的问题 -- Windows API 什么时候开始巧妙地连接以目录分隔符开头的 UNC 工作目录和路径?我找不到关于该行为的文档,但它显然有效。而且它 不是 特定于 WSL。当使用 UNC 工作目录进行常规网络共享时,我观察到相同的串联行为。

更奇怪的是 .NET 的 path handling is not this smart about UNC concatenation. From the doc,我们用 fopen 观察到的行为预期用于 DOS 路径,但对于 UNC:

UNC paths must always be fully qualified. They can include relative directory segments (. and ..), but these must be part of a fully qualified path. You can use relative paths only by mapping a UNC path to a drive letter.

而且我能够通过简单的 Get-Content.

在 PowerShell 中确认该行为
回到我们定期安排的...

但除此之外,您甚至不需要示例代码来演示这一点。您可以通过从 WSL 中调用 notepad.exe 来看到相同的行为:

$ cd /etc
$ notepad.exe /home/<username>/testfile.txt
# Creates or opens the proper file using \wsl.localhost\Ubuntu\home\<username>\testfile.txt

$ cd /mnt/c/Users
$ notepad.exe /home/<username>/testfile.txt
# Results in "The system cannot find the path specified", because it is really attempting to open C:\home\<username>/testfile.txt, and the `home` directory (likely) doesn't exist at that path.
以及您的其他相关问题:

How does it know what WSL instance is the parent?

如果现在还不清楚,我认为可以肯定地说 WSL /init 知道您所在的 WSL 实例,因为它无论如何都在“编排”整个事情。

Does this survive nested within process tree?

只要一个进程不更改树中下一个进程的工作目录,就可以。但是,CMD 不理解 UNC 路径,因此,如果它在进程链中,您的程序将失败。