Replies: 6 comments
|
@buddha68 你的三处源级判断我在 1. write 后端确实是「临时文件 + link 硬链接」——你从报错签名反推正确
所以你的"依据报错签名判断"准确无误。 2. EISDIR 来自 Node/libuv 运行时层, 不是 dsh 应用层——但 dsh 侧可以兜住
function errorMessage(error: unknown): string {
return error instanceof Error ? error.message : String(error)
}它不做任何 errno 翻译。所以在 也就是说: EISDIR 是 Node ( 3. 修复方向 — 和已有的一个同族缺口正好同源这其实不是孤例。
4. 附带发现(盘根 mkdir EPERM)我确认了同类你 #5 的 家族关联(供 maintainer 参考)我把这个归入"原子发布原语假设通用 FS 能力"家族——同轴已有 #3577(Windows rename EXDEV, 两个小问题可进一步确认: ① 你写 |
|
@argszero 感谢如此快速的逐项核实,源码锚点(fsio.ts:546/580、44-46、543)非常清楚,「EISDIR 来自 libuv 运行时层而非 dsh 应用层」这一点彻底闭环了我们反馈里的第 2 问。 回答你的两个小问题: ① 新文件还是已存在文件? 是新文件——目标路径当时不存在(报错后 Test-Path 验证为不存在),即走了 createIfAbsent 分支,与你推断的「policy 默认把未观察文件当 createIfAbsent 处理」一致。补充:同日我们在另一块 exFAT 移动硬盘(E 盘)复测,写入的也是新文件,同款报错。我们没有测过「目标已存在」的场景,如果你需要这个数据点,我们可以在 exFAT 实机上补测。 ② rename + 先读后覆盖? 我们没有实测过 rename 方案(当时的绕行验证是脚本直接覆写,exFAT 上可用)。「原文件存在时先读后覆盖以保留 no-clobber 语义」从用户视角听起来是更好的方向,具体取舍交给你和 maintainer 定夺。 我们能配合的:本机有两块 exFAT 移动硬盘(4TB/2TB)+ 全 NTFS 内置盘的 Windows 11 实机,是现成的双环境测试床。如果你出参考 diff(fsio.ts 降级 + errorMessage 修正 + 盘根 mkdir 兜底 + 注入式单测),我们可以在实机装上后逐项复测(exFAT 新文件 / 已存在文件 / 盘根 / NTFS 回归四场景),出实测报告回帖。 Node/libuv 上游的 EISDIR 误映射问题,我们也可以把最小复现整理成英文 issue 提交到 nodejs/node——需要的话说一声。 再次感谢! |
|
@buddha68 感谢反馈两组实机数据(H 盘 + 同款新文件 + E 盘复现),并同意在 exFAT/NTFS 实机上逐项复测。你点的「参考 diff(fsio.ts 降级 + errorMessage 修正 + 盘根 mkdir 兜底 + 注入式单测)」我按 参考 diff:
|
|
@argszero Thank you for the remarkably thorough source-level verification and the reference diff — the precise anchors (fsio.ts:546/580, 44–46, 543) and the libuv-layer answer to our question #2 fully close the loop on our report. Two updates from our side: 1. On the four-scenario retest — we've decided not to apply the patch to our installed runtime; modifying our local installation is more invasive than we're comfortable with, so we'll leave the implementation and retest to the maintainers. One data point we can contribute without any patch: the disk-root 2. Upstream Node/libuv issue — filed. As suggested, we've submitted the Thanks again — happy to provide additional pre-patch data points from our two exFAT drives if anything else would help. |
|
@buddha68 感谢三项更新。你们不装补丁、把实现交给 maintainer 完全合理——已在 Discussion 里给出可验证的参考 diff 本身就是最大的贡献。上游 nodejs/node#65817 已确认:EISDIR 误映射是 libuv 侧独立问题, 值得 maintainer 同时看到。重点说 drive-root mkdir EPERM 这条——它比 exFAT link 更值得单独修, 因为你实测它是跨文件系统的(C/D/E/F/H 五个盘符 + NTFS/exFAT 都有)。 drive-root mkdir EPERM 是独立 bug(非 exFAT 专属)
const directory = dirname(absolutePath) // 盘根下写文件时 = 'H:\'
await mkdir(directory, { recursive: true })
好消息: 这与 exFAT link 是两个可分离的修复, 且这个直接封死"盘根下写文件"这一整类操作。参考 diff(新增一个"mkdir 失败但目录已存在则容忍"的辅助, 只在目录确实存在时抑制 EPERM/EACCES): @@ packages/fs/fs-local/src/fsio.ts @@
- const directory = dirname(absolutePath)
- await mkdir(directory, { recursive: true })
+ const directory = dirname(absolutePath)
+ await ensureDirectory(directory)/** Mkdir the target's parent, tolerating an already-present directory even when
* the OS reports it as EPERM/EACCES (Windows drive roots do this — 'H:\' is
* an existing directory but mkdir returns EPERM, not EEXIST). */
async function ensureDirectory(directory: string): Promise<void> {
try {
await mkdir(directory, { recursive: true })
} catch (error: unknown) {
if (!(error instanceof Error) || ('code' in error && error.code !== 'EPERM' && error.code !== 'EACCES')) throw error
// The parent exists as a directory — recursive:true already did its job;
// only a drive-root (or ACL) "cannot create" that points at an existing
// dir should be tolerated.
let stats
try {
stats = await lstat(directory)
} catch (statError: unknown) {
throw error // dir vanished or unprobeable: keep the original mkdir failure
}
if (!stats.isDirectory()) throw error
}
}
注入式单测(可复测)—— 用 fsio 既有 internals 挂钩
it('tolerates mkdir EPERM when the parent already exists (Windows drive root)', async () => {
// 模拟 drive-root: mkdir 抛 EPERM, lstat 报 directory
const file = join(dir, 'a.txt')
await writeFileAtomic(file, 'ours', undefined, undefined, {
mkdir: async () => { throw Object.assign(new Error('EPERM'), { code: 'EPERM' }) },
}, { displayPath: file })
expect(await readFile(file, 'utf8')).toBe('ours')
})结论与分工两个修复互相独立、可分开合:
你们两个数据点(exFAT link EISDIR + 五盘符 drive-root EPERM)让 maintainer 可以明确区分 fix A/B 两个独立缺陷。后续我不主动追, 等 maintainer 或 nodejs/node 侧进展; 若你愿意, 修好后可在实机核一遍 fix A(盘根下写新文件, NTFS 即可, 无需 exFAT)。 |
|
@argszero Thank you — the decomposition into two independent fixes (A: drive-root mkdir EPERM, B: exFAT link fallback) is very clear, and the prioritization makes sense. On the follow-up: yes, we're willing. To be precise about the boundary we set earlier — what we'd rather not do is patch our installed runtime by hand. But once a fix lands in an official release, we'll upgrade and verify on our machine: fix A is easy to re-check (write a new file at a drive root on NTFS), and we'll sanity-check fix B on the two exFAT volumes in the same pass. Results will be reported back here. Thanks again for the thorough work on both diffs and for the upstream analysis. |
Uh oh!
There was an error while loading. Please reload this page.
致 DeepSeek(dsh)技术反馈:文件写入工具在 exFAT 卷上必然失败(EISDIR 误报)
一、症结
write 工具向 exFAT 卷写文件时,报如下错误:
两个问题:
从报错签名(
link '<临时文件>' -> '<目标>')判断,write 后端的落盘方式是:先在目标目录旁写一个.tmpdir临时文件,再用 link 硬链接到最终路径(原子替换的常见实现)。此为依据报错形态的判断,我们未核对源码,请贵方确认。二、实测矩阵(同一台机器、同一会话、2026-09-04)
规律完全切分:NTFS 全好,exFAT 全挂,与盘是内置还是外接无关——只与文件系统类型相关。
三、根因对照实验(绕开 dsh,直接验证 link)
在 PowerShell 里用 Python 对各卷执行
os.link(src, dst):四、最小复现(不依赖 dsh,任何 exFAT 卷可复)
五、附带发现:向任何盘的根目录写入均报 EPERM
write 工具写
X:\根目录下的文件时(C/D/E/F/H 全部盘符实测),报EPERM mkdir 'X:\'。写入前的父目录确保逻辑(ensureParentDir)似乎对「盘根」这个已存在的路径仍发起了 mkdir。此问题影响小(日常很少向盘根写文件),但与 EISDIR 一并反馈。六、影响面与当前规避
七、修复建议(按优先级)
八、请贵方确认的问题
All reactions