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] 中提出这个问题,但没有得到任何回应,所以我希望这里的人可以提供提示。
简短摘要。我相信:
/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 路径,因此,如果它在进程链中,您的程序将失败。
我发现如果我从 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] 中提出这个问题,但没有得到任何回应,所以我希望这里的人可以提供提示。
简短摘要。我相信:
/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?
这部分(我相信)相当容易回答,尽管不是决定性的。如 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
.
回到我们定期安排的...
但除此之外,您甚至不需要示例代码来演示这一点。您可以通过从 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 路径,因此,如果它在进程链中,您的程序将失败。