directory.getdirectories跨卷失败是因.net对win32 findfirstfileex的封装限制,遇挂载点或符号链接可能静默跳过或抛unauthorizedaccessexception;推荐改用directoryinfo.enumeratedirectories获取明确异常反馈。

Windows 上 Directory.GetDirectories 为什么跨卷失败?
它根本不是隔离机制,只是普通 API 调用 —— 默认不检查路径合法性,但遇到跨 NTFS 卷的符号链接或挂载点时,可能静默跳过或抛 UnauthorizedAccessException。这不是容器行为,是 .NET 运行时对底层 Win32 FindFirstFileEx 的封装限制。
- 真实场景:D:data 挂载为 Z:,代码调用
Directory.GetDirectories("Z:\")却返回空数组,而cmd /c dir Z:能看到内容 - 原因:.NET 6+ 默认启用
AppContext.SetSwitch("System.IO.EnableUnsafePathTraversal", false),主动拦截非标准路径解析逻辑 - 临时绕过(不推荐):
AppContext.SetSwitch("System.IO.EnableUnsafePathTraversal", true),但会削弱路径遍历防护 - 更安全做法:改用
DirectoryInfo+EnumerateDirectories,它对重解析点(Reparse Points)有更明确的异常反馈
Docker for Windows 容器里 File.Exists 返回 false 的真实原因
不是 .NET 的 bug,是 Linux 容器运行时(LCOW)与 Windows 主机文件系统之间的挂载语义不一致导致的。Windows 主机上的 C:ppconfig.json 映射进容器后路径变成 /app/config.json,但容器内 .NET 进程看到的是 ext4 文件系统视图,而 Windows 主机上该路径可能被杀毒软件加锁、或挂载时用了 ro(只读)标志。
- 典型错误现象:
File.Exists("/app/config.json")返回false,但ls -l /app/能列出该文件 - 排查顺序:先在容器内执行
stat /app/config.json,看是否报No such file or directory;再查 Docker run 命令是否漏了-v C:pp:/app:rw中的:rw - Windows 特有坑:如果主机路径含中文或空格,且未用双引号包裹
-v参数,Docker CLI 会截断路径,File.Exists必然失败 - 兼容性注意:.NET 7+ 在容器中默认启用
System.IO.UseLegacyPathHandling=false,对 UNC 和混合路径更严格,旧代码迁移时需验证
AssemblyLoadContext 能不能隔离文件系统访问?
不能。它的职责是程序集加载域隔离,和文件 I/O 完全无关。有人误以为用独立 AssemblyLoadContext 加载一个 DLL 就能“沙箱化”其所有 File.ReadAllBytes 调用,结果发现它照样能读取宿主进程有权限的任意路径。
- 常见误解来源:把 .NET 的“上下文”概念和操作系统级命名空间(如 mount namespace)混淆了
- 真正起作用的是容器 runtime(如 containerd)配置的
rootfs和mount选项,不是 .NET 层能控制的 - 若需运行时路径白名单,只能靠外部手段:比如在容器启动前用
chmod -R a-w /etc锁定敏感目录,或用 seccomp profile 拦截openat系统调用 - 性能影响:强行在
AssemblyLoadContext中注入自定义FileStream替换逻辑,会导致所有 IO 经过额外委托跳转,实测吞吐下降 15%~20%
为什么 Path.GetFullPath 在容器里返回 /app/./config.json 而不是 /app/config.json?
因为 .NET 对路径规范化的行为依赖于运行时检测到的操作系统类型,而 Docker 容器常以 Linux 内核运行,即使镜像基于 mcr.microsoft.com/dotnet/runtime:8.0-windowsservercore,只要实际跑在 WSL2 或 LCOW 下,Path 类就按 Unix 规则处理 . 和 ..,不会合并掉当前目录段。
- 后果:某些依赖字符串相等判断的逻辑失效,比如用
path == Path.GetFullPath(path)做缓存 key - 解决方法:不用
==,改用string.Equals(Path.GetFullPath(a), Path.GetFullPath(b), StringComparison.Ordinal) - 更稳妥方案:在容器入口脚本里统一做路径预处理,比如
sed -i 's|/app/./|/app/|g' /app/config.json(仅限配置文件) - 容易忽略的一点:
Path.GetFullPath在 Windows 容器中返回带盘符路径(C:ppconfig.json),但在 Linux 容器中永远返回类 Unix 格式,跨平台部署时必须用Path.IsPathRooted判断而非字符串匹配










