Repository navigation
[Bug] fs-local 的原子写在「mkdir -p 已存在目录返回 EPERM」的文件系统上全不可写(SSHFS/SFTP 挂载盘必踩) #9376
Replies: 2 comments
|
我对着当前 master(d743267)核实了,两层原因都成立。packages/fs/fs-local/src/fsio.ts 的 writeFileAtomic 无条件 mkdir(directory, { recursive: true }),随后 staging 目录的 chmod(0o700)、临时文件的 handle.chmod(0o600) 和保留原文件 mode 的 handle.chmod(mode) 都没有容错。 我按你的思路实现了,推到了 fork: https://github.com/Mide69/deepseek-harness/tree/fix/fs-local-atomic-write-mkdir-eperm 和你本地补丁有两处刻意不同:
加了 ensureDirectory 和 applyStagingMode 两个小函数,用注入的错误码测了每个分支(existing dir 接受 EPERM/EACCES,文件和查不到的路径重新抛出,其他错误码重新抛出,win32 与 linux 对 EINVAL 的不同处理),fsio.ts 覆盖率仍是 100%。fs-local 整个测试套件通过,typecheck 和 lint 通过,README 中英文和 Agent Note 也补了。 我这边没有 SFTP 挂载环境,所以真实挂载上的行为是按你的报告和注入的错误码验证的,没有在 sshfs-win 上亲测。你要是方便,用你那套「绝对 file URL 直接 import」的方式在你的挂载上跑一遍这个分支会很有价值。 你说的 VolumePrefix 和 FileSecurity 两个挂载参数不在这个包里,我没动。 |
|
在真实 SSHFS 挂载上跑完了这个分支,全绿。补上你提到的那块验证。 环境与验证方式
真实挂载上的结果第 1 步正是关键:目标目录预先存在, 错误注入对照(补充证据)把加载进来的 bundle 里
第三行是特意验的: 两点与分支无关的观察
再次感谢—— |
Uh oh!
There was an error while loading. Please reload this page.
摘要
@deepseek-ai/dsh-fs-local的原子写在**「对已存在目录执行mkdir -p会返回EPERM」的文件系统**上完全不可用:模型侧的write/edit工具、以及dsh-better-sidebar的fs.write都会在真正写文件之前中止。通过 SSHFS 挂载的远端目录(sshfs-win + WinFsp)是必然踩到的一类——这类挂载只能走 SFTP,而 SFTP v3 没有「已存在」这个错误码。环境
0.2.0-rc.2,Windows 11SSHFS-Win 3.5.20357+WinFsp 2.1.25156,把远端 Debian 12(OpenSSH 9.2p1)的/root挂成本地盘符X:症状
read工具:正常(能读远端文件,中文也正常)write工具:EPERM: operation not permitted, mkdir '\\sshfs\<host>\'edit工具:同一处EPERMdsh-better-sidebar的fs.write):同一处EPERM根因
第一层:
mkdir -p的语义差异。packages/fs/fs-local的writeFileAtomic()无条件执行:Node 的递归
mkdir只有在拿到EEXIST时才把「目录已存在」当成成功。而 OpenSSH 的sftp-server在errno_to_portable()里把EEXIST落在default分支 → 通用SSH2_FX_FAILURE;sshfs 再把SSH_FX_FAILURE映射成EPERM。于是对已存在目录的mkdir -p返回EPERM,libuv 原样抛出,写入在第一步就结束。这不能归咎于某一个实现:SFTP 协议本身没有「已存在」错误码,所有走 SFTP 的挂载盘都会有同样的表现。可以独立复现:
md X:\nbr(目录已存在)在 cmd 里同样报「拒绝访问」,而不是「已存在」。第二层:staging 的 chmod。 即使跨过第一层,紧随的三处调用在同一个挂载上返回
EINVAL(fchmod):代码自己的注释已写明这类 mode 在 Windows 上是 inert("inert as a mode on Windows but identifies replacement security semantics"),但失败目前仍是致命的。
建议的修法(我们已在本地改并验证通过)
mkdir容忍EPERM/EACCES,并stat确认目标确实是目录;其他错误原样抛出。win32下把 staging 的两处chmod降级为尽力而为,其他平台保持严格语义。验证方式(不用重启 DSH 也能验)
用绝对 file URL 直接 import 打过补丁的真实
dsh-fs-local,new LocalFileSystem(new Context(), { cwd, diffBasisMaxBytes })之后依次执行:resolve → writeText(新建) → readText → writeText(替换) → editText → readText结果:三条写路径全部通过,远端
cat内容与写入完全一致;未打补丁时第一条就报EPERM ... mkdir '\\sshfs\<host>\'。附:挂载侧另外两个必需的参数(与本仓无关,供其他用户参考)
如果后续要支持「把远端目录当工作区」,除本问题外还有两处拦路虎,都在挂载参数上:
-o VolumePrefix=...:不带它就是一个「本地型」WinFsp 卷,Node 的realpath.native(内部走GetFinalPathNameByHandle)报UNKNOWN: unknown error, realpath,resolve()直接失败,DSH 所有文件工具都停在第一步;-o FileSecurity=...:不带它时 WinFsp 把 POSIX700目录映射成拒绝挂载用户的 DACL,服务器/root连列目录都做不到(dir X:\root报「拒绝访问」)。把当前用户 SID 写进 DACL 即可解决后者。
说明
我们已阅读 CONTRIBUTING,理解现阶段不接受外部 PR,因此只在此报告并投票。
All reactions