fs: cpSync copyDir fast path can terminate on filesystem errors
维护者通常 1 天内回复
评估
- 难度
- 4/5
- 预计耗时
- 3-5 天
- 新手友好度
- 64/100
- Issue 类型
- 缺陷
- 描述清晰度
- 基本清楚
- 活跃度
- 冷清
- 技术栈
- cpp, javascript, node.js
调研方向
使用 issue 中的脚本重现 Windows 失败,然后检查 src/node_file.cc 和 CpSyncCopyDir 的实现。跟踪目录迭代、路径规范化和文件类型检查,并验证文件系统失败会变成可捕获的 JavaScript 异常,而不是终止进程。
由索引模型根据 Issue 内容生成。
描述
Version
v22.17.0 and later; verified against v27.0.0-pre built from current main.
Platform
Microsoft Windows NT 10.0.26100.0 x64
Subsystem
fs
What steps will reproduce the bug?
Run this script on Windows:
'use strict';
const assert = require('node:assert');
const { execFileSync, spawnSync } = require('node:child_process');
const {
cpSync,
existsSync,
mkdirSync,
rmSync,
writeFileSync,
} = require('node:fs');
const { tmpdir } = require('node:os');
const { join } = require('node:path');
function run(command, args) {
return execFileSync(command, args, {
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'pipe'],
});
}
function currentWindowsUser() {
return run('whoami', []).trim();
}
function restrictDirectory(dir) {
run('icacls', [dir, '/deny', `${currentWindowsUser()}:(OI)(CI)(RX)`]);
}
function restoreDirectory(dir) {
if (existsSync(dir)) {
run('icacls', [dir, '/remove:d', currentWindowsUser()]);
}
}
if (process.argv[2] === 'child') {
assert.throws(() => {
cpSync(process.argv[3], process.argv[4], { recursive: true });
});
process.exit(0);
}
const root = join(tmpdir(), `node-cpsync-${process.pid}`);
const src = join(root, 'src');
const dest = join(root, 'dest');
const restrictedDir = join(src, 'restricted');
mkdirSync(restrictedDir, { recursive: true });
writeFileSync(join(src, 'readable.txt'), 'readable\n');
writeFileSync(join(restrictedDir, 'blocked.txt'), 'blocked\n');
restrictDirectory(restrictedDir);
try {
const child = spawnSync(process.execPath, [__filename, 'child', src, dest], {
encoding: 'utf8',
});
console.log({
status: child.status,
signal: child.signal,
stdout: child.stdout,
stderr: child.stderr,
});
} finally {
restoreDirectory(restrictedDir);
rmSync(root, { recursive: true, force: true });
}
The important part is that fs.cpSync(src, dest, { recursive: true }) is called without a filter option, so it uses the native copyDir fast path.
How often does it reproduce? Is there a required condition?
It reproduces when the native fs.cpSync() copyDir fast path encounters a filesystem error during directory iteration, path canonicalization, or file type checks.
The fast path is used for recursive directory copies when no filter option is provided. Passing filter: () => true avoids this path and falls back to the JavaScript implementation.
What is the expected behavior? Why is that the expected behavior?
fs.cpSync() should report the filesystem failure as a JavaScript exception that callers can catch with try/catch or assert.throws().
Filesystem APIs should convert native filesystem failures into JavaScript errors instead of terminating the process.
What do you see instead?
Some std::filesystem calls in the native copyDir implementation use throwing overloads. When those operations fail, the C++ exception can bypass Node's normal error conversion.
Instead of a catchable JavaScript exception, the process can terminate in the native layer.
Additional information
This appears to come from src/node_file.cc's CpSyncCopyDir implementation. Some calls already use std::error_code, but others use throwing std::filesystem overloads, including directory iteration and file type/path checks.
The fix is to use non-throwing std::filesystem overloads throughout the copyDir path and convert each failure with ThrowStdErrException.
- 主要语言
- JavaScript
- 星标
- 122k
- 派生
- 38.4k
- 平均合并
- 3 天 22 小时
- 30 天内合并 PR
- 273
环境准备
- 没有 Dockerfile 或 Docker Compose 文件
- 有 Pull Request 模板
- 阅读贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
nodejs/node 的其他 Issue
-
build / doc: missing platform and toolchain info for `linux-x64-musl`可能已有人在做 关联的 PR 仍在进行中或已合并。 未关闭alpine build doc
难度 2/5 1-3 小时 新手友好度 75/100
维护者通常 1 天内回复
-
[Docs] `process.loadEnvFile()` does not document behaviour when variables already exist可能已有人在做 @Sepandard 于 10 天前认领。 未关闭doc
难度 1/5 1 小时以内 新手友好度 90/100
维护者通常 1 天内回复
-
Stream.prototype.forEach will block in first promise in queue before read more chunk可能已有人在做 @mmustafasenoglu 于 10 天前认领。 未关闭doc
难度 2/5 1-3 小时 新手友好度 65/100
维护者通常 1 天内回复
-
build
难度 1/5 1 小时以内 新手友好度 88/100
维护者通常 1 天内回复
-
`TextEncoder.encodeInto()` underfills the destination for some non-ASCII text可能已有人在做 @XadillaX 于 24 天前认领。 未关闭
难度 2/5 1-3 小时 新手友好度 84/100
nodejs/node#65994 · 2 条评论 · 2 个 reaction ·
维护者通常 1 天内回复
相似的 Issue
-
难度 2/5 1-3 小时 新手友好度 80/100
-
documentation
难度 1/5 1 小时以内 新手友好度 88/100
githubnext/gh-aw-workshop#4251 ·
维护者通常 1 天内回复
-
customer-support needs-triage Platform(Default)
难度 2/5 1-3 小时 新手友好度 75/100
维护者通常 2 天内回复
-
难度 2/5 1-3 小时 新手友好度 72/100
Leonxlnx/taste-skill#129 ·
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 76/100