Skip to content

Commit 73171e8

Browse files
committed
fs: use libuv scandir for cpSync directory iteration
Replace std::filesystem::directory_iterator with libuv's uv_fs_scandir to avoid process abort when copying directories with non-ASCII paths. Previously, std::filesystem::directory_iterator could throw uncatchable exceptions when encountering encoding issues (e.g., GBK-encoded Chinese paths on Windows). In environments with buggy libc++ implementations (especially Electron), this triggers __libcpp_verbose_abort(), causing immediate process termination that cannot be caught by JavaScript try-catch blocks. This patch uses libuv's uv_fs_scandir and uv_fs_scandir_next instead, which properly handle path encoding and return errors that can be converted to JavaScript exceptions. This approach is consistent with other fs operations in Node.js (e.g., fs.readdirSync) and ensures: 1. No process abort on encoding errors 2. Errors are catchable by JavaScript 3. fs.cpSync() can successfully copy directories with non-ASCII paths 4. Consistent behavior across different C++ standard library implementations The fix also adds proper cleanup of uv_fs_t requests on all error paths to prevent resource leaks.
1 parent bf452bb commit 73171e8

1 file changed

Lines changed: 64 additions & 13 deletions

File tree

src/node_file.cc

Lines changed: 64 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3738,23 +3738,64 @@ static void CpSyncCopyDir(const FunctionCallbackInfo<Value>& args) {
37383738
&isolate](std::filesystem::path src,
37393739
std::filesystem::path dest) {
37403740
std::error_code error;
3741-
for (auto dir_entry : std::filesystem::directory_iterator(src)) {
3742-
auto dest_file_path = dest / dir_entry.path().filename();
3741+
3742+
// Use libuv's uv_fs_scandir instead of std::filesystem::directory_iterator
3743+
// to avoid crashes with non-ASCII paths (especially GBK-encoded paths on Windows
3744+
// when running inside Electron with a buggy libc++ implementation).
3745+
auto src_str = ConvertPathToUTF8(src);
3746+
uv_fs_t scandir_req;
3747+
int rc = uv_fs_scandir(nullptr, &scandir_req, src_str.c_str(), 0, nullptr);
3748+
if (rc < 0) {
3749+
env->ThrowUVException(rc, "scandir", src_str.c_str());
3750+
uv_fs_req_cleanup(&scandir_req);
3751+
return false;
3752+
}
3753+
3754+
uv_dirent_t ent;
3755+
while ((rc = uv_fs_scandir_next(&scandir_req, &ent)) != UV_EOF) {
3756+
if (rc < 0) {
3757+
env->ThrowUVException(rc, "scandir", src_str.c_str());
3758+
uv_fs_req_cleanup(&scandir_req);
3759+
return false;
3760+
}
3761+
3762+
auto entry_name = std::filesystem::path(ent.name);
3763+
auto entry_path = src / entry_name;
3764+
auto dest_file_path = dest / entry_name;
37433765
auto dest_str = ConvertPathToUTF8(dest);
3744-
3745-
if (dir_entry.is_symlink()) {
3766+
3767+
// Get entry type using uv_fs_lstat
3768+
uv_fs_t stat_req;
3769+
auto entry_path_str = ConvertPathToUTF8(entry_path);
3770+
rc = uv_fs_lstat(nullptr, &stat_req, entry_path_str.c_str(), nullptr);
3771+
if (rc < 0) {
3772+
env->ThrowUVException(rc, "lstat", entry_path_str.c_str());
3773+
uv_fs_req_cleanup(&stat_req);
3774+
uv_fs_req_cleanup(&scandir_req);
3775+
return false;
3776+
}
3777+
3778+
const uv_stat_t* stat = static_cast<const uv_stat_t*>(stat_req.ptr);
3779+
bool is_symlink = S_ISLNK(stat->st_mode);
3780+
bool is_directory = S_ISDIR(stat->st_mode);
3781+
bool is_regular = S_ISREG(stat->st_mode);
3782+
uv_fs_req_cleanup(&stat_req);
3783+
3784+
if (is_symlink) {
37463785
if (verbatim_symlinks) {
37473786
std::filesystem::copy_symlink(
3748-
dir_entry.path(), dest_file_path, error);
3787+
entry_path, dest_file_path, error);
37493788
if (error) {
37503789
env->ThrowStdErrException(error, "cp", dest_str.c_str());
3790+
uv_fs_req_cleanup(&scandir_req);
37513791
return false;
37523792
}
37533793
} else {
37543794
auto symlink_target =
3755-
std::filesystem::read_symlink(dir_entry.path().c_str(), error);
3795+
std::filesystem::read_symlink(entry_path.c_str(), error);
37563796
if (error) {
37573797
env->ThrowStdErrException(error, "cp", dest_str.c_str());
3798+
uv_fs_req_cleanup(&scandir_req);
37583799
return false;
37593800
}
37603801

@@ -3764,6 +3805,7 @@ static void CpSyncCopyDir(const FunctionCallbackInfo<Value>& args) {
37643805
std::filesystem::read_symlink(dest_file_path.c_str(), error);
37653806
if (error) {
37663807
env->ThrowStdErrException(error, "cp", dest_str.c_str());
3808+
uv_fs_req_cleanup(&scandir_req);
37673809
return false;
37683810
}
37693811

@@ -3774,6 +3816,7 @@ static void CpSyncCopyDir(const FunctionCallbackInfo<Value>& args) {
37743816
"Cannot copy %s to a subdirectory of self %s";
37753817
THROW_ERR_FS_CP_EINVAL(
37763818
env, message, symlink_target, current_dest_symlink_target);
3819+
uv_fs_req_cleanup(&scandir_req);
37773820
return false;
37783821
}
37793822

@@ -3786,6 +3829,7 @@ static void CpSyncCopyDir(const FunctionCallbackInfo<Value>& args) {
37863829
"cannot overwrite %s with %s";
37873830
THROW_ERR_FS_CP_SYMLINK_TO_SUBDIRECTORY(
37883831
env, message, current_dest_symlink_target, symlink_target);
3832+
uv_fs_req_cleanup(&scandir_req);
37893833
return false;
37903834
}
37913835

@@ -3795,6 +3839,7 @@ static void CpSyncCopyDir(const FunctionCallbackInfo<Value>& args) {
37953839
std::filesystem::remove(dest_file_path, error);
37963840
if (error) {
37973841
env->ThrowStdErrException(error, "cp", dest_str.c_str());
3842+
uv_fs_req_cleanup(&scandir_req);
37983843
return false;
37993844
}
38003845
} else if (std::filesystem::is_regular_file(dest_file_path)) {
@@ -3804,13 +3849,14 @@ static void CpSyncCopyDir(const FunctionCallbackInfo<Value>& args) {
38043849
std::make_error_code(std::errc::file_exists),
38053850
"cp",
38063851
dest_file_path_str.c_str());
3852+
uv_fs_req_cleanup(&scandir_req);
38073853
return false;
38083854
}
38093855
}
38103856
}
38113857
auto symlink_target_absolute = std::filesystem::weakly_canonical(
38123858
std::filesystem::absolute(src / symlink_target));
3813-
if (dir_entry.is_directory()) {
3859+
if (is_directory) {
38143860
std::filesystem::create_directory_symlink(
38153861
symlink_target_absolute, dest_file_path, error);
38163862
} else {
@@ -3819,37 +3865,42 @@ static void CpSyncCopyDir(const FunctionCallbackInfo<Value>& args) {
38193865
}
38203866
if (error) {
38213867
env->ThrowStdErrException(error, "cp", dest_str.c_str());
3868+
uv_fs_req_cleanup(&scandir_req);
38223869
return false;
38233870
}
38243871
}
3825-
} else if (dir_entry.is_directory()) {
3826-
auto entry_dir_path = src / dir_entry.path().filename();
3872+
} else if (is_directory) {
38273873
std::filesystem::create_directory(dest_file_path);
3828-
auto success = copy_dir_contents(entry_dir_path, dest_file_path);
3874+
auto success = copy_dir_contents(entry_path, dest_file_path);
38293875
if (!success) {
3876+
uv_fs_req_cleanup(&scandir_req);
38303877
return false;
38313878
}
3832-
} else if (dir_entry.is_regular_file()) {
3879+
} else if (is_regular) {
38333880
std::filesystem::copy_file(
3834-
dir_entry.path(), dest_file_path, file_copy_opts, error);
3881+
entry_path, dest_file_path, file_copy_opts, error);
38353882
if (error) {
38363883
if (error.value() == EEXIST) {
38373884
THROW_ERR_FS_CP_EEXIST(isolate,
38383885
"[ERR_FS_CP_EEXIST]: Target already exists: "
38393886
"cp returned EEXIST (%s already exists)",
38403887
dest_file_path);
3888+
uv_fs_req_cleanup(&scandir_req);
38413889
return false;
38423890
}
38433891
env->ThrowStdErrException(error, "cp", dest_str.c_str());
3892+
uv_fs_req_cleanup(&scandir_req);
38443893
return false;
38453894
}
38463895

38473896
if (preserve_timestamps &&
3848-
!CopyUtimes(dir_entry.path(), dest_file_path, env)) {
3897+
!CopyUtimes(entry_path, dest_file_path, env)) {
3898+
uv_fs_req_cleanup(&scandir_req);
38493899
return false;
38503900
}
38513901
}
38523902
}
3903+
uv_fs_req_cleanup(&scandir_req);
38533904
return true;
38543905
};
38553906

0 commit comments

Comments
 (0)